Uh oh!
There was an error while loading. Please reload this page.
fix: resolve node bins via package.json instead of spawning npx .cmd shims so verify/smoke scripts run on Windows - #1997
Conversation
…shims so the verify/smoke scripts run on Windows On Windows npx/npm are .cmd shims a shell-free execFileSync/spawnSync cannot start (ENOENT since the CVE-2024-27980 hardening), so verify:typecheck-coverage died at validate's second step — doubly silently, echoing "(no diagnostic captured)" per project and then reporting all 918 tracked files as getting no tsc pass — and verify:build-gate, the smoke test-server bootstraps, and pack:verify's npm pack failed the same way. Add scripts/lib/resolve-node-bin.mjs: resolves the JS entry behind a package's bin from <pkg>/package.json (deep resolution is blocked by Vite 8's exports map) and spawns it via process.execPath — the same walk npx --no-install did, cross-platform and shell-free. Switch the six npx call sites to it; the npm pack call gets the existing shell-on-win32 idiom instead (npm has no in-tree package to resolve). An unresolvable tsc is now a hard "cannot measure" error with actionable stderr, pinned by a new main() regression test. Closes#1939 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ram lib Upstream #1965 moved the tsc --listFilesOnly machinery into scripts/lib/tsc-program.mjs (shared with the new verify:dep-lockstep), still spawning `npx` — which is ENOENT on Windows. Resolve the conflict by taking upstream's factoring and porting the #1939 fix into the lib: rawProjectListing and verify-typecheck-coverage's --showConfig pass now spawn process.execPath with the cached, hard-failing tscEntry resolver (exported from tsc-program.mjs). Fixture copies in both main tests pick up the new lib import graph. verify:dep-lockstep now runs on Windows too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
olaservo
commented
Aug 17, 2026
Merged |
There was a problem hiding this comment.
Pull request overview
Makes verification and smoke tooling Windows-compatible by resolving Node package binaries directly instead of spawning .cmd shims.
Changes:
- Adds a shared package-bin resolver and tests.
- Updates TypeScript, Vite, and smoke command execution.
- Enables Windows shell resolution for
npm pack.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/lib/resolve-node-bin.mjs | Adds shared binary resolution. |
scripts/lib/resolve-node-bin.test.mjs | Tests resolver behavior. |
scripts/lib/tsc-program.mjs | Runs resolved TypeScript entry. |
scripts/verify-typecheck-coverage.mjs | Uses the shared TypeScript resolver. |
scripts/verify-typecheck-coverage.main.test.mjs | Tests hard failure when TypeScript is unavailable. |
scripts/verify-dep-lockstep.main.test.mjs | Includes the resolver in fixtures. |
scripts/verify-build-gate.mjs | Runs Vite through Node. |
scripts/smoke-cli.mjs | Builds test servers cross-platform. |
scripts/smoke-tui.mjs | Builds test servers cross-platform. |
scripts/smoke-web-app.mjs | Builds app test servers cross-platform. |
scripts/pack-and-verify.mjs | Makes npm pack spawnable on Windows. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Copilot review on #1997: - A string-form `bin` is npm's shorthand for ONE command, named after the package, so ignoring `binName` let a typo silently resolve the package's only executable while bypassing the documented "declares no such bin" failure. Match it against the manifest's unscoped name instead. - A declared bin whose file is absent (partial install) was returned unchecked. `process.execPath <missing>` still spawns and exits 1 with empty stdout, which `rawProjectListing` records as "no diagnostic captured" — reproducing the bogus every-file-uncovered report this helper exists to eliminate. Throw here, where the remedy is actionable. Also restores `rawProjectListing`'s docblock, which the new `tscEntry` block had orphaned above itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01387r3MotGk52hrkfjzHFsN Signed-off-by: cliffhall <cliff@futurescale.com>
cliffhall
commented
Aug 17, 2026
Copilot review round 1 — both comments were correct and both are fixed in 4b143ce. Mirroring at PR level since inline replies get hidden once the threads go outdated. 1. String-form 2. No existence check on the resolved entry (thread) — the sharper of the two, because it relocates rather than removes the failure this PR targets: on a partial install ( Also folded in one thing found independently of the review: the new Verification — full
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (3)
scripts/lib/resolve-node-bin.mjs:25
<pkg>/package.jsonis not always exported. A valid package can defineexportswithout./package.json; in that case this throwsERR_PACKAGE_PATH_NOT_EXPORTEDeven though its declared bin is installed, contradicting the shared resolver's contract. Locate the manifest through thenode_modulessearch paths (or another export-independent package-root lookup) and add a fixture whoseexportshidespackage.json.
* spawn can't start on Windows. Resolves `<pkg>/package.json` and reads its
* `bin` field (what npx itself does) rather than resolving the bin path
* directly, because an `exports` map blocks deep resolution — Vite 8 doesn't
* export `./bin/vite.js`, so `require.resolve("vite/bin/vite.js")` throws
* `ERR_PACKAGE_PATH_NOT_EXPORTED` (`./package.json` is always exported).
scripts/pack-and-verify.mjs:142
shell: truemakes Node concatenate these arguments without escaping them. On Windows,tmpdir()commonly contains spaces (for example under a user profile), so--pack-destinationis split into multiple arguments andpack:verifystill fails; shell metacharacters in the path are also interpreted. Invoke npm's JavaScript CLI withprocess.execPathand keep arguments shell-free, applying the same mechanism to this file's other npm invocations that pass generated temp/tarball paths.
// npm is npm.cmd on Windows, which needs a shell to resolve (#1939) — the
// same idiom as runInherit/runBin below.
{ cwd: repoRoot, encoding: "utf8", shell: process.platform === "win32" },
scripts/lib/resolve-node-bin.mjs:1
- The documentation was not updated for this new shared tooling module.
AGENTS.md:124-133still describesscripts/libaroundtsc-program.mjsand only pure-parser tests, whileREADME.md:315saystest:scriptscovers only the listed parsers. Addresolve-node-bin.mjsand its resolution tests to the relevant project-structure/test descriptions so the documented tooling architecture matches the repository.
// Shared resolver for spawning a package's CLI cross-platform (#1939).
…Windows
Copilot review round 2 (suppressed comments), all three real:
- `require.resolve("<pkg>/package.json")` is governed by the package's
`exports`, and Node keeps no special case for `./package.json` — so a
package declaring an `exports` map without it threw
ERR_PACKAGE_PATH_NOT_EXPORTED even though its bin was installed and
spawnable, a false "not installed" pointing at `npm install` for a
package already on disk. Walk `resolve.paths()` instead, which never
consults `exports`. The docblock claiming the opposite is corrected.
- `shell: true` (needed at all on Windows to start the `.cmd` shims) makes
Node hand `cmd.exe` one space-joined string, so an argument holding a
path with a space splits in two. Every generated path in
pack-and-verify lives under `tmpdir()`, which sits beneath the user
profile. Quote them via `shellArgs()` at all four spawn sites, the
command path included — the hazard predates this PR, so fix the class.
- Document the new module: `scripts/lib/resolve-node-bin.mjs` in AGENTS.md's
scripts/ tree, its tests in the README's `test:scripts` row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01387r3MotGk52hrkfjzHFsN
Signed-off-by: cliffhall <cliff@futurescale.com>cliffhall
commented
Aug 17, 2026
Copilot review round 2 — the visible review said "no new comments", but the Suppressed comments (3) block held three real findings. All fixed in b399bc6. 1. 2. One scoping note: this hazard predates this PR — 3. Docs drift. Fair catch and required by this repo's own maintenance rules. Added Verification — full |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
scripts/pack-and-verify.mjs:114
- This quoting predicate misses valid Windows path characters that
cmd.exestill interprets, notably parentheses, and quoting does not suppress%VAR%expansion. A temp/profile path such asC:\Temp(1)is therefore passed unquoted and can be parsed as shell syntax; a path containing%NAME%can be rewritten even when quoted. Since these path-bearing calls are the Windows fix this PR introduces, use a completecmd.exeescaping strategy (or avoid the shell where possible) and cover spaces plus shell metacharacters in an automated test.
return args.map((arg) =>
/[\s&|<>^"]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg,
);
AGENTS.md:136
- This documentation says the pack script uses
resolve-node-bin.mjsandprocess.execPath, butpack-and-verify.mjsdoes not import the resolver and intentionally continues spawningnpx/npmthrough a Windows shell. Please distinguish the verify/smoke direct-Node approach from the pack script's shell-quoting approach so the source-of-truth architecture description matches the implementation.
│ # through — #1965; resolve-node-bin.mjs resolves a
│ # package's bin through its own package.json so the
│ # verify/smoke/pack scripts spawn it with
│ # `process.execPath` instead of the `npx`/`npm`
│ # `.cmd` shims Node refuses to spawn shell-free on
Uh oh!
There was an error while loading. Please reload this page.
Copilot review round 3, all three real: - `require.resolve.paths()` also appends Node's GLOBAL FOLDERS ($HOME/.node_modules, $HOME/.node_libraries, $PREFIX/lib/node) and any NODE_PATH entries, so round 2's lookup could satisfy a missing repo install from a GLOBALLY installed typescript/vite — measuring the programs with the wrong compiler instead of producing the actionable "run npm install" this helper promises. Filter to the node_modules directories on fromDir's own ancestor chain. - The quoting predicate missed characters cmd.exe still parses, notably parentheses (`C:\Temp(1)`), and quoting cannot suppress `%VAR%` at all — expansion happens before quotes are processed. Extracted to lib/win-shell-args.mjs with a broad predicate and a hard error on `%` rather than a silently different path. `platform` is a parameter so the Windows behavior is exercised on the POSIX machines that run the suite. - AGENTS.md claimed pack-and-verify spawns via `process.execPath`; it does not, and cannot — its children are `npm` and the installed `.bin` shim. State the exception and why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01387r3MotGk52hrkfjzHFsN Signed-off-by: cliffhall <cliff@futurescale.com>
cliffhall
commented
Aug 17, 2026
Copilot review round 3 — 1 visible comment + 2 more in the Suppressed comments block. All three were real; all three fixed in c935521. 1. 2. The quoting predicate was incomplete — correct on both counts. Parentheses group in 3. AGENTS.md overstated the fix — right, and worth correcting precisely because that file is the source of truth. It said the pack script spawns via Verification — full |
Closes#1939
npx/npmare.cmdshims on Windows and Node refuses shell-less.cmdspawns, sovalidate/cidied atverify:typecheck-coverage— silently, as a bogus "918 files get no tsc pass" report. This adds a sharedscripts/lib/resolve-node-bin.mjs(resolves a package'sbinvia itspackage.json, spawned withprocess.execPath) and patches all seven affected call sites across the verify/smoke/pack scripts; a resolution failure is now a hard "cannot resolve — run npm install" error instead of an empty file set. Verified on Windows:test:scripts94/94 (+7 new tests incl. a regression pin),verify:typecheck-coverageandverify:build-gategreen. Full root-cause in the issue comment.🤖 Generated with Claude Code