diff --git a/packages/spec/scripts/check-generated.ts b/packages/spec/scripts/check-generated.ts index 26b47d29cb4..630f8b56aee 100644 --- a/packages/spec/scripts/check-generated.ts +++ b/packages/spec/scripts/check-generated.ts @@ -684,7 +684,10 @@ function runScript(script: string): { ok: boolean; output: string } { // the build alone; every gate keeps the caller's environment. if (script === SPEC_BUILD) delete env.OS_SKIP_DTS; try { - const output = execSync(`pnpm -s ${script}`, { cwd: pkgRoot, env, stdio: ['ignore', 'pipe', 'pipe'] }).toString(); + // `--silent`, never `-s`: a global pnpm 11+/12 `@pnpm/exe` frontend parses the + // argv before handing off to the pinned pnpm and rejects `-s` with exit 2, which + // reads here as every gate stale. + const output = execSync(`pnpm --silent ${script}`, { cwd: pkgRoot, env, stdio: ['ignore', 'pipe', 'pipe'] }).toString(); return { ok: true, output }; } catch (err: any) { return { ok: false, output: `${err?.stdout?.toString() ?? ''}${err?.stderr?.toString() ?? ''}`.trim() }; diff --git a/scripts/build-console.sh b/scripts/build-console.sh index dec6466d11d..62272092ac9 100755 --- a/scripts/build-console.sh +++ b/scripts/build-console.sh @@ -321,9 +321,18 @@ pushd "$BUILD_ROOT" > /dev/null # objectui's root package.json may pin packages that aren't available on # every mirror. Fall back to the public registry just for this install. +# +# The console build needs objectui's devDependencies, so production mode is +# forced OFF: an inherited `npm_config_production=true` or an .npmrc +# `production=true` would otherwise skip them (NODE_ENV=production alone does +# not, under pnpm 10). Spelled `--config.production=false`, never +# `--prod=false`: a global pnpm 11+/12 `@pnpm/exe` frontend parses the argv +# itself before handing off to the version objectui pins, rejects a value on its +# boolean `--prod` and exits 2 without installing anything. The pinned pnpm and +# that frontend both accept this spelling. NPM_CONFIG_REGISTRY_OVERRIDE="${OBJECTUI_NPM_REGISTRY:-https://registry.npmjs.org}" npm_config_registry="$NPM_CONFIG_REGISTRY_OVERRIDE" \ - pnpm install --frozen-lockfile --prefer-offline --prod=false + pnpm install --frozen-lockfile --prefer-offline --config.production=false # Build the console's workspace deps via turbo, then the SPA itself directly # (see DEPS_BUILD_CMD/BUILD_CMD above for why these are split). diff --git a/scripts/check-regen-pending.mjs b/scripts/check-regen-pending.mjs index ca127a866a9..dda041ea4d7 100755 --- a/scripts/check-regen-pending.mjs +++ b/scripts/check-regen-pending.mjs @@ -859,9 +859,12 @@ export function gateCouldNotRun(output, exitCode, fromDir, gate = null) { return null; } +// `--silent`, never `-s`: a global pnpm 11+/12 `@pnpm/exe` frontend parses the argv +// before handing off to the pinned pnpm and rejects `-s` with exit 2 — every artifact +// would read as stale and every commit would be refused. function runCheck(script, cwd) { try { - execSync(`pnpm -s ${script}`, { cwd, stdio: ['ignore', 'pipe', 'pipe'] }); + execSync(`pnpm --silent ${script}`, { cwd, stdio: ['ignore', 'pipe', 'pipe'] }); return { ok: true, output: '', code: 0 }; } catch (err) { return { @@ -1396,7 +1399,7 @@ function fixtureSelfTest() { /** * The pnpm the fixture's stub gate runs under, pinned to the repository's own. * - * `runCheck` spawns every gate with `pnpm -s`, and CI reaches pnpm through + * `runCheck` spawns every gate with `pnpm --silent`, and CI reaches pnpm through * Corepack (`.github/actions/setup-pnpm`). Corepack resolves the version from * the project it is invoked IN, so a fixture carrying no `packageManager` field * does not inherit this repo's pin and does not get "whatever the machine has" @@ -1405,8 +1408,10 @@ function fixtureSelfTest() { * published that morning: the day `latest` moved to a major whose CLI rejects * `-s`, the "clean" stub exited 2 without ever running, and every case that * reads the stub's own exit code went red on a tree nobody had touched. It is - * the FIXTURE that was unpinned, never production — the real hook runs `pnpm -s` - * in this repository, which is pinned. + * the FIXTURE that was unpinned, never production — the real hook runs in this + * repository, which is pinned. A pin does not reach the argv parser of a global + * pnpm 11+ `@pnpm/exe` frontend, though, which is why the spawn is spelled + * `--silent` rather than `-s`. * * Read off the ROOT manifest rather than written as a literal, so the fixture * cannot drift from the pnpm this repo pins. The case below asserts both halves, @@ -1508,7 +1513,7 @@ function fixtureSelfTest() { const merge = runHook('stale'); // The fixture's OWN prerequisite, asserted before any verdict is read out of a - // gate's exit code: every case below spawns `pnpm -s` inside this directory, so + // gate's exit code: every case below spawns `pnpm --silent` inside this directory, so // an unpinned fixture grades the registry's `latest` pnpm instead of the one // this repository pins, and the failure lands on cases that have nothing to do // with pnpm. ⛔ Not `=== rootPackageManager` on its own: were the root manifest diff --git a/scripts/git-merge-regen.mjs b/scripts/git-merge-regen.mjs index a5df11fb1a0..cac3150afdd 100755 --- a/scripts/git-merge-regen.mjs +++ b/scripts/git-merge-regen.mjs @@ -1194,7 +1194,7 @@ function reconcileOwnership() { 'a package command filters to its owner', ownerRunCommand(DEFAULT_OWNER, 'gen:x') === `pnpm --filter ${DEFAULT_OWNER} gen:x`, ], - ['a spawn asks for silence', ownerRunCommand(ROOT_OWNER, 'check:x', { silent: true }) === 'pnpm -s check:x'], + ['a spawn asks for silence', ownerRunCommand(ROOT_OWNER, 'check:x', { silent: true }) === 'pnpm --silent check:x'], ]; const failures = cases.filter(([, ok]) => !ok).map(([name]) => name); @@ -1715,7 +1715,7 @@ function endToEndWorktreeRefusal() { const target = REGEN_ARTIFACTS[0].path; const gateScript = REGEN_ARTIFACTS[0].check; - /* The stub gate, and the pin that keeps it honest. `pnpm -s` resolves its own + /* The stub gate, and the pin that keeps it honest. `pnpm --silent` resolves its own * version from the manifest of the directory it runs in, so a fixture with no * `packageManager` grades whatever Corepack resolves as `latest` instead of the * pnpm this repository pins — the same trap `check-regen-pending.mjs`'s fixture diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index c4656786dba..5dd62afcb7a 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -9316,7 +9316,7 @@ export function governedReadCensus({ files = null, read = null } = {}) { * quoted literal that `resolvePathExpression` refuses by construction, so * admitting them would add a scan that cannot return a target: dead code that * reads as coverage. Live specimens of the refused class in this tree, both - * shell-quoted: `pnpm -s ${script}` and `git rev-parse --show-toplevel`. + * shell-quoted: `pnpm --silent ${script}` and `git rev-parse --show-toplevel`. * * THE PROGRAM POSITION ONLY — argument 0, plus the elements of an argv ARRAY * LITERAL in argument 1. That is where a program path is; an options object is diff --git a/scripts/pm/dispatch-gates.self-test.mjs b/scripts/pm/dispatch-gates.self-test.mjs index a03261c64f0..61d68d432f8 100644 --- a/scripts/pm/dispatch-gates.self-test.mjs +++ b/scripts/pm/dispatch-gates.self-test.mjs @@ -8728,7 +8728,7 @@ export function selfTest({ tier = 'full' } = {}) { "const r1 = spawnSync(process.execPath, [join(ROOT, TOOL), '--self-test'], { stdio: 'inherit' });", // followed: argv array, component NAMED "const r2 = execFileSync('node', [join(ROOT, 'scripts/invoked-as.mjs')]);", // followed: component written out "const r3 = spawnSync('git', ['ls-files'], { cwd: ROOT });", // a program that is not in this tree - 'const r4 = execSync(`pnpm -s ${script}`);', // shell form: a command STRING, never scanned + 'const r4 = execSync(`pnpm --silent ${script}`);', // shell form: a command STRING, never scanned "const r5 = spawnSync(process.execPath, [join(ROOT, 'package.json')]);", // tracked, but not program text "const r6 = spawnSync(process.execPath, [join(ROOT, 'scripts/does-not-exist.mjs')]);", // resolves, untracked "const r7 = spawnSync(process.execPath, [join(ROOT, 'scripts/fixture.mjs')]);", // itself: the identity key owns it diff --git a/scripts/regen-artifacts.mjs b/scripts/regen-artifacts.mjs index 1dcf2599b90..483b1cfcbf5 100644 --- a/scripts/regen-artifacts.mjs +++ b/scripts/regen-artifacts.mjs @@ -957,11 +957,13 @@ export function ownerDir(owner, workspacePkgs) { * * @param {string} owner * @param {string} script - * @param {{ silent?: boolean }} [options] `silent` adds `-s`, for a spawn rather than advice + * @param {{ silent?: boolean }} [options] `silent` adds `--silent`, for a spawn rather than + * advice — never `-s`, which a global pnpm 11+ `@pnpm/exe` frontend rejects before the + * pinned pnpm ever runs * @returns {string} */ export function ownerRunCommand(owner, script, { silent = false } = {}) { - const s = silent ? ' -s' : ''; + const s = silent ? ' --silent' : ''; return owner === ROOT_OWNER ? `pnpm${s} ${script}` : `pnpm${s} --filter ${owner} ${script}`; }