* fix(javascript): decline source-map UNC references with an unusable host
A source map can name a Windows share whose host a file URL cannot carry,
such as `\\bad host\share\a.js`. On the Node builds `engines` admits,
`pathToFileURL(path, { windows: true })` fails a native assertion and calls
`abort()` on Node 22.19-22.22, 24.11-24.15 and 26.0, so the process dies
with exit 134 instead of declining the reference. Node 22.23 and 24.16 raise
`ERR_INVALID_URL` instead, and older builds silently drop the host, so the
same reference resolves three different ways depending on the runtime.
Validate the UNC host before handing the path to Node. A host carrying a
forbidden host code point, a percent escape or nothing at all now resolves as
`unresolved` on every admitted build, and `npm test` passes the existing
`\\bad host\share\a.js` row everywhere. Well-formed UNC hosts and
drive-letter paths are untouched.
* fix(javascript): validate UNC hosts through IDNA before conversion
Reject Unicode source-map UNC hosts that URL/IDNA processing cannot use or normalizes to an empty hostname before calling Windows pathToFileURL. Keep the original source identity and preserve usable normalized hosts.
Exercise the built reference resolver in owned subprocesses, including U+00AD and U+200C as source names and source roots. The original PR implementation aborts under Node 22.22.2; the new regression isolates those signals from the test runner. Add this test to the existing Node 22 Windows CI step.
Validation: 64 focused tests pass on Node 22.22.2 and 24.18.0; npm run check and test discovery pass. Local verification ran on macOS; Windows verification remains with CI.
---------
Co-authored-by: Yi-111-a <yi-111-a@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
499 lines
19 KiB
YAML
499 lines
19 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main, "release/*"]
|
|
|
|
concurrency:
|
|
group: ci-${{ github.event.pull_request.number || github.ref }}
|
|
# The latest main push verifies the full tree, including earlier changes.
|
|
# Publication uses its own release workflow and concurrency group.
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
HUSKY: "0"
|
|
|
|
jobs:
|
|
pr-title:
|
|
name: Conventional PR title
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Validate title for Release Please
|
|
env:
|
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
run: >-
|
|
node -e 'const title = process.env.PR_TITLE ?? "";
|
|
const conventional = /^(?:feat|fix|docs|refactor|perf|test|build|ci|chore|revert)(?:\([a-z0-9._/-]+\))?!?: .+/u;
|
|
if (!conventional.test(title)) {
|
|
console.error("PR title must use Conventional Commits, for example: feat: add historical source import");
|
|
process.exit(1);
|
|
}'
|
|
|
|
changes:
|
|
name: Classify changes
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
code: ${{ steps.package.outputs.required }}
|
|
package: ${{ steps.package.outputs.required }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Select package and Windows lanes
|
|
id: package
|
|
shell: bash
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
if [[ "${GITHUB_EVENT_NAME}" != "pull_request" ]]; then
|
|
echo "required=true" >> "${GITHUB_OUTPUT}"
|
|
# Compare the PR's changes, excluding unrelated updates on its base.
|
|
elif git diff --quiet "${BASE_SHA}...${HEAD_SHA}" -- \
|
|
. \
|
|
':(exclude)docs/**' \
|
|
':(top,exclude,glob)README*.md' \
|
|
':(exclude)AGENTS.md' \
|
|
':(exclude)CONTRIBUTING.md'; then
|
|
echo "required=false" >> "${GITHUB_OUTPUT}"
|
|
else
|
|
diff_status=$?
|
|
# A failed comparison must fail classification, not report a scope.
|
|
if [[ "${diff_status}" -ne 1 ]]; then
|
|
exit "${diff_status}"
|
|
fi
|
|
echo "required=true" >> "${GITHUB_OUTPUT}"
|
|
fi
|
|
|
|
static:
|
|
name: Static checks
|
|
needs: changes
|
|
if: ${{ always() }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Require successful change classification
|
|
env:
|
|
CHANGES_RESULT: ${{ needs.changes.result }}
|
|
run: test "${CHANGES_RESULT}" = "success"
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
run: npm run check:ci
|
|
- if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
run: npm run verify:test-discovery
|
|
- if: github.event_name == 'pull_request' && needs.changes.outputs.code != 'true'
|
|
run: npm run format:check
|
|
|
|
test-shard:
|
|
name: Test shard (${{ matrix.shard }})
|
|
needs: [changes, build]
|
|
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1/4, 2/4, 3/4, 4/4]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
name: rea-dist
|
|
path: dist
|
|
- name: Generate test artifacts from the downloaded runtime
|
|
run: |
|
|
node scripts/generate-mcp-tool-catalog.mjs
|
|
node scripts/generate-skill-metadata.mjs
|
|
node scripts/generate-product-catalog.mjs
|
|
node scripts/generate-completion-ledger.mjs
|
|
- run: npm run test:ci:shard:run -- --shard=${{ matrix.shard }}
|
|
- uses: actions/upload-artifact@v4
|
|
if: ${{ !cancelled() }}
|
|
with:
|
|
name: vitest-report-${{ strategy.job-index }}
|
|
path: .vitest/blob/*
|
|
include-hidden-files: true
|
|
if-no-files-found: error
|
|
overwrite: true
|
|
retention-days: 7
|
|
|
|
build:
|
|
name: Build package
|
|
needs: changes
|
|
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run build:cached
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: rea-dist
|
|
path: dist
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
test:
|
|
name: Test (coverage + timing)
|
|
needs: [changes, test-shard]
|
|
if: ${{ always() }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Require successful test shards
|
|
env:
|
|
CHANGES_RESULT: ${{ needs.changes.result }}
|
|
CODE_REQUIRED: ${{ needs.changes.outputs.code }}
|
|
SHARD_RESULT: ${{ needs.test-shard.result }}
|
|
run: |
|
|
test "${CHANGES_RESULT}" = "success"
|
|
test "${CODE_REQUIRED}" != "true" || test "${SHARD_RESULT}" = "success"
|
|
- uses: actions/checkout@v4
|
|
if: needs.changes.outputs.code == 'true' && success()
|
|
- uses: actions/setup-node@v4
|
|
if: needs.changes.outputs.code == 'true' && success()
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- if: needs.changes.outputs.code == 'true' && success()
|
|
run: npm ci
|
|
- uses: actions/download-artifact@v4
|
|
if: needs.changes.outputs.code == 'true' && success()
|
|
with:
|
|
path: .vitest/blob
|
|
pattern: vitest-report-*
|
|
merge-multiple: true
|
|
- name: Merge coverage, JUnit, and timing reports
|
|
if: needs.changes.outputs.code == 'true' && success()
|
|
run: >-
|
|
npm run test:ci:merge --
|
|
--coverage.reportsDirectory=coverage
|
|
--coverage.reporter=text
|
|
--coverage.reporter=json-summary
|
|
--coverage.reporter=lcov
|
|
--reporter=default
|
|
--reporter=junit
|
|
--reporter=json
|
|
--outputFile.junit=test-results.xml
|
|
--outputFile.json=test-results.json
|
|
- name: Summarize slowest test files
|
|
if: needs.changes.outputs.code == 'true' && success()
|
|
shell: bash
|
|
run: |
|
|
node <<'NODE' >> "${GITHUB_STEP_SUMMARY}"
|
|
const report = JSON.parse(require("node:fs").readFileSync("test-results.json", "utf8"));
|
|
const files = (report.testResults ?? [])
|
|
.map((result) => ({
|
|
name: result.name,
|
|
duration: Math.max(0, (result.endTime ?? result.startTime) - result.startTime),
|
|
}))
|
|
.sort((left, right) => right.duration - left.duration)
|
|
.slice(0, 20);
|
|
console.log("## Slowest deterministic test files\n");
|
|
console.log("| File | Duration (ms) |");
|
|
console.log("| --- | ---: |");
|
|
for (const file of files) {
|
|
console.log(`| \`${file.name}\` | ${file.duration} |`);
|
|
}
|
|
NODE
|
|
- uses: actions/upload-artifact@v4
|
|
if: needs.changes.outputs.code == 'true' && always()
|
|
with:
|
|
name: test-results
|
|
path: |
|
|
test-results.xml
|
|
test-results.json
|
|
coverage/
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
conformance-fixtures:
|
|
name: Source-built conformance fixtures
|
|
needs: changes
|
|
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Verify fixtures from a clean checkout
|
|
run: npm run verify:fixtures
|
|
|
|
windows-native-build:
|
|
name: Build bundled Windows x64 native controls
|
|
needs: changes
|
|
if: github.event_name == 'push' || needs.changes.outputs.package == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
- name: Prepare cross compiler
|
|
run: |
|
|
# Slow Azure archive transfers can exhaust the job before compilation.
|
|
# Keep the same signed packages and avoid slow HTTP mirror fallback.
|
|
for source in /etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources; do
|
|
if [ -f "$source" ]; then
|
|
sudo sed -i 's|azure.archive.ubuntu.com|archive.ubuntu.com|g; s|http://archive.ubuntu.com|https://archive.ubuntu.com|g' "$source"
|
|
fi
|
|
done
|
|
sudo apt-get update
|
|
sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
|
|
- run: npm run build:windows-native
|
|
- run: npm run verify:windows-native:artifact
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-native-controls
|
|
path: native/windows/build/
|
|
if-no-files-found: error
|
|
|
|
package-e2e:
|
|
name: Native package baseline (${{ matrix.platform }} ${{ matrix.architecture }})
|
|
needs: [changes, windows-native-build]
|
|
if: github.event_name == 'push' || needs.changes.outputs.package == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 2
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
platform: linux
|
|
architecture: x64
|
|
- os: ubuntu-24.04-arm
|
|
platform: linux
|
|
architecture: arm64
|
|
- os: macos-15
|
|
platform: darwin
|
|
architecture: arm64
|
|
- os: macos-15-intel
|
|
platform: darwin
|
|
architecture: x64
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20
|
|
env:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
UV_THREADPOOL_SIZE: "2"
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- name: Verify native runtime identity
|
|
env:
|
|
EXPECTED_CI_PLATFORM: ${{ matrix.platform }}
|
|
EXPECTED_CI_ARCHITECTURE: ${{ matrix.architecture }}
|
|
run: >-
|
|
node -e "const assert = require('node:assert/strict');
|
|
assert.equal(process.platform, process.env.EXPECTED_CI_PLATFORM);
|
|
assert.equal(process.arch, process.env.EXPECTED_CI_ARCHITECTURE);
|
|
console.log(JSON.stringify({ platform: process.platform, architecture: process.arch, node: process.version }));"
|
|
- run: npm ci
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
name: windows-native-controls
|
|
path: native/windows/build/
|
|
- run: npm run verify:windows-native:artifact
|
|
- run: npm run verify:package
|
|
- name: Verify real Node Inspector discovery and observation
|
|
timeout-minutes: 10
|
|
run: npm run verify:inspector
|
|
|
|
apple-artifacts:
|
|
name: Real Apple artifact CLI/MCP E2E
|
|
needs: changes
|
|
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
runs-on: macos-15
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run verify:keyed-archive
|
|
- run: npm run verify:asset-catalog
|
|
- run: npm run verify:macos-bundle
|
|
- name: Legacy simulator platform verification
|
|
run: npm run verify:legacy-simulator
|
|
- name: Native installer and compiled NIB hierarchy regressions
|
|
run: npm run test:focused -- tests/boundary/filesystem/installScript.test.ts tests/boundary/filesystem/nibNativeHierarchy.test.ts tests/boundary/filesystem/interfaceBuilderBundle.test.ts
|
|
- name: Real binary metadata and Interface Builder integration
|
|
run: npm run verify:apple-dispatch && npm run verify:interface-builder
|
|
- name: Native plist CLI selection regressions
|
|
run: npm run test:focused -- tests/boundary/cli/nativePlistCli.test.ts tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts tests/boundary/cli/nativePlistSelectionFailureCli.test.ts
|
|
- name: Native Swift demangling CLI and MCP regressions
|
|
run: npm run test:focused -- tests/boundary/cli/nativeDemangleCli.test.ts tests/boundary/mcp/nativeDemangleMcp.test.ts
|
|
- name: Native signature target binding and CLI/MCP recovery regressions
|
|
run: npm run test:focused -- tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts tests/boundary/cli/nativeSignatureCli.test.ts tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
|
|
- name: Process filesystem snapshot descriptor regressions
|
|
run: npm run test:focused -- src/process/capture/FilesystemSnapshot.test.ts
|
|
- name: Native Darwin ownership and process cleanup regressions
|
|
run: npm run test:focused -- tests/boundary/process/darwinProcessRunTokenReader.test.ts tests/boundary/process/directAnalysisSignals.test.ts tests/boundary/process/processCaptureLifecycle.test.ts tests/boundary/process/processCli.test.ts tests/boundary/mcp/sessionMcp.test.ts
|
|
|
|
reconstruction-readiness:
|
|
name: Reconstruction readiness
|
|
needs: changes
|
|
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- name: Run the public CLI/MCP reconstruction journey
|
|
env:
|
|
REA_READINESS_REPORT_PATH: readiness-report.json
|
|
run: |
|
|
set -o pipefail
|
|
npm run verify:readiness | tee readiness-summary.json
|
|
- uses: actions/upload-artifact@v4
|
|
if: ${{ !cancelled() }}
|
|
with:
|
|
name: reconstruction-readiness
|
|
path: |
|
|
readiness-summary.json
|
|
readiness-report.json
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
windows-curated:
|
|
name: Windows capability and package lane
|
|
needs: [changes, windows-native-build]
|
|
if: github.event_name == 'push' || needs.changes.outputs.package == 'true'
|
|
runs-on: windows-latest
|
|
timeout-minutes: 25
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run build:cached
|
|
- run: npm run typecheck
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
name: windows-native-controls
|
|
path: native/windows/build/
|
|
- run: npm run verify:windows-native:artifact
|
|
- name: Record named Windows host capabilities
|
|
run: node scripts/windows-capabilities.mjs | Out-File -Encoding utf8NoBOM windows-capabilities.json
|
|
- name: Validate Windows host capability report
|
|
run: >-
|
|
node -e "const report = JSON.parse(require('node:fs').readFileSync('windows-capabilities.json', 'utf8'));
|
|
if (report.platform !== 'win32' || report.architecture !== 'x64')
|
|
throw new Error('expected a Windows x64 capability report');"
|
|
# Keep this list aligned with tests/process-global/curatedWindowsLane.test.ts,
|
|
# which fails when any path below is renamed or removed. Vitest silently
|
|
# ignores unmatched paths, so a stale entry would otherwise report green
|
|
# while skipping coverage.
|
|
#
|
|
# The matching native artifact establishes real Job Object and private
|
|
# runtime boundaries for these fixtures. Ghidra and Java are not needed;
|
|
# real engine claims remain in the separate controlled-provider workflow.
|
|
- name: Run curated Windows capability tests
|
|
run: >-
|
|
npx vitest run --coverage.enabled=false
|
|
tests/boundary/filesystem/binaryTarget.test.ts
|
|
tests/boundary/filesystem/safeOutputTree.test.ts
|
|
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
|
|
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
|
|
tests/boundary/filesystem/setupUnavailableProvider.test.ts
|
|
tests/boundary/filesystem/windowsNpxRegistration.test.ts
|
|
tests/boundary/filesystem/webScriptManifestPaths.test.ts
|
|
tests/boundary/process/ProcessEvidence.host.test.ts
|
|
tests/acceptance/webModuleCli.test.ts
|
|
tests/acceptance/webSourceLocationCli.test.ts
|
|
tests/boundary/cli/referenceSourceImportStatus.test.ts
|
|
tests/boundary/filesystem/asarInventoryFreshness.test.ts
|
|
tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts
|
|
tests/conformance/providers/ghidra/windowsCapabilities.test.ts
|
|
src/ghidra/GhidraInstallation.test.ts
|
|
tests/boundary/providers/ghidra/ghidraTransport.test.ts
|
|
tests/boundary/providers/ghidra/ghidraTargetSnapshot.test.ts
|
|
tests/boundary/providers/ghidra/ghidraLauncher.test.ts
|
|
tests/boundary/providers/hopper/hopperClient.cleanupRetry.test.ts
|
|
src/windows/WindowsNativeLoader.test.ts
|
|
src/windows/WindowsOwnedProcess.test.ts
|
|
src/ghidra/GhidraLauncherEnvironment.test.ts
|
|
src/process/ProcessOwnership.test.ts
|
|
src/process/ProcessOwnership.part2.test.ts
|
|
src/process/ProcessOwnership.part3.test.ts
|
|
src/process/ProcessOwnership.validation.test.ts
|
|
src/application/Update.test.ts
|
|
tests/process-global/updateMaintenance.test.ts
|
|
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
|
|
- run: npm run verify:windows-native
|
|
- name: Verify real Node Inspector discovery and observation
|
|
timeout-minutes: 10
|
|
env:
|
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
|
UV_THREADPOOL_SIZE: "2"
|
|
run: npm run verify:inspector
|
|
- run: npm run verify:package:windows
|
|
- name: Verify native directory identity on Node 24
|
|
run: >-
|
|
npx vitest run --coverage.enabled=false
|
|
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
|
|
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
|
|
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22.19.0
|
|
- name: Verify native path boundaries on Node 22
|
|
run: >-
|
|
npx vitest run --coverage.enabled=false
|
|
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
|
|
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
|
|
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
|
tests/boundary/process/sourceMapUncHosts.test.ts
|
|
- uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: windows-capabilities
|
|
path: windows-capabilities.json
|
|
retention-days: 30
|
|
|
|
docs:
|
|
name: Docs Generation
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run docs:check
|
|
- name: Generation leaves authored files unchanged
|
|
run: git diff --exit-code
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: generated-docs
|
|
path: |
|
|
docs/public/product-catalog.json
|
|
docs/verification/managed-conformance-*.json
|
|
skills/reverse-engineer-anything
|
|
if-no-files-found: error
|