Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion scripts/check-i18n-bundles.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -87,7 +87,9 @@
// prerequisite failure naming the package whose dist is stale — never a count.
import { spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import {
atRepoRoot,
CLI,
CLI_BUILD_FIX,
looksLikeMissingCliCommand,
Expand DownExpand Up@@ -380,6 +382,55 @@ function selfTest() {
const undeclaredTarget = oclifCommandFileFor({ oclif: {} }, ['i18n', 'extract']);
expect('#5217 unreadable shape defers, loudly', !!undeclaredTarget.unknown && !undeclaredTarget.file, `an unreadable oclif block must yield a reason, not a guessed path; got ${JSON.stringify(undeclaredTarget)}`);

// …and the probe that puts the REAL package.json through it is anchored to the
// module's own location, not the cwd (#11394). Before that fix this gate could
// not pre-check from anywhere but the repo root — it printed `build prerequisite
// not pre-checked` and deferred. Both halves of the seam are pinned, because
// fixing only the first would have been WORSE than the defect: a resolving probe
// over a CWD-relative existence check reports "the workspace CLI is not built"
// about a CLI that is built.
const probeCwdBefore = process.cwd();
const onRootProbe = resolveCliCommandFile(EXTRACT_COMMAND_ID);
let offRootProbe;
let anchoredReadOffRoot;
let bareReadOffRoot;
try {
process.chdir(tmpdir());
offRootProbe = resolveCliCommandFile(EXTRACT_COMMAND_ID);
anchoredReadOffRoot = existsSync(atRepoRoot('scripts/check-i18n-bundles.mjs'));
bareReadOffRoot = existsSync('scripts/check-i18n-bundles.mjs');
} finally {
process.chdir(probeCwdBefore);
}
expect(
'#11394 the shared CLI probe answers a path, not a deferral',
!!onRootProbe.file && !onRootProbe.unknown,
`over this repo the probe must derive the command file; got ${JSON.stringify(onRootProbe)}`,
);
expect(
'#11394 …and answers the same from any cwd',
JSON.stringify(offRootProbe) === JSON.stringify(onRootProbe),
`off-root the probe said ${JSON.stringify(offRootProbe)}, on-root ${JSON.stringify(onRootProbe)}`,
);
expect(
'#11394 …spelled repo-relative, as `CLI` and every message here are',
typeof onRootProbe.file === 'string' && !onRootProbe.file.startsWith('/'),
`anchoring the READ must not leak into the vocabulary; got ${JSON.stringify(onRootProbe)}`,
);
// The consumer side of the same seam, proven with a read that is true on ANY
// tree: `--self-test` runs with no build, so comparing `existsSync` on the
// command file itself would agree over nothing from both cwds.
expect(
'#11394 an anchored read lands on the repo from a foreign cwd',
anchoredReadOffRoot,
'atRepoRoot() did not reach this repo from ' + tmpdir(),
);
expect(
'#11394 …and the bare spelling demonstrably would not have',
!bareReadOffRoot,
'the bare spelling resolved off-root too, so this assertion proves nothing about anchoring',
);

// -------------------------------------------------------------------------
// Fourth classifier (#7681): the OTHER prerequisite — a workspace package this
// gate loads whose build output no longer matches its source. Pinned here or
Expand DownExpand Up@@ -673,7 +724,16 @@ function checkCliBuildPrerequisite() {
console.error(`check-i18n-bundles: ${resolved.unknown} — build prerequisite not pre-checked`);
return;
}
if (existsSync(resolved.file)) return;
// `resolved.file` is repo-relative (`packages/cli/dist/commands/i18n/extract.js`),
// so the EXISTENCE check on it needs the same anchor the read behind it got
// (#11394). Unanchored, an off-root run that got this far would report "the
// workspace CLI is not built" about a CLI that IS built — the #5862 defect (a
// confident diagnosis pointing somewhere innocent) rebuilt one layer down, and
// the exact sentence `check-i18n-coverage.mjs` already carries over its own copy
// of this line. Before #11394 the probe could not get here from a foreign cwd at
// all, so anchoring the read without anchoring this would have traded a harmless
// deferral for a false hard failure.
if (existsSync(atRepoRoot(resolved.file))) return;
reportPrerequisiteNotMet('the workspace CLI is not built', [
`This gate runs the BUILT CLI. ${CLI} is only a source stub that hands`,
`off to oclif, which resolves \`os ${EXTRACT_COMMAND_ID.join(' ')}\` from the compiled`,
Expand Down
61 changes: 61 additions & 0 deletions scripts/check-i18n-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -885,6 +885,67 @@ function selfTest() {
`absolute paths would silently re-key every baseline entry; got ${JSON.stringify(offRoot.slice(0, 2))}`,
);

// The SHARED probe's own anchoring (#11394). #10907 anchored this FILE; the
// module it asks "is the CLI built?" kept reading `packages/cli/package.json`
// CWD-relatively, so from any other cwd the probe ENOENTed and deferred — and
// every off-root run was preceded by a line about a workspace that was fine.
// Pinned here rather than in the shared module because the module has no
// self-test of its own: both of its consumers pin its classifiers, and this is
// the one property of it that a recorded string cannot prove.
const probeCwdBefore = process.cwd();
let offRootProbe;
try {
process.chdir(tmpdir());
offRootProbe = resolveCliCommandFile(LINT_COMMAND_ID);
} finally {
process.chdir(probeCwdBefore);
}
const onRootProbe = resolveCliCommandFile(LINT_COMMAND_ID);
expect(
'#11394 the shared CLI probe answers a path, not a deferral',
!!onRootProbe.file && !onRootProbe.unknown,
`over this repo the probe must derive the command file; got ${JSON.stringify(onRootProbe)}`,
);
expect(
'#11394 …and answers the same from any cwd',
JSON.stringify(offRootProbe) === JSON.stringify(onRootProbe),
`off-root the probe said ${JSON.stringify(offRootProbe)}, on-root ${JSON.stringify(onRootProbe)} — ` +
'the read behind it is CWD-relative again',
);
expect(
'#11394 …spelled repo-relative, as every message and rerun command is',
typeof onRootProbe.file === 'string' && !onRootProbe.file.startsWith('/') && !onRootProbe.file.includes(REPO_ROOT),
`anchoring the READ must not leak into the vocabulary; got ${JSON.stringify(onRootProbe)}`,
);
// …and the consumer side of the same seam: this gate asks the FILESYSTEM about
// that repo-relative answer, and `at()` is what makes the question land on the
// repo rather than the cwd. Unanchored it would report "the workspace CLI is not
// built" from a foreign cwd over a built one — which is why the probe resolving
// and this check being anchored are one change, not two.
//
// Proven with a read that is true on ANY tree rather than by comparing
// `existsSync` on the command file: `--self-test` runs with no build, where that
// file is absent from both cwds and the comparison would agree over nothing.
let anchoredReadOffRoot;
let bareReadOffRoot;
try {
process.chdir(tmpdir());
anchoredReadOffRoot = existsSync(at('scripts/check-i18n-coverage.mjs'));
bareReadOffRoot = existsSync('scripts/check-i18n-coverage.mjs');
} finally {
process.chdir(probeCwdBefore);
}
expect(
'#11394 an anchored read lands on the repo from a foreign cwd',
anchoredReadOffRoot,
`at() did not reach this repo from ${tmpdir()} — REPO_ROOT is ${REPO_ROOT}`,
);
expect(
'#11394 …and the bare spelling demonstrably would not have',
!bareReadOffRoot,
'the bare spelling resolved off-root too, so this assertion proves nothing about anchoring',
);

// Anti-#4690 on the population itself. Red on nothing is the assertion that
// matters — this is the one verdict whose failure mode is a green line.
expect('#10907 an empty population is refused', !!emptyPopulationVerdict([]), 'zero configs must never be a pass');
Expand Down
64 changes: 58 additions & 6 deletions scripts/cli-build-prerequisite.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,8 +25,34 @@
// CLI and a stale dependency are different facts with different remedies, and the
// two signatures must not match each other's corpus.
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const HERE = dirname(fileURLToPath(import.meta.url));
/** This module lives in `scripts/`, so the repo root is one level up (#11394). */
const REPO_ROOT = resolve(HERE, '..');

/**
* A repo-relative path, resolved against the module-derived root — the ONE seam
* between this module's vocabulary and the filesystem (#11394), the shape #10907
* put on `check-i18n-coverage.mjs` one file over.
*
* EXPORTED, unlike that gate's private `at()`, because the vocabulary is: this
* module hands its consumers a repo-relative `file`, and a consumer that then
* asks the filesystem about it has to reach the same disk this module read from.
* Identical in value to `check-i18n-coverage.mjs`'s `at()` — both files live in
* `scripts/` — and deliberately not imported from it: that gate imports THIS
* module, so taking its root would be a cycle, and the anchor is two lines.
*/
export const atRepoRoot = (rel) => join(REPO_ROOT, rel);

// Repo-relative ON PURPOSE, and NOT to be anchored (#10907's note, restated here
// because #11394 anchored the reads around them). These spellings are the text
// every consumer's error messages and rerun commands are written in, and `CLI` is
// an argv path spawned against a cwd of the repo root. `atRepoRoot` above is the
// one seam that turns them into paths on disk, so the vocabulary stays relative
// while every READ is anchored. Making them absolute would rewrite the commands
// this repo tells a reader to run.
/** The bin stub every gate spawns. A source file — its presence means nothing. */
export const CLI = 'packages/cli/bin/run.js';
/** The package whose `oclif` block declares where the built commands land. */
Expand DownExpand Up@@ -182,18 +208,44 @@ export function looksLikeStaleWorkspaceDist(text) {
/**
* `oclifCommandFileFor` against the real `packages/cli/package.json`.
*
* Both failure modes come back as one `unknown` REASON rather than a guessed
* path, because a probe that cannot read the declaration must not turn a
* correctly-built workspace red: the caller prints the reason and defers to its
* in-loop signature net, which is the actual enforcement.
* The read is ANCHORED (#11394). It used to be `join(CLI_PKG, 'package.json')`
* against the cwd, so from anywhere but the repo root it ENOENTed and the probe
* deferred — not because the declaration was unreadable, but because it had been
* looked for in the wrong place. Every off-root run of either consumer was
* preceded by
*
* check-i18n-coverage: could not read packages/cli/package.json (ENOENT …)
* — build prerequisite not pre-checked
*
* over a workspace that was fine, and on an UNBUILT tree it cost the diagnosis
* outright: the one environment fact reached the in-loop net once per config
* instead of being named once, up front.
*
* The deferral DIRECTION is unchanged and still correct — both failure modes come
* back as one `unknown` REASON rather than a guessed path, because a probe that
* cannot read the declaration must not turn a correctly-built workspace red: the
* caller prints the reason and defers to its in-loop signature net, which is the
* actual enforcement. What #11394 changed is which facts can reach it. Only a
* genuinely unreadable or unshaped declaration does now; a foreign cwd does not.
*
* ⛔ Do not restore the cwd-relative read to reproduce a deferral. It was used as
* one once — #11395's twelve-way cause split was measured through it — and that
* reason discharged at `a1508766`: `groupFailuresByCause` keys a cause on a `fix`
* drawn from a CLOSED set of constants, and the per-config rerun command is
* rendered by the report, so the split cannot recur whether this probe defers or
* not. A deferral for testing is `oclifCommandFileFor({ oclif: {} }, …)`, which is
* pure and is what both consumers' `--self-test` already pins.
*
* @param {string[]} commandId
* @returns {{ file: string } | { unknown: string }}
*/
export function resolveCliCommandFile(commandId) {
let pkgJson;
try {
pkgJson = JSON.parse(readFileSync(join(CLI_PKG, 'package.json'), 'utf8'));
// The message stays repo-relative (it is the path a reader would `cat` from
// the root); only the READ is anchored. node's own ENOENT text carries the
// absolute path it tried, which is evidence rather than vocabulary.
pkgJson = JSON.parse(readFileSync(atRepoRoot(join(CLI_PKG, 'package.json')), 'utf8'));
} catch (e) {
return { unknown: `could not read ${CLI_PKG}/package.json (${e.message})` };
}
Expand Down
Loading