diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a02b8463c1..fe8214e3d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -819,6 +819,11 @@ jobs: with: save: "true" - run: npm ci + - name: Typecheck with the official Windows compiler + env: + GOMAXPROCS: "2" + GOMEMLIMIT: "3GiB" + run: npm run typecheck -- --checkers 2 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: rea-build-cache @@ -849,6 +854,7 @@ jobs: npx vitest run --coverage.enabled=false src/process/capture/FilesystemSnapshot.test.ts tests/process-global/filesystemSnapshotFallbackCancellation.test.ts + tests/boundary/filesystem/typescriptCompiler.test.ts tests/boundary/filesystem/binaryTarget.test.ts tests/boundary/filesystem/safeOutputTree.test.ts tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts diff --git a/docs/testing.md b/docs/testing.md index 4152100bd3..43ba47cde5 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -46,6 +46,24 @@ without executing tests or building. A missing Git base reports how to fetch it or select another revision. Source typechecking needs no compiled runtime or generated test metadata. +Typecheck, runtime compilation, and Frida binding verification share +`scripts/run-typescript.mjs`. The pinned tsrs package supplies Linux x64/arm64 +(GNU and musl) and macOS arm64 compilers. Windows and other unsupported hosts +retain the existing stable official TypeScript compiler. Its upstream version +can differ from tsrs, so both compiler tasks hash the selected package and version +through `scripts/run-turbo.mjs` before cache lookup. Outputs from different +compilers occupy different cache entries. Repository npm scripts and focused-test +preparation use that entrypoint; a raw Turbo compiler task without matching +identity fails instead of publishing an ambiguous successful cache entry. +A missing binary on a supported host also fails without silently falling back. + +The Rust compiler defaults to at most two checker threads. For `--noEmit` checks, +`--maxMemory 3G` also sets a soft memory target; emit builds do not use that target. +Explicit compiler arguments can override the defaults. These controls do not +impose a process-family memory limit. Termux's explicit build, Stryker's compiler +API dependency, and independent fixture compiler oracles keep their existing +TypeScript versions. + `npm run build:cached` compiles the CLI/MCP runtime and its packaged skill. Turbo's strict task environment preserves caller-selected `GOMAXPROCS`, `GOMEMLIMIT`, and `UV_THREADPOOL_SIZE` for resource-constrained builds and @@ -233,6 +251,11 @@ and retain representative compiled CLI parity checks. Every distinct source scenario still runs through the public workflow; session-lifecycle scenarios need their own sessions and cleanup assertions. +When a matrix already holds a named tool definition from live `tools/list`, +pass that definition through the SDK's `toolDefinition` call option. This keeps +the selected output-schema validation without eagerly compiling unrelated +catalog schemas. Re-read definitions if the server advertises a changed list. + Report unavailable authority as a named skip. Await owned cleanup before a verifier reports success. Native process inspectors are released at Vitest worker teardown; fork termination does not run Node exit hooks, and per-file @@ -648,8 +671,11 @@ missing entries are omitted as whole entries, and consumers rebuild cache misses including when every entry is unavailable. Malformed plans, non-file entries, and I/O errors other than disappearance still fail publication. After a trusted static run, superseded cross-run Turbo snapshots for that OS and architecture -are deleted down to the two newest generations. The Windows lane relies on the Linux static job for typechecking. They retain the normal build and prepack -commands, which restore matching outputs or rebuild on a cache miss. Native +are deleted down to the two newest generations. The Windows lane also runs the +official compiler's complete source typecheck with two checker threads, +independently of the accelerated Linux checker. Both lanes retain the normal +build and prepack commands, which restore matching outputs or rebuild on a cache +miss. Native artifacts, dependency installs, package archives, and runtime verification remain host-local. Keep platform-dependent outputs out of this shared cache. diff --git a/licenses/npm-dependencies.txt b/licenses/npm-dependencies.txt index 8225eedadf..03d4921e06 100644 --- a/licenses/npm-dependencies.txt +++ b/licenses/npm-dependencies.txt @@ -2,7 +2,7 @@ REA npm dependency inventory Prepared from the root package-lock.json; refresh this inventory when locked dependencies change. License identifiers are upstream lockfile declarations, not a legal assessment. Runtime includes transitive and optional platform packages; development includes build, test and website tooling. -Locked package entries: 752; runtime: 180. +Locked package entries: 758; runtime: 180. Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt. @algolia/abtesting@1.25.0 | development | MIT @@ -1390,6 +1390,31 @@ commander@13.1.0 | runtime | MIT Package: https://www.npmjs.com/package/@toon-format/toon/v/2.3.1 Archive: https://registry.npmjs.org/@toon-format/toon/-/toon-2.3.1.tgz Integrity: sha512-sWqEMeAJGMda9qRrfPKrX3C4c33CO9SJuvJzzrcBEn5sbDB+k6pSLkDsfN0rVnLQwR97MV3sDgVbcxQNsq8xVw== +@tsrs/darwin-arm64@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0 + Lockfile path: node_modules/@tsrs/darwin-arm64 + Package: https://www.npmjs.com/package/@tsrs/darwin-arm64/v/0.18.0-ts7.1.0-dev.20260929 + Archive: https://registry.npmjs.org/@tsrs/darwin-arm64/-/darwin-arm64-0.18.0-ts7.1.0-dev.20260929.tgz + Integrity: sha512-sAirMpaGA9uRQm2W+t4zC7PjnijNuFa4PCSIfLKKo0JyAUCRfnMeev0UGBQ9F3g+9Nz187tvikUs5Irh5+eajA== +@tsrs/linux-arm64-gnu@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0 + Lockfile path: node_modules/@tsrs/linux-arm64-gnu + Package: https://www.npmjs.com/package/@tsrs/linux-arm64-gnu/v/0.18.0-ts7.1.0-dev.20260929 + Archive: https://registry.npmjs.org/@tsrs/linux-arm64-gnu/-/linux-arm64-gnu-0.18.0-ts7.1.0-dev.20260929.tgz + Integrity: sha512-zm9/z9yylig68JiZF4Aq0Cm26n09KEU8YoQRlG7tdDWTBNAIViYw8knxnWYsjMWVZ2XiU4mYq1xdrOKuOgABHg== +@tsrs/linux-arm64-musl@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0 + Lockfile path: node_modules/@tsrs/linux-arm64-musl + Package: https://www.npmjs.com/package/@tsrs/linux-arm64-musl/v/0.18.0-ts7.1.0-dev.20260929 + Archive: https://registry.npmjs.org/@tsrs/linux-arm64-musl/-/linux-arm64-musl-0.18.0-ts7.1.0-dev.20260929.tgz + Integrity: sha512-Lw7VVU/YQ9JcarODICEiwzMLdFqZprqh5RGTgbS5Uc6I95P635/g5xA1pOC8RVs4efBvAR30J54iDG3l5JEYiw== +@tsrs/linux-x64-gnu@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0 + Lockfile path: node_modules/@tsrs/linux-x64-gnu + Package: https://www.npmjs.com/package/@tsrs/linux-x64-gnu/v/0.18.0-ts7.1.0-dev.20260929 + Archive: https://registry.npmjs.org/@tsrs/linux-x64-gnu/-/linux-x64-gnu-0.18.0-ts7.1.0-dev.20260929.tgz + Integrity: sha512-EdBwBgNICGu1r1h23ixdcKwsMo6E9ll7ZsY3mNERNYqE1Pwdtcat99d5E7zB1J+oy/Xk6YqiizaB6nolIjpDTw== +@tsrs/linux-x64-musl@0.18.0-ts7.1.0-dev.20260929 | development, optional | Apache-2.0 + Lockfile path: node_modules/@tsrs/linux-x64-musl + Package: https://www.npmjs.com/package/@tsrs/linux-x64-musl/v/0.18.0-ts7.1.0-dev.20260929 + Archive: https://registry.npmjs.org/@tsrs/linux-x64-musl/-/linux-x64-musl-0.18.0-ts7.1.0-dev.20260929.tgz + Integrity: sha512-LHk+AOAtXuTlhJhOJ2+elriEIX17ycXljojeNxrrzckOipTG1LksoVWuCSas2nu00EnWAzrdKpt9oqPylUKZfQ== @turbo/darwin-64@2.11.7 | development, optional | MIT Lockfile path: node_modules/@turbo/darwin-64 Package: https://www.npmjs.com/package/@turbo/darwin-64/v/2.11.7 @@ -3400,6 +3425,11 @@ tslib@2.8.1 | development | 0BSD Package: https://www.npmjs.com/package/tslib/v/2.8.1 Archive: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz Integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tsrs@0.18.0-ts7.1.0-dev.20260929 | development | Apache-2.0 + Lockfile path: node_modules/tsrs + Package: https://www.npmjs.com/package/tsrs/v/0.18.0-ts7.1.0-dev.20260929 + Archive: https://registry.npmjs.org/tsrs/-/tsrs-0.18.0-ts7.1.0-dev.20260929.tgz + Integrity: sha512-9T0b4Uam/yMFIK16GUx0fUrJqYZnJ/gvSmV9JB8NTyDePvwCI45WFWxyEV/UMV3KfdNRJG/PBsm3fSIB9WmWsA== tsx@4.23.15 | development | MIT Lockfile path: node_modules/tsx Package: https://www.npmjs.com/package/tsx/v/4.23.15 diff --git a/package-lock.json b/package-lock.json index a8d20bd23d..392b35cdbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "oxfmt": "0.72.0", "oxlint": "1.87.0", "rolldown": "1.2.12", + "tsrs": "0.18.0-ts7.1.0-dev.20260929", "tsx": "4.23.15", "turbo": "2.11.7", "typescript": "7.0.2", @@ -4407,6 +4408,103 @@ "integrity": "sha512-sWqEMeAJGMda9qRrfPKrX3C4c33CO9SJuvJzzrcBEn5sbDB+k6pSLkDsfN0rVnLQwR97MV3sDgVbcxQNsq8xVw==", "license": "MIT" }, + "node_modules/@tsrs/darwin-arm64": { + "version": "0.18.0-ts7.1.0-dev.20260929", + "resolved": "https://registry.npmjs.org/@tsrs/darwin-arm64/-/darwin-arm64-0.18.0-ts7.1.0-dev.20260929.tgz", + "integrity": "sha512-sAirMpaGA9uRQm2W+t4zC7PjnijNuFa4PCSIfLKKo0JyAUCRfnMeev0UGBQ9F3g+9Nz187tvikUs5Irh5+eajA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@tsrs/linux-arm64-gnu": { + "version": "0.18.0-ts7.1.0-dev.20260929", + "resolved": "https://registry.npmjs.org/@tsrs/linux-arm64-gnu/-/linux-arm64-gnu-0.18.0-ts7.1.0-dev.20260929.tgz", + "integrity": "sha512-zm9/z9yylig68JiZF4Aq0Cm26n09KEU8YoQRlG7tdDWTBNAIViYw8knxnWYsjMWVZ2XiU4mYq1xdrOKuOgABHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@tsrs/linux-arm64-musl": { + "version": "0.18.0-ts7.1.0-dev.20260929", + "resolved": "https://registry.npmjs.org/@tsrs/linux-arm64-musl/-/linux-arm64-musl-0.18.0-ts7.1.0-dev.20260929.tgz", + "integrity": "sha512-Lw7VVU/YQ9JcarODICEiwzMLdFqZprqh5RGTgbS5Uc6I95P635/g5xA1pOC8RVs4efBvAR30J54iDG3l5JEYiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@tsrs/linux-x64-gnu": { + "version": "0.18.0-ts7.1.0-dev.20260929", + "resolved": "https://registry.npmjs.org/@tsrs/linux-x64-gnu/-/linux-x64-gnu-0.18.0-ts7.1.0-dev.20260929.tgz", + "integrity": "sha512-EdBwBgNICGu1r1h23ixdcKwsMo6E9ll7ZsY3mNERNYqE1Pwdtcat99d5E7zB1J+oy/Xk6YqiizaB6nolIjpDTw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@tsrs/linux-x64-musl": { + "version": "0.18.0-ts7.1.0-dev.20260929", + "resolved": "https://registry.npmjs.org/@tsrs/linux-x64-musl/-/linux-x64-musl-0.18.0-ts7.1.0-dev.20260929.tgz", + "integrity": "sha512-LHk+AOAtXuTlhJhOJ2+elriEIX17ycXljojeNxrrzckOipTG1LksoVWuCSas2nu00EnWAzrdKpt9oqPylUKZfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@turbo/darwin-64": { "version": "2.11.7", "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.11.7.tgz", @@ -9832,6 +9930,26 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsrs": { + "version": "0.18.0-ts7.1.0-dev.20260929", + "resolved": "https://registry.npmjs.org/tsrs/-/tsrs-0.18.0-ts7.1.0-dev.20260929.tgz", + "integrity": "sha512-9T0b4Uam/yMFIK16GUx0fUrJqYZnJ/gvSmV9JB8NTyDePvwCI45WFWxyEV/UMV3KfdNRJG/PBsm3fSIB9WmWsA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsrs": "bin/tsrs" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@tsrs/darwin-arm64": "0.18.0-ts7.1.0-dev.20260929", + "@tsrs/linux-arm64-gnu": "0.18.0-ts7.1.0-dev.20260929", + "@tsrs/linux-arm64-musl": "0.18.0-ts7.1.0-dev.20260929", + "@tsrs/linux-x64-gnu": "0.18.0-ts7.1.0-dev.20260929", + "@tsrs/linux-x64-musl": "0.18.0-ts7.1.0-dev.20260929" + } + }, "node_modules/tsx": { "version": "4.23.15", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", diff --git a/package.json b/package.json index 8aab2719b9..4b34fdb6e8 100644 --- a/package.json +++ b/package.json @@ -104,31 +104,31 @@ "metadata:generate": "node scripts/run-exclusive.mjs artifacts node scripts/generate-package-metadata.mjs", "metadata:check": "node scripts/generate-package-metadata.mjs --check", "mcp-registry:check": "node scripts/verify-mcp-registry-metadata.mjs", - "mcp-catalog:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:mcp-catalog", - "mcp-catalog:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run mcp-catalog:check:built", + "mcp-catalog:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:mcp-catalog", + "mcp-catalog:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run mcp-catalog:check:built", "prebuild": "npm run deps:check", - "build": "node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills --force", + "build": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills --force", "precheck": "npm run deps:check", - "check": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards", + "check": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check && npm run verify:architecture-guards", "precheck:fast": "npm run deps:check", - "check:fast": "turbo run typecheck lint --output-logs=errors-only", + "check:fast": "node scripts/run-turbo.mjs run typecheck lint --output-logs=errors-only", "precheck:changed": "npm run deps:check", "check:changed": "npm run check:fast && npm run test:changed", "precheck:test": "npm run deps:check", - "check:test": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint:built format:check knip metadata:check test:run", + "check:test": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run", "precheck:pr": "npm run deps:check", - "check:pr": "node scripts/run-exclusive.mjs artifacts turbo run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos", + "check:pr": "node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check test:run && npm run test:python && npm run docs:check && npm run jscpd && npm run scan:todos", "precheck:ci": "npm run deps:check", "precheck:ci:fast": "npm run deps:check", "precheck:ci:deep": "npm run deps:check", "check:ci": "npm run check:ci:fast && npm run check:ci:deep", "check:ci:fast": "node scripts/run-exclusive.mjs artifacts npm run check:ci:fast:tasks", - "check:ci:fast:tasks": "turbo run typecheck lint:built format:check", + "check:ci:fast:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check", "check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python", - "check:ci:deep:tasks": "turbo run knip metadata:check jscpd scan:todos", - "check:ci:tasks": "turbo run typecheck lint:built format:check knip metadata:check jscpd scan:todos", + "check:ci:deep:tasks": "node scripts/run-turbo.mjs run knip metadata:check jscpd scan:todos", + "check:ci:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check jscpd scan:todos", "ci:static-context": "node scripts/ci/static-task-context.mjs", - "build:cached": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills", + "build:cached": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills", "build:termux": "node scripts/run-exclusive.mjs artifacts npm run build:termux:run", "build:termux:run": "npm run compile:termux && npm run artifacts:skills", "format": "oxfmt .", @@ -151,7 +151,7 @@ "test:built": "node scripts/ci/test-group.mjs built", "pretest:boundary": "npm run test:prepare", "test:boundary": "vitest run --project boundary --project cli-boundary --project process-boundary --project mcp-boundary --project process-global", - "pretest:mcp": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:skills artifacts:mcp-catalog", + "pretest:mcp": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills artifacts:mcp-catalog", "test:mcp": "vitest run --project mcp-boundary", "pretest:acceptance": "npm run build:cached", "test:acceptance": "vitest run --project acceptance", @@ -174,7 +174,7 @@ "test:watch": "vitest --project domain --project services --project adapters --project composition --project conformance --project evaluation --changed", "pretest:watch:all": "npm run test:prepare", "test:watch:all": "vitest --changed", - "typecheck": "tsc --noEmit", + "typecheck": "node scripts/run-typescript.mjs --noEmit", "verify:test-temp-hygiene": "npm run test:prepare && node scripts/verify-test-temp-hygiene.mjs", "verify:hopper": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper.mjs", "verify:hopper:deadlines": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper-deadlines.mjs", @@ -213,7 +213,7 @@ "verify:ghidra:windows": "npm run build:cached && npm run build:ghidra-windows-fixture && node scripts/verify-real-ghidra-windows.mjs", "verify:browser": "npm run build:cached && node scripts/verify-real-browser.mjs", "verify:frida": "npm run build:cached && node scripts/verify-frida.mjs", - "verify:frida:bindings": "tsc --noEmit -p tsconfig.frida-verification.json", + "verify:frida:bindings": "node scripts/run-typescript.mjs --noEmit -p tsconfig.frida-verification.json", "verify:browser:dom": "node scripts/verify-browser-dom-destinations.mjs", "verify:browser:network": "npm run build:cached && node scripts/verify-browser-network.mjs", "verify:electron": "npm run build:cached && node scripts/verify-real-electron.mjs", @@ -256,17 +256,17 @@ "probe:windows-capabilities": "npm run build:cached && node scripts/windows-capabilities.mjs", "verify:published": "node scripts/verify-published-package.mjs", "config:print": "node scripts/print-mcp-config.mjs", - "evidence:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:managed-evidence", - "evidence:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run evidence:check:built", + "evidence:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:managed-evidence", + "evidence:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run evidence:check:built", "predocs:dev": "npm run docs:generate", "docs:dev": "vitepress dev docs", "predocs:build": "npm run docs:generate", "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs", "docs:generate": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts npm run docs:prepare", - "docs:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run docs:check:built && npm run test:local -- tests/conformance/readmeConsistency.test.ts", + "docs:check": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run docs:check:built && npm run test:local -- tests/conformance/readmeConsistency.test.ts", "docs:check:built": "node scripts/generate-package-metadata.mjs --check && node scripts/generate-skill-metadata.mjs --check && node scripts/generate-product-catalog.mjs --check && node scripts/generate-completion-ledger.mjs --check && node scripts/check-doc-facts.mjs && node scripts/generate-error-schema.mjs --check && npm run licenses:check", - "docs:error-schema": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:error-schema", + "docs:error-schema": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:error-schema", "prepack": "node scripts/prepack.mjs", "prepare": "node scripts/prepare.mjs", "verify:asset-catalog": "npm run build:cached && node scripts/verify/apple/asset-catalog.mjs", @@ -305,16 +305,16 @@ "verify:solidity:metadata": "npm run build:cached && node scripts/verify-solidity-metadata.mjs", "verify:recorded:crash": "npm run build:cached && node scripts/verify-recorded-crash.mjs", "mcp-catalog:check:built": "node scripts/generate-mcp-tool-catalog.mjs --check", - "compile": "node scripts/clean-build-output.mjs && tsc -p tsconfig.build.json && node scripts/record-catalog-digests.mjs && node scripts/bundle-runtime.mjs", + "compile": "node scripts/clean-build-output.mjs && node scripts/run-typescript.mjs -p tsconfig.build.json && node scripts/record-catalog-digests.mjs && node scripts/bundle-runtime.mjs", "compile:termux": "node scripts/clean-build-output.mjs && node --max-old-space-size=3072 node_modules/typescript-termux/lib/tsc.js -p tsconfig.build.json && node scripts/record-catalog-digests.mjs && node scripts/bundle-runtime.mjs", "artifacts:mcp-catalog": "node scripts/generate-mcp-tool-catalog.mjs", "artifacts:skills": "node scripts/generate-skill-metadata.mjs", "artifacts:product-catalog": "node scripts/generate-product-catalog.mjs", "artifacts:managed-evidence": "node scripts/generate-completion-ledger.mjs", "artifacts:error-schema": "node scripts/generate-error-schema.mjs", - "test:prepare": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts turbo run artifacts:mcp-catalog artifacts:product-catalog artifacts:managed-evidence", + "test:prepare": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:mcp-catalog artifacts:product-catalog artifacts:managed-evidence", "evidence:check:built": "node scripts/generate-completion-ledger.mjs --check", - "docs:prepare": "node scripts/generate-package-metadata.mjs && turbo run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema", + "docs:prepare": "node scripts/generate-package-metadata.mjs && node scripts/run-turbo.mjs run artifacts:product-catalog artifacts:managed-evidence artifacts:error-schema", "verify:web:qlog": "npm run build:cached && node scripts/verify-qlog-captures.mjs", "licenses:generate": "node scripts/generate-third-party-inventory.mjs", "licenses:check": "node scripts/generate-third-party-inventory.mjs --check" @@ -396,6 +396,7 @@ "oxfmt": "0.72.0", "oxlint": "1.87.0", "rolldown": "1.2.12", + "tsrs": "0.18.0-ts7.1.0-dev.20260929", "tsx": "4.23.15", "turbo": "2.11.7", "typescript": "7.0.2", diff --git a/scripts/ci/plan.mjs b/scripts/ci/plan.mjs index e7c3de8ddb..fb9260bde4 100644 --- a/scripts/ci/plan.mjs +++ b/scripts/ci/plan.mjs @@ -63,6 +63,31 @@ function classifyPackage(base, head) { const after = JSON.parse(git("show", `${head}:package.json`)); const { scripts: beforeScripts, ...beforeRest } = before; const { scripts: afterScripts, ...afterRest } = after; + const compilerDependencies = (metadata) => ({ + dependencies: { + typescript: metadata.dependencies?.typescript, + tsrs: metadata.dependencies?.tsrs, + }, + devDependencies: { + typescript: metadata.devDependencies?.typescript, + tsrs: metadata.devDependencies?.tsrs, + }, + }); + const compilerScripts = [ + "compile", + "typecheck", + "build", + "build:cached", + "test:prepare", + "docs:prepare", + "verify:frida:bindings", + ].flatMap((name) => [name, `pre${name}`, `post${name}`]); + selected.package ||= + JSON.stringify(compilerDependencies(before)) !== + JSON.stringify(compilerDependencies(after)) || + compilerScripts.some( + (name) => beforeScripts?.[name] !== afterScripts?.[name], + ); if (JSON.stringify(beforeRest) !== JSON.stringify(afterRest)) { full("Package metadata or dependencies changed"); return; diff --git a/scripts/ci/scopes.mjs b/scripts/ci/scopes.mjs index 32a15faaa3..54ebaa7d17 100644 --- a/scripts/ci/scopes.mjs +++ b/scripts/ci/scopes.mjs @@ -92,6 +92,17 @@ export const SCOPES = { ], // Four-host installed-package matrix and the Windows curated lane. package: [ + // Both host compilers must be exercised when their installation, options, + // or entrypoints change; Linux's accelerated checker cannot stand in for Go. + "scripts/run-typescript.*", + "scripts/run-turbo.mjs", + "turbo.json", + "tsconfig.json", + "tsconfig.build.json", + "tsconfig.frida-verification.json", + "package-lock.json", + ".nvmrc", + ".npmrc", ".github/actions/publish-mcp/**", ".github/actions/prepare-release-package/**", ".github/workflows/release-recovery.yml", @@ -135,6 +146,7 @@ export const SCOPES = { "src/application/DshCordis*.ts", "src/cli/interactiveSetup.ts", "src/cli/setupCommands.ts", + "tests/boundary/filesystem/typescriptCompiler.test.ts", "tests/boundary/filesystem/binaryTarget.test.ts", "tests/boundary/filesystem/safeOutputTree.test.ts", "tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts", diff --git a/scripts/run-development-tests.mjs b/scripts/run-development-tests.mjs index d53b59c847..8237f8ad58 100644 --- a/scripts/run-development-tests.mjs +++ b/scripts/run-development-tests.mjs @@ -59,7 +59,7 @@ try { "scripts/run-exclusive.mjs", "artifacts", process.execPath, - resolve("node_modules/turbo/bin/turbo"), + resolve("scripts/run-turbo.mjs"), "run", ...(plan.needsBuild ? ["artifacts:skills"] : []), ...plan.artifactTasks, diff --git a/scripts/run-turbo.mjs b/scripts/run-turbo.mjs new file mode 100644 index 0000000000..0c8404bcf1 --- /dev/null +++ b/scripts/run-turbo.mjs @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +import { createRequire } from "node:module"; +import { pathToFileURL } from "node:url"; + +import { resolveTypeScriptCompiler } from "./run-typescript.mjs"; + +// Compute the actual host compiler before Turbo hashes or restores its tasks. +process.env.REA_TYPESCRIPT_COMPILER = resolveTypeScriptCompiler().identity; +const require = createRequire(import.meta.url); +await import(pathToFileURL(require.resolve("turbo/bin/turbo")).href); diff --git a/scripts/run-typescript.d.mts b/scripts/run-typescript.d.mts new file mode 100644 index 0000000000..f5bafbab36 --- /dev/null +++ b/scripts/run-typescript.d.mts @@ -0,0 +1,11 @@ +export function selectTypeScriptCompiler( + platform: string, + arch: string, + optionalDependencies: Readonly>, +): "tsrs" | "typescript"; + +export function resolveTypeScriptCompiler(): { + readonly name: "tsrs" | "typescript"; + readonly identity: string; + readonly launcher: string; +}; diff --git a/scripts/run-typescript.mjs b/scripts/run-typescript.mjs new file mode 100644 index 0000000000..1fc0c2351a --- /dev/null +++ b/scripts/run-typescript.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node + +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { availableParallelism } from "node:os"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +/** Use the pinned package's platform inventory; let its launcher select libc. */ +export const selectTypeScriptCompiler = ( + platform, + arch, + optionalDependencies, +) => { + if (platform === "win32") return "typescript"; + const prefix = `@tsrs/${platform}-${arch}`; + return Object.keys(optionalDependencies).some( + (name) => name === prefix || name.startsWith(`${prefix}-`), + ) + ? "tsrs" + : "typescript"; +}; + +export const resolveTypeScriptCompiler = () => { + const require = createRequire(import.meta.url); + const rustPackagePath = require.resolve("tsrs/package.json"); + const rustPackage = JSON.parse(readFileSync(rustPackagePath, "utf8")); + const compiler = selectTypeScriptCompiler( + process.platform, + process.arch, + rustPackage.optionalDependencies, + ); + const packagePath = + compiler === "tsrs" + ? rustPackagePath + : require.resolve("typescript/package.json"); + const metadata = + compiler === "tsrs" + ? rustPackage + : JSON.parse(readFileSync(packagePath, "utf8")); + const command = compiler === "tsrs" ? "tsrs" : "tsc"; + return { + name: compiler, + identity: `${compiler}@${metadata.version}`, + launcher: resolve(dirname(packagePath), metadata.bin[command]), + }; +}; + +const main = async () => { + const compiler = resolveTypeScriptCompiler(); + if ( + process.env.TURBO_HASH !== undefined && + process.env.REA_TYPESCRIPT_COMPILER !== compiler.identity + ) + throw new Error( + "Compiler identity is missing or stale. Run Turbo through scripts/run-turbo.mjs.", + ); + if (compiler.name === "tsrs") { + const args = process.argv.slice(2); + // Rust consumes --maxMemory once; duplicating it becomes an unknown option. + // Preserve explicit resource choices rather than injecting another value. + const defaults = [ + ["--checkers", String(Math.min(2, availableParallelism()))], + ["--maxMemory", "3G"], + ].flatMap(([option, value]) => + args.includes(option) ? [] : [option, value], + ); + process.argv.splice(2, 0, ...defaults); + } + // Delegate execution, diagnostics, status and signals to the package's launcher. + // An unavailable binary on a supported host must fail rather than hide drift. + await import(pathToFileURL(compiler.launcher).href); +}; + +if (process.argv[1] === fileURLToPath(import.meta.url)) await main(); diff --git a/scripts/verify-real-browser.mjs b/scripts/verify-real-browser.mjs index 4ba8d1e964..ed5d8f8680 100644 --- a/scripts/verify-real-browser.mjs +++ b/scripts/verify-real-browser.mjs @@ -555,6 +555,12 @@ async function verifyPageScopedTransport(provider, proxy, origin) { throw new Error( "Page-scoped CDP disconnect must report transport loss without claiming a target crash", ); + const remaining = await provider.listTargets( + listBrowserTargetsInputSchema.parse({ cdp_endpoint: proxy.endpoint }), + ); + if (!remaining.ok) throw remaining.error; + if (!remaining.value.targets.some(({ target_id }) => target_id === target)) + throw new Error("Page-scoped CDP disconnect removed its live target"); } async function browserExecutable() { diff --git a/src/application/binary/AnalysisProviderRegistry.test.ts b/src/application/binary/AnalysisProviderRegistry.test.ts index bd258f3702..a09112ff2f 100644 --- a/src/application/binary/AnalysisProviderRegistry.test.ts +++ b/src/application/binary/AnalysisProviderRegistry.test.ts @@ -5,6 +5,7 @@ import { createAnalysisExecution, type AnalysisClient, type AnalysisProviderCandidate, + type AnalysisProviderInspectionOptions, type CapabilityDescriptor, type ProviderAvailability, type ProviderIdentity, @@ -825,7 +826,9 @@ it("joins cancelled readiness finalization and shutdown before admitting another const registry = new AnalysisProviderRegistry([ { ...base, - inspectAvailability: async ({ signal } = {}) => { + inspectAvailability: async ({ + signal, + }: AnalysisProviderInspectionOptions = {}) => { calls++; if (calls === 1) { observed = signal; @@ -982,7 +985,9 @@ it("registers selection before async readiness can race shutdown", async () => { const registry = new AnalysisProviderRegistry([ { ...base, - inspectAvailability: async ({ signal } = {}) => { + inspectAvailability: async ({ + signal, + }: AnalysisProviderInspectionOptions = {}) => { calls++; observed = signal; return base.inspectAvailability(); diff --git a/tests/acceptance/applications/peResources.test.ts b/tests/acceptance/applications/peResources.test.ts index 202a19e9d4..8b606bc011 100644 --- a/tests/acceptance/applications/peResources.test.ts +++ b/tests/acceptance/applications/peResources.test.ts @@ -9,7 +9,11 @@ import { peResourcesSchema } from "../../../src/domain/native/peResources.js"; import { peResourceFixture } from "../../../src/native/pe/PeResources.fixture.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; -import { cliTest, type TestCli } from "../../support/cli/cliFixture.js"; +import { + cliTest, + runFileCli, + type TestCli, +} from "../../support/cli/cliFixture.js"; type PeResourceCall = ( arguments_: Record, @@ -22,7 +26,7 @@ const expectPeInputFailureAcrossAdapters = async (input: { readonly reason: "invalid_format" | "invalid_value"; }) => { const { cli, call, path, reason } = input; - const result = await cli.run({ + const result = await runFileCli(cli, { arguments: ["inspect-pe-resources", path, "--json"], }); expect(result.exitCode).toBe(1); @@ -105,7 +109,7 @@ const expectPeWordAlignedRecordsAcrossAdapters = async (input: { ); const path = join(root, `word-aligned-${plus}-${index}.exe`); await writeFile(path, fixture.bytes); - const result = await cli.run({ + const result = await runFileCli(cli, { arguments: ["inspect-pe-resources", path, "--json"], }); expect(result.exitCode, result.stderr).toBe(0); @@ -196,7 +200,7 @@ const expectPeMappingOwnershipAcrossAdapters = async ( }); continue; } - const result = await cli.run({ + const result = await runFileCli(cli, { arguments: ["inspect-pe-resources", path, "--json"], }); expect(result.exitCode, name).toBe(0); @@ -249,9 +253,14 @@ const expectPeInventoriesAcrossAdapters = async ( `resource${plus ? "64" : "32"}${empty ? "-empty" : ""}.exe`, ); await writeFile(path, fixture.bytes); - const result = await cli.run({ + const invocation = { arguments: ["inspect-pe-resources", path, "--json"], - }); + }; + // Keep an independent public-wrapper process for both PE32 and PE32+. + // The empty-payload variants reuse only compiled-command startup. + const result = await (empty + ? runFileCli(cli, invocation) + : cli.run(invocation)); expect(result.exitCode).toBe(0); const evidence = contract.outputSchema.parse(result.json); const report = peResourcesSchema.parse(evidence.normalized_result); diff --git a/tests/boundary/filesystem/ciChangeScope.test.ts b/tests/boundary/filesystem/ciChangeScope.test.ts index d68af5ab0c..44b8044502 100644 --- a/tests/boundary/filesystem/ciChangeScope.test.ts +++ b/tests/boundary/filesystem/ciChangeScope.test.ts @@ -660,7 +660,7 @@ it.each([ { code: true, runtime: true, - package: false, + package: true, evm: false, browser: false, }, @@ -1155,7 +1155,7 @@ it.each([ { code: true, runtime: true, - package: false, + package: true, browser: false, evm: false, sqlite: false, @@ -1166,7 +1166,7 @@ it.each([ { code: true, runtime: true, - package: false, + package: true, browser: false, evm: false, }, @@ -1496,3 +1496,89 @@ it("never runs a manual-only verifier from an automatic workflow", async () => { ), ).toEqual([]); }); + +it.each([ + "scripts/run-typescript.mjs", + "scripts/run-typescript.d.mts", + "scripts/run-turbo.mjs", + "turbo.json", + "tsconfig.json", + "tsconfig.build.json", + "tsconfig.frida-verification.json", + "package-lock.json", + ".nvmrc", + ".npmrc", +])("selects package hosts for compiler input %s", async (path) => { + const { directory, base } = await fixture(); + const head = await change(directory, [path]); + const plan = await classify(directory, base, head); + expect(plan).toMatchObject({ + tier: "fast", + code: true, + runtime: true, + package: true, + }); + expect(plan.matrix).toEqual({ + include: [ + { os: "ubuntu-latest", platform: "linux", architecture: "x64" }, + { os: "ubuntu-24.04-arm", platform: "linux", architecture: "arm64" }, + { os: "macos-15", platform: "darwin", architecture: "arm64" }, + ], + }); +}); + +it.each(["typescript", "tsrs"])( + "selects fallback host checks when compiler dependency %s changes", + async (name) => { + const { directory } = await fixture(); + const before = { name: "fixture", devDependencies: { [name]: "1.0.0" } }; + const base = await change( + directory, + ["package.json"], + JSON.stringify(before), + ); + const head = await change( + directory, + ["package.json"], + JSON.stringify({ ...before, devDependencies: { [name]: "2.0.0" } }), + ); + expect(await classify(directory, base, head)).toMatchObject({ + code: true, + runtime: true, + package: true, + }); + }, +); + +it.each([ + "compile", + "typecheck", + "build", + "build:cached", + "test:prepare", + "docs:prepare", + "verify:frida:bindings", + "prebuild:cached", + "postcompile", + "pretypecheck", +])( + "selects fallback host checks when compiler command %s changes", + async (name) => { + const { directory } = await fixture(); + const base = await change( + directory, + ["package.json"], + JSON.stringify({ scripts: { [name]: "node scripts/old.mjs" } }), + ); + const head = await change( + directory, + ["package.json"], + JSON.stringify({ scripts: { [name]: "node scripts/new.mjs" } }), + ); + expect(await classify(directory, base, head)).toMatchObject({ + code: true, + runtime: true, + package: true, + }); + }, +); diff --git a/tests/boundary/filesystem/ciTestReports.test.ts b/tests/boundary/filesystem/ciTestReports.test.ts index 582b8b77fb..815f736a50 100644 --- a/tests/boundary/filesystem/ciTestReports.test.ts +++ b/tests/boundary/filesystem/ciTestReports.test.ts @@ -364,7 +364,7 @@ it("runs the existing onboarding and Python producer guards from their selected "node scripts/run-exclusive.mjs artifacts npm run check:ci:fast:tasks", ); expect(scripts["check:ci:fast:tasks"]).toBe( - "turbo run typecheck lint:built format:check", + "node scripts/run-turbo.mjs run typecheck lint:built format:check", ); expect(scripts["test:python"]).toContain( "bridge/native/test_rea_lldb_tracer.py", diff --git a/tests/boundary/filesystem/dependencyInstall.test.ts b/tests/boundary/filesystem/dependencyInstall.test.ts index 1eef778637..34609de849 100644 --- a/tests/boundary/filesystem/dependencyInstall.test.ts +++ b/tests/boundary/filesystem/dependencyInstall.test.ts @@ -196,6 +196,8 @@ const createBuildCacheFixture = async (): Promise => { "tsconfig.json", "tsconfig.build.json", "scripts/check-dependency-install.mjs", + "scripts/run-turbo.mjs", + "scripts/run-typescript.mjs", "scripts/run-exclusive.mjs", "scripts/run-exclusive-command.mjs", "scripts/lib/windows-owned-command.mjs", @@ -277,7 +279,7 @@ const runTurbo = (cwd: string, environment: NodeJS.ProcessEnv = process.env) => run( process.execPath, [ - join(cwd, "node_modules", "turbo", "bin", "turbo"), + join(cwd, "scripts", "run-turbo.mjs"), "run", "artifacts:skills", "--output-logs=full", diff --git a/tests/boundary/filesystem/staticTaskCache.test.ts b/tests/boundary/filesystem/staticTaskCache.test.ts index fed6cf8e9c..823dc0a7cd 100644 --- a/tests/boundary/filesystem/staticTaskCache.test.ts +++ b/tests/boundary/filesystem/staticTaskCache.test.ts @@ -1,6 +1,7 @@ import { spawnSync } from "node:child_process"; +import { createRequire } from "node:module"; import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; -import { join } from "node:path"; +import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { expect, it } from "vitest"; @@ -8,9 +9,10 @@ import { z } from "zod"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { workflowGit } from "../../support/workflowGit.js"; +import { resolveTypeScriptCompiler } from "../../../scripts/run-typescript.mjs"; const repository = fileURLToPath(new URL("../../../", import.meta.url)); -const turbo = join(repository, "node_modules/turbo/bin/turbo"); +const turbo = join(repository, "scripts/run-turbo.mjs"); const reportScript = join(repository, "scripts/ci/static-check-report.mjs"); const rootConfigSchema = z.object({ globalDependencies: z.array(z.string()).optional(), @@ -349,6 +351,13 @@ it.each([ diagnostic: "TS2322", invalidatesCompile: false, }, + { + label: "compiler launcher", + path: "scripts/run-typescript.mjs", + content: 'throw new Error("compiler launcher changed");\n', + diagnostic: "compiler launcher changed", + invalidatesCompile: true, + }, { label: "source declaration", path: "src/types.d.mts", @@ -410,11 +419,15 @@ it.each([ ["compile", "typecheck"], { // Exercise the real compiler phase with the production task inputs. - compile: "tsc -p tsconfig.build.json", - typecheck: "tsc --noEmit", + compile: "node scripts/run-typescript.mjs -p tsconfig.build.json", + typecheck: "node scripts/run-typescript.mjs --noEmit", }, { "#compiler-scope": "./scripts/root-types.d.mts" }, ); + await writeFile( + join(root, "scripts/run-typescript.mjs"), + await readFile(join(repository, "scripts/run-typescript.mjs")), + ); for (const name of [ "tsconfig.json", "tsconfig.build.json", @@ -612,3 +625,125 @@ it("invalidates checker task hashes for Node, ignore, and VitePress config chang `${task} must ignore vitest.config.ts`, ).toBe(initial.get(task)); }); + +it.skipIf(resolveTypeScriptCompiler().name !== "tsrs")( + "does not replay an official compiler success for a Rust compiler error", + async () => { + const root = await createTestTempDirectory("rea-cross-compiler-cache-"); + const cache = await createTestTempDirectory("rea-cross-compiler-store-"); + const require = createRequire(import.meta.url); + await mkdir(join(root, "scripts")); + await mkdir(join(root, "src")); + await mkdir(join(root, "node_modules/tsrs"), { recursive: true }); + for (const name of ["typescript", "turbo"]) + await symlink( + dirname(require.resolve(`${name}/package.json`)), + join(root, "node_modules", name), + "junction", + ); + for (const name of ["run-typescript.mjs", "run-turbo.mjs"]) + await writeFile( + join(root, "scripts", name), + await readFile(join(repository, "scripts", name)), + ); + await writeTaskFixture(root, ["compile", "typecheck"], { + compile: "node scripts/run-typescript.mjs -p tsconfig.build.json", + typecheck: "node scripts/run-typescript.mjs --noEmit", + }); + const rustPath = require.resolve("tsrs/package.json"); + const rust = z + .object({ version: z.string(), bin: z.object({ tsrs: z.string() }) }) + .parse(JSON.parse(await readFile(rustPath, "utf8"))); + const writeInventory = (supported: boolean) => + writeFile( + join(root, "node_modules/tsrs/package.json"), + JSON.stringify({ + name: "tsrs", + version: rust.version, + bin: { tsrs: resolve(dirname(rustPath), rust.bin.tsrs) }, + optionalDependencies: supported + ? { [`@tsrs/${process.platform}-${process.arch}`]: rust.version } + : {}, + }), + ); + await writeInventory(false); + await writeFile( + join(root, "tsconfig.json"), + JSON.stringify({ + compilerOptions: { + strict: true, + rootDir: "src", + types: [], + lib: ["ES2023", "DOM"], + target: "ES2023", + module: "NodeNext", + outDir: "dist", + }, + include: ["src/**/*.ts"], + }), + ); + await writeFile( + join(root, "tsconfig.build.json"), + '{"extends":"./tsconfig.json"}\n', + ); + await writeFile( + join(root, ".gitignore"), + "node_modules/\ndist/\n.cache/\n.turbo/\n", + ); + // TS 7.0 accepts this contextual callback; TS 7.1 reports TS7031. + // The source stays identical while only the selected compiler changes. + await writeFile( + join(root, "src/repro.ts"), + [ + "interface Options { signal?: AbortSignal }", + "interface Provider { inspect(options?: Options): Promise }", + "declare const base: Provider;", + "const providers: Provider[] = [{ ...base, inspect: async ({ signal } = {}) => {", + " if (signal?.aborted) throw signal.reason;", + "} }];", + ].join("\n"), + ); + await workflowGit(root, ["init", "--initial-branch=main"]); + await workflowGit(root, ["add", "."]); + await workflowGit(root, ["commit", "-m", "test: seed compiler isolation"]); + const run = (task: string, dryRun = false) => + execute( + join(root, "scripts/run-turbo.mjs"), + [ + "run", + task, + ...(dryRun ? ["--dry=json"] : ["--output-logs=errors-only"]), + "--cache=local:rw", + "--cache-dir", + cache, + ], + root, + ); + const hash = (task: string) => { + const result = run(task, true); + expect(result.status, result.stdout + result.stderr).toBe(0); + const entry = dryRunSchema + .parse(JSON.parse(result.stdout)) + .tasks.find((value) => value.taskId === task); + if (entry === undefined) throw new Error(`Missing compiler task ${task}`); + return entry.hash; + }; + const hashes = new Map(); + for (const task of ["compile", "typecheck"]) { + const cold = run(task); + expect(cold.status, cold.stdout + cold.stderr).toBe(0); + hashes.set(task, hash(task)); + const warm = run(task); + expect(warm.status, warm.stdout + warm.stderr).toBe(0); + expect(warm.stdout).toMatch(/Cached:\s+1 cached/u); + } + await writeInventory(true); + for (const task of ["compile", "typecheck"]) { + const result = run(task); + expect(result.status, result.stdout + result.stderr).toBeGreaterThan(0); + expect(result.stdout + result.stderr).toContain("TS7031"); + expect(result.stdout).toMatch(/Cached:\s+0 cached/u); + expect(hash(task)).not.toBe(hashes.get(task)); + } + }, +); diff --git a/tests/boundary/filesystem/typescriptCompiler.test.ts b/tests/boundary/filesystem/typescriptCompiler.test.ts new file mode 100644 index 0000000000..d49f5b6089 --- /dev/null +++ b/tests/boundary/filesystem/typescriptCompiler.test.ts @@ -0,0 +1,206 @@ +import { spawnSync } from "node:child_process"; +import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { expect, it } from "vitest"; +import { z } from "zod"; + +import { + resolveTypeScriptCompiler, + selectTypeScriptCompiler, +} from "../../../scripts/run-typescript.mjs"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +const repository = fileURLToPath(new URL("../../../", import.meta.url)); +const launcher = join(repository, "scripts/run-typescript.mjs"); +const require = createRequire(import.meta.url); +const rustPackage = z + .object({ + version: z.string(), + optionalDependencies: z.record(z.string(), z.string()), + }) + .parse( + JSON.parse(await readFile(require.resolve("tsrs/package.json"), "utf8")), + ); +const officialPackage = z + .object({ version: z.string() }) + .parse( + JSON.parse( + await readFile(require.resolve("typescript/package.json"), "utf8"), + ), + ); +const execute = (script: string, args: string[]) => + spawnSync(process.execPath, [script, ...args], { + encoding: "utf8", + timeout: 15_000, + env: { ...process.env, TURBO_HASH: undefined }, + }); + +it.each([ + ["linux", "x64", "tsrs"], + ["linux", "arm64", "tsrs"], + ["darwin", "arm64", "tsrs"], + ["win32", "x64", "typescript"], + ["win32", "arm64", "typescript"], + ["darwin", "x64", "typescript"], + ["android", "arm64", "typescript"], + ["linux", "riscv64", "typescript"], +])( + "selects %s/%s compiler %s from the pinned inventory", + (platform, arch, compiler) => { + expect( + selectTypeScriptCompiler( + platform, + arch, + rustPackage.optionalDependencies, + ), + ).toBe(compiler); + }, +); + +it("always retains the official compiler on Windows", () => { + expect( + selectTypeScriptCompiler("win32", "x64", { "@tsrs/win32-x64": "future" }), + ).toBe("typescript"); +}); + +it("runs the selected installed compiler", () => { + const result = execute(launcher, ["--version"]); + expect(result.error).toBeUndefined(); + expect(result.status, result.stderr).toBe(0); + const compiler = selectTypeScriptCompiler( + process.platform, + process.arch, + rustPackage.optionalDependencies, + ); + expect(result.stdout).toContain( + compiler === "tsrs" + ? `tsrs ${rustPackage.version}` + : `Version ${officialPackage.version}`, + ); +}); + +it("forwards build arguments and preserves real type errors and exit status", async () => { + const root = await createTestTempDirectory("rea-typescript-compiler-"); + const input = join(root, "input.ts"); + await writeFile( + join(root, "tsconfig.json"), + JSON.stringify({ + compilerOptions: { + strict: true, + target: "ES2023", + types: [], + outDir: "output", + }, + files: ["input.ts"], + }), + ); + await writeFile(input, "export const value: number = 1;\n"); + const valid = execute(launcher, ["-p", root, "--pretty", "false"]); + expect(valid.error).toBeUndefined(); + expect(valid.status, valid.stdout + valid.stderr).toBe(0); + expect(await readFile(join(root, "output/input.js"), "utf8")).toContain( + "value = 1", + ); + const resourceArgs = + resolveTypeScriptCompiler().name === "tsrs" + ? ["--checkers", "1", "--maxMemory", "1G"] + : ["--checkers", "1"]; + const checked = execute(launcher, ["-p", root, "--noEmit", ...resourceArgs]); + expect(checked.error).toBeUndefined(); + expect(checked.status, checked.stdout + checked.stderr).toBe(0); + await writeFile(input, 'export const value: number = "invalid";\n'); + const invalid = execute(launcher, [ + "-p", + root, + "--noEmit", + "--pretty", + "false", + ]); + const direct = execute(resolveTypeScriptCompiler().launcher, [ + "-p", + root, + "--noEmit", + "--pretty", + "false", + ]); + expect(direct.error).toBeUndefined(); + expect(direct.status, direct.stdout + direct.stderr).toBeGreaterThan(0); + expect(direct.stdout + direct.stderr).toContain("TS2322"); + expect(invalid.error).toBeUndefined(); + expect(invalid.status, invalid.stdout + invalid.stderr).toBe(direct.status); + expect(invalid.stdout + invalid.stderr).toContain("TS2322"); + expect(await readFile(join(root, "output/input.js"), "utf8")).toContain( + "value = 1", + ); +}); + +it.skipIf(process.platform === "win32")( + "does not retry failed supported compilers with the official compiler", + async () => { + const root = await createTestTempDirectory("rea-typescript-failure-"); + const script = join(root, "run-typescript.mjs"); + await copyFile(launcher, script); + for (const name of ["tsrs", "typescript"]) { + const directory = join(root, "node_modules", name); + await mkdir(directory, { recursive: true }); + await writeFile( + join(directory, "package.json"), + JSON.stringify({ + name, + type: "module", + bin: { [name === "tsrs" ? "tsrs" : "tsc"]: "cli.mjs" }, + optionalDependencies: { + [`@tsrs/${process.platform}-${process.arch}`]: "fixture", + }, + }), + ); + await writeFile( + join(directory, "cli.mjs"), + name === "tsrs" + ? 'console.error("supported compiler failed"); process.exitCode = 42;\n' + : 'console.log("unexpected fallback");\n', + ); + } + const result = execute(script, ["--noEmit"]); + expect(result.error).toBeUndefined(); + expect(result.status, result.stdout + result.stderr).toBe(42); + expect(result.stderr).toContain("supported compiler failed"); + expect(result.stdout).not.toContain("unexpected fallback"); + // The same selection must expose a missing installed launcher as a failure. + await writeFile( + join(root, "node_modules/tsrs/package.json"), + JSON.stringify({ + name: "tsrs", + bin: { tsrs: "missing.mjs" }, + optionalDependencies: { + [`@tsrs/${process.platform}-${process.arch}`]: "fixture", + }, + }), + ); + const missing = execute(script, ["--noEmit"]); + expect(missing.status, missing.stdout + missing.stderr).toBe(1); + expect(missing.stderr).toContain("ERR_MODULE_NOT_FOUND"); + expect(missing.stdout).not.toContain("unexpected fallback"); + }, +); + +it.each([undefined, "typescript@stale"])( + "rejects missing or stale compiler identity %s inside a Turbo task", + (identity) => { + const result = spawnSync(process.execPath, [launcher, "--version"], { + encoding: "utf8", + timeout: 15_000, + env: { + ...process.env, + TURBO_HASH: "compiler-task", + REA_TYPESCRIPT_COMPILER: identity, + }, + }); + expect(result.error).toBeUndefined(); + expect(result.status, result.stdout + result.stderr).toBe(1); + expect(result.stderr).toContain("Compiler identity is missing or stale"); + }, +); diff --git a/tests/boundary/mcp/providerSelectionMcp.test.ts b/tests/boundary/mcp/providerSelectionMcp.test.ts index df484959d2..66d49eec6b 100644 --- a/tests/boundary/mcp/providerSelectionMcp.test.ts +++ b/tests/boundary/mcp/providerSelectionMcp.test.ts @@ -12,6 +12,7 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createAnalysisExecution, type AnalysisProviderCandidate, + type AnalysisProviderInspectionOptions, type CapabilityDescriptor, type ProviderIdentity, } from "../../../src/application/AnalysisProvider.js"; @@ -411,7 +412,9 @@ it.each(["ready", "cancelled", "physical"] as const)( new AnalysisProviderRegistry([ { ...base, - inspectAvailability: async ({ signal } = {}) => { + inspectAvailability: async ({ + signal, + }: AnalysisProviderInspectionOptions = {}) => { enter(); await gate; if (mode === "physical") diff --git a/tests/process-global/curatedWindowsLane.test.ts b/tests/process-global/curatedWindowsLane.test.ts index 637ca8d23a..48a1edda6b 100644 --- a/tests/process-global/curatedWindowsLane.test.ts +++ b/tests/process-global/curatedWindowsLane.test.ts @@ -136,3 +136,16 @@ it("keeps every explicit focused CI test path executable after file moves", asyn ); expect(missing.filter((path) => path !== undefined)).toEqual([]); }, 10_000); + +it("checks the complete source with the fallback compiler before Windows build reuse", async () => { + const workflow = workflowSchema.parse( + parse(await readFile(WORKFLOW, "utf8")), + ); + const steps = workflow.jobs["windows-curated"]?.steps ?? []; + const checked = steps.findIndex( + (step) => step.run === "npm run typecheck -- --checkers 2", + ); + const built = steps.findIndex((step) => step.run === "npm run build:cached"); + expect(checked).toBeGreaterThan(-1); + expect(built).toBeGreaterThan(checked); +}); diff --git a/turbo.json b/turbo.json index 37783a4afd..e3be6d9d9d 100644 --- a/turbo.json +++ b/turbo.json @@ -3,13 +3,20 @@ "cacheMaxAge": "7d", "cacheMaxSize": "500MB", "globalDependencies": [".nvmrc", ".npmrc"], - "globalPassThroughEnv": ["GOMAXPROCS", "GOMEMLIMIT", "UV_THREADPOOL_SIZE"], + "globalPassThroughEnv": [ + "GOMAXPROCS", + "GOMEMLIMIT", + "UV_THREADPOOL_SIZE", + "REA_TYPESCRIPT_COMPILER" + ], "tasks": { "compile": { "dependsOn": ["metadata:check"], "inputs": [ "package.json", "package-lock.json", + "scripts/run-turbo.mjs", + "scripts/run-typescript.mjs", "scripts/check-dependency-install.mjs", "scripts/clean-build-output.mjs", "scripts/bundle-runtime.mjs", @@ -22,12 +29,15 @@ "tsconfig.json", "tsconfig.build.json" ], - "outputs": ["dist/**"] + "outputs": ["dist/**"], + "env": ["REA_TYPESCRIPT_COMPILER"] }, "typecheck": { "inputs": [ "package.json", "package-lock.json", + "scripts/run-turbo.mjs", + "scripts/run-typescript.mjs", "src/**/*.{cts,mts,ts,tsx}", "src/**/package.json", "tests/**/*.{cts,mts,ts,tsx}", @@ -44,7 +54,8 @@ "!docs/.vitepress/cache/**", "!docs/.vitepress/dist/**" ], - "outputs": [] + "outputs": [], + "env": ["REA_TYPESCRIPT_COMPILER"] }, "metadata:check": { "inputs": [