fix(scripts): keep the resolver local, complete the cmd.exe quoting

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>
This commit is contained in:
cliffhall 2026-08-17 00:18:40 -04:00
commit c93552139c

View file

@ -17,7 +17,7 @@ import {
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { resolveNodeBin } from "./resolve-node-bin.mjs";
import { localSearchDirs, resolveNodeBin } from "./resolve-node-bin.mjs";
const repoRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
@ -148,6 +148,53 @@ test("resolves a package whose `exports` hides ./package.json", () => {
}
});
test("localSearchDirs keeps only node_modules on fromDir's ancestor chain", () => {
// `require.resolve.paths()` appends Node's GLOBAL FOLDERS and any NODE_PATH
// entries. Honouring those would break the guarantee this module exists for
// — that the spawned tsc/vite is the REPO-PINNED one — by letting a globally
// installed TypeScript stand in for a missing repo install, measuring the
// programs with the wrong compiler instead of failing actionably.
const from = path.join(path.sep, "repo", "clients", "web");
const kept = [
path.join(from, "node_modules"),
path.join(path.sep, "repo", "clients", "node_modules"),
path.join(path.sep, "repo", "node_modules"),
path.join(path.sep, "node_modules"),
];
const dropped = [
path.join(path.sep, "home", "dev", ".node_modules"),
path.join(path.sep, "home", "dev", ".node_libraries"),
path.join(path.sep, "usr", "local", "lib", "node"),
// A NODE_PATH entry off the chain — a real node_modules, just not ours.
path.join(path.sep, "opt", "shared", "node_modules"),
];
assert.deepEqual(localSearchDirs([...kept, ...dropped], from), kept);
});
test("a package visible only outside the ancestor chain is not resolved", () => {
// The end-to-end form of the above: `ghostpkg` exists on disk, but under a
// sibling tree rather than an ancestor of `fromDir`.
const outside = fixtureDir(
{ name: "elsewherepkg", bin: { elsewhere: "bin/e.js" } },
{ createBinFile: true },
);
const consumer = mkdtempSync(path.join(tmpdir(), "resolve-node-bin-from-"));
try {
writeFileSync(
path.join(consumer, "package.json"),
JSON.stringify({ name: "consumer" }),
"utf8",
);
assert.throws(
() => resolveNodeBin("elsewherepkg", "elsewhere", consumer),
/cannot find elsewherepkg/,
);
} finally {
rmSync(outside, { recursive: true, force: true });
rmSync(consumer, { recursive: true, force: true });
}
});
test("accepts a scoped package's string-form bin under its unscoped name", () => {
const dir = fixtureDir(
{ name: "@scope/tool", bin: "bin/tool.js" },