Repository navigation
Commit 83e31cd
test(cli): tmpdir fixtures that run pnpm reach no registry (dev-no-watch pin, doctor readers, init leg) (#24455)
Fixes #24419
Clause-②: no
Basis for the Clause-② line: every changed file is under
`packages/cli/test/**`, the CLI's test harness. `@objectstack/cli`
publishes `dist`, `README.md` and `CHANGELOG.md` (its `files[]`).
Measured on head `8994da181`, after `pnpm turbo run build
--filter='@objectstack/cli...'`: the identifiers this PR adds
(`derivePackageManagerPin`, `packageManagerPin`, `PACKAGE_ROOT`,
`STUB_PNPM_VERSION`, `writeStubPnpm`, `stubBinDir`, `fixtureRoot`) occur
in 0 of those files. The positive control `devWatchActive`, which comes
from `src/`, occurs in 2. Nothing published moves, so this PR carries
the `skip-changeset` label and no changeset.
## The pin's shape: derived from pnpm (option B, the seat's decision)
The first head (`bd0651500`) read the `packageManager` pin from the root
`package.json`. That read leaves `packages/cli`, so
`check:cross-package-test-inputs` went red, and declaring it needed
three files outside the claim's file surface. The work stopped there,
and the seat chose option B (PR thread, patch round 1).
The pin is now derived. `dev-no-watch.pin.test.ts` runs `pnpm --version`
with `cwd` = `packages/cli` and writes `pnpm@VERSION` into the fixtures.
That answer is the root pin, reached through Corepack's own walk. No
root file is read and no new turbo input is declared.
`check:cross-package-test-inputs` exits 0 on `8994da181`, and
`turbo.json`, `scripts/cross-package-test-inputs.mjs` and
`scripts/check-cross-package-test-inputs.mjs` are untouched (`git diff
db1f1fd HEAD` is empty for all three).
Why B:
- The card's acceptance is that the test reaches no network. B meets it,
and the pin is derived rather than copied by hand.
- A would have made the root `package.json` an input of
`@objectstack/cli#test`. Every PR that edits it would then add cli's
PR-tier suite to Test Core. Measured: a root-`package.json`-only edit
gives `turbo ls --affected` 0 packages today (the control, a
`packages/types/package.json` edit, gives 69 including cli). On
`origin/main`, 42 of the last 300 commits (since 2026-10-16) touched the
root `package.json` and no `packages/`, `examples/` or `apps/` path.
What B leaves, stated in the file's own comment: the pin is an
undeclared input, so a pin-only bump does not re-run this file. This
suite's other `pnpm -v` and `pnpm pack` callers have the same blind
spot, so B adds no new class of it. The fixture spec carries no
integrity hash, and the derivation costs one extra spawn.
Option A's patch was measured and not taken. On `bd0651500`, three edits
turned the gate green with `check-ci-filter-parity` also green, and were
then restored (blob equal to HEAD, `git diff HEAD` empty for all three):
- `turbo.json`: `"$TURBO_ROOT$/package.json"` in the
`@objectstack/cli#test` inputs;
- `scripts/cross-package-test-inputs.mjs`: `'package.json'` in the cli
globs;
- `scripts/check-cross-package-test-inputs.mjs`: `'package.json'` in the
pinned `TURBO_ROOT_FILE_INPUTS` roster.
## The premise, measured on `db1f1fd20` before any change
CI's runner shape was rebuilt locally as `.github/actions/setup-pnpm`
leaves it: Corepack shims first on `PATH`, and a private `COREPACK_HOME`
holding exactly the pinned `pnpm@10.31.0` (materialised with `corepack
install` in the repo root, with no `lastKnownGood.json`). The registry
was made unreachable: `COREPACK_NPM_REGISTRY` and `npm_config_registry`
point at a local server that answers HTTP 429 to every request and logs
each one. A logging `pnpm` shim in front of Corepack's records every
pnpm call's cwd and argv.
- **Mechanism.** In a bare `tmpdir()` directory, `pnpm --version` sent
`GET /pnpm/latest` and exited 1 with `Server answered with HTTP 429 when
performing the request to …/pnpm/latest`. That is CI's signature on PR
#24415. The same directory with a `package.json` carrying the root pin:
`10.31.0`, 0 requests.
- **`dev-no-watch.pin.test.ts`, unchanged:** the same two tests CI
failed went red. "and the failure is pnpm refusing the PACKAGE…" failed
with `AssertionError: os dev ended with code 1`. "the fixture starts its
own dev script when asked — the control" failed with `expected 1 to be
+0`. Both outputs carry the 429 line. The `--no-watch` spawn never runs
pnpm, because the refusal comes first. "`os dev --watch=false` exits 1
instead of 0" stayed GREEN under the 429, because Corepack's failure
exits 1 too. Only its sibling assertion on pnpm's own sentence tells the
two apart.
## The shape, and why
The fixtures carry the repository's own pin as `pnpm@VERSION`, where
VERSION is what `pnpm --version` answers in `packages/cli` (see the
section above). Nothing is copied by hand. It is the one version CI's
Corepack store already holds, so it resolves with no network call.
- **Where the pin sits.** One fixture root holds both fixtures, and its
`package.json` carries the pin. `workspace/package.json` carries it too,
because it is the nearest manifest. `project/` holds no manifest.
Measured with pnpm 10.31.0: a `package.json` inside the project
directory changes pnpm's answer from `No projects found in "…"` (the
sentence this file pins) to `No projects matched the filters in "…"`.
With the pin one level up, the answer is unchanged.
- **Why not the env route.** `COREPACK_DEFAULT_TO_LATEST=0` does not
remove the network reach in CI's shape. It swaps `latest` for Corepack's
own bundled default, which the store does not hold. Measured: `GET
/pnpm/10.13.1`, 429, exit 1. `COREPACK_ENABLE_NETWORK=0` would only turn
the fetch into a refusal.
- The two tests keep their subjects: pnpm's no-projects refusal, and the
`--no-watch` refusal against a fixture whose `dev` script runs.
## The family census
Method: the CLI paths that run pnpm were enumerated from every
child-process call site in `packages/cli/src`. There are three: `os dev`
monorepo mode (`pnpm … dev`), `os doctor` (`pnpm -v`), and `os init`
(`PM install`). Tests that spawn pnpm directly, or drive one of those
three paths, were enumerated by `git grep` over the command literals.
Each candidate was then run in the CI shape above, with the registry
refused.
| File | Runs pnpm? | cwd | Pin in reach? | Registry, measured before |
Verdict |
|---|---|---|---|---|---|
| `test/dev-no-watch.pin.test.ts` | `os dev` → `pnpm --filter false …
dev`, `pnpm dev` | `tmpdir()` | no | 2 × `GET /pnpm/latest`, 2 tests red
| **fixed**: pin derived from `pnpm --version` in `packages/cli` |
| `test/package-owned-command-readers.test.ts` | `os doctor` → `pnpm -v`
(2 children) | `tmpdir()` | no | 2 × `GET /pnpm/latest`, tests GREEN
(doctor turns a failed probe into a row) | **fixed**: a stub `pnpm` on
`PATH` answers `-v` and refuses anything else. A new `it` per shape
asserts the stub was reached |
| `test/create-refuses-invalid-project-name.e2e.test.ts` (nightly tier)
| `os init NAME` (no `--no-install`) → `pnpm install` | `tmpdir()`
scaffold (no pin, on purpose) | no | 1 × `GET /pnpm/latest`, 1 test red.
With a working registry it installs the scaffold's dependencies from npm
| **fixed**: `--no-install`. The leg's subject is that `init` accepts
the name |
| `test/init.test.ts` | no: renders templates in-process | n/a | n/a | 0
pnpm calls | no reach |
| `test/init-created-files-summary.e2e.test.ts` | a fake `pnpm` leads
`PATH` | `tmpdir()` | n/a | 0 real pnpm calls, 0 requests | no reach |
| `test/published-subpath-console.pin.test.ts` | `pnpm pack` |
`packages/cli` | yes | 0 requests | no reach (`tmpdir()` is only
`--pack-destination`) |
| `test/published-subpath-hook-body.pin.test.ts` | `pnpm pack` |
`packages/cli` | yes | 0 requests | no reach |
| `src/commands/doctor-*.test.ts` (10 files, in-process `Doctor.run`) |
`pnpm -v` | the process's real cwd, `packages/cli` (they spy
`process.cwd()`, which a child never reads) | yes | 87 calls, 0 requests
| no reach |
| `test/create-objectstack-stack-reach.test.ts` (found by the whole-tier
run below; the grep missed it) | `pnpm --version` |
`packages/cli/node_modules/.create-objectstack-reach-*` | yes | 0
requests (measured at `bd0651500`) | no reach |
Other `os dev` spawns refuse before pnpm (`--no-watch`) or boot one
environment (`--artifact`, a config in cwd). Other `os init` spawns
refuse before the install, pass `--no-install`, or stub `npm`.
`exit-signal.pin.test.ts` mocks `child_process`.
**Whole PR tier, after the fix, in the CI shape with the registry
refused** (head `bd0651500`):
- `--project unit`: 311 files, 4610 tests passed, **0 registry
requests**. The only pnpm calls were the two `pnpm pack` runs and 87 ×
`pnpm -v`, all in `packages/cli`.
- `--project integration`: 160 files, 159 passed + 1 skipped (a
pre-existing skip in a file this PR does not touch), 1375 tests passed +
9 skipped, **0 registry requests**. pnpm was called by dev-no-watch's
two spawns (pinned) and by `create-objectstack-stack-reach.test.ts`'s
`pnpm --version` in `packages/cli/node_modules/…`, where the root pin is
in reach. That file is the table's last row.
Patch round 1 (`8994da181`) changes only `dev-no-watch.pin.test.ts`.
That file was re-proved on the new head (see Proof); the whole-tier
readings above were taken on `bd0651500`.
The nightly tier was not run whole. Its pnpm-reaching candidates are the
two `.e2e` rows above, each measured.
## Proof: the converted tests with the registry refused
- Head `8994da181`: `dev-no-watch.pin` + `package-owned-command-readers`
16/16 passed, **0 registry requests**. The derivation's `pnpm --version`
ran in `packages/cli`. The pnpm calls in
`/tmp/os-dev-no-watch-*/project` and `…/workspace` resolved to `10.31.0`
from the store.
- Head `bd0651500`, files unchanged since:
`create-refuses-invalid-project-name.e2e` +
`init-created-files-summary.e2e` (`OS_TEST_TIERS=nightly`) 10/10 passed,
**0 registry requests**.
- The Corepack store still holds exactly `v1/pnpm/10.31.0`, with no
`lastKnownGood.json`.
- Also green in this container's default shape (a standalone pnpm
10.28.0, not Corepack), with both registries refused, on `8994da181`:
`dev-no-watch.pin` 8/8, 0 requests.
## Ablation (`scripts/ablation-replace.mjs`, wrap mode, fix committed
first, head `8994da181`)
- Anchor `packageManager: packageManagerPin` (2 hits, `--expect 2`),
replaced with `packageManager: undefined`. On disk: anchor 2 → 0,
replacement 0 → 2, blob `0881d577b041` → `1df76c152cb3`.
- With the registry refused, it went RED, the expected direction. The
same two tests failed as in the premise run: "and the failure is pnpm
refusing the PACKAGE…" and "the fixture starts its own dev script when
asked — the control". 6 passed, and there were 2 × `GET /pnpm/latest`.
- Restore: blob after restore `0881d577b041` equals HEAD, `git diff
HEAD` empty, `git status --porcelain` empty.
- The same ablation on `bd0651500`'s read-the-manifest shape gave the
same red: 2 failed, 2 requests, restore proven.
## Tiers
The tiers are unchanged. `vitest-tiers.ts`'s `tierSignals` gives the
same answer for each changed file before (`db1f1fd20`), on `bd0651500`
and on `8994da181`. For `dev-no-watch.pin` and `create-refuses-…e2e`:
integration (`childProcess, entryBasename, tsxBin`). For
`package-owned-command-readers`: integration (`childProcess,
helperCliOrTsx`). The e2e file keeps its nightly name.
`test/vitest-tiers-partition.test.ts` passed (22/22 on `8994da181`).
## Gates (local, head `8994da181`)
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 52 derived commands, each run with its exit
code captured before any pipe. All 52 exited 0, `pnpm
check:cross-package-test-inputs` included.
- `pnpm check:dual-build-cjs-loads` first answered 3, PREREQUISITE NOT
MET: the fresh worktree had no `dist/` for packages outside cli's
closure, so that run measured nothing. A later gate in the battery
(`check:type-check-debt`) built them, and the re-run exited 0.
- The head is behind `origin/main`, and the derivation flagged two gate
files changed there. The same derivation on a tree at `origin/main`
`725a5b131`, with this diff applied, gives the identical 52 commands.
- The 11 declared-wide families were each run and exited 0. `pnpm
check:cli-test-child-env` exited 0.
- `--ran` reconciliation: 52 derived, 52 run, 0 NOT-MEASURED (a derived
zero: all 52 recorded an exit code), 0 UNRUN.
- `eslint --no-inline-config` over the 3 changed files: exit 0, 0
errors, 0 warnings, none ignored.
- `pnpm --filter @objectstack/cli typecheck`: exit 0. `tsc -p
tsconfig.test.json --listFiles` lists all 3 changed files. Its 28 errors
are exactly the ledgered `test-typecheck-debt.json` set, none in these
files.
- CI on `8994da181`, read before this reply: 33 success, 7 skipped, 0
failures. All five required contexts are green (`Lint & Repo Gates`,
`TypeScript Type Check`, `Test Core`, `Build Core`, `Governed Surface
Queue Guard`).
## Acceptance notes
- `os doctor`'s pnpm row misreports a Corepack fetch failure as `✗ pnpm
Not found → Install pnpm: npm install -g pnpm@10.28.1`, and prints
Corepack's stack trace above the report. Measured in a `tmpdir()` with
no pin under Corepack, with the registry answering 429. Filed as #24464,
not fixed here (`packages/cli/src/**` is held off).
- In the 10 in-process doctor tests, the `process.cwd()` spy does not
reach `doctor`'s `pnpm -v` child, which runs in the real cwd. That is
harmless here (the pin is in reach) and is noted only.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9847f87 commit 83e31cd
3 files changed
Lines changed: 137 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
182 | 182 | | |
183 | 183 | | |
184 | 184 | | |
185 | | - | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
186 | 195 | | |
187 | 196 | | |
188 | 197 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
| 49 | + | |
| 50 | + | |
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
62 | 110 | | |
63 | 111 | | |
64 | 112 | | |
| |||
143 | 191 | | |
144 | 192 | | |
145 | 193 | | |
| 194 | + | |
| 195 | + | |
146 | 196 | | |
147 | 197 | | |
148 | 198 | | |
149 | 199 | | |
150 | 200 | | |
151 | 201 | | |
152 | 202 | | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
153 | 216 | | |
154 | | - | |
| 217 | + | |
| 218 | + | |
155 | 219 | | |
156 | 220 | | |
157 | 221 | | |
158 | 222 | | |
159 | | - | |
160 | | - | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
161 | 227 | | |
162 | 228 | | |
163 | 229 | | |
164 | 230 | | |
165 | 231 | | |
166 | 232 | | |
| 233 | + | |
167 | 234 | | |
168 | 235 | | |
169 | 236 | | |
| |||
176 | 243 | | |
177 | 244 | | |
178 | 245 | | |
179 | | - | |
180 | | - | |
| 246 | + | |
181 | 247 | | |
182 | 248 | | |
183 | 249 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
43 | 77 | | |
44 | 78 | | |
45 | 79 | | |
46 | 80 | | |
47 | 81 | | |
48 | 82 | | |
49 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
50 | 93 | | |
51 | 94 | | |
52 | 95 | | |
| |||
97 | 140 | | |
98 | 141 | | |
99 | 142 | | |
| 143 | + | |
| 144 | + | |
100 | 145 | | |
101 | 146 | | |
102 | 147 | | |
| |||
137 | 182 | | |
138 | 183 | | |
139 | 184 | | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
140 | 191 | | |
141 | 192 | | |
142 | 193 | | |
| |||
0 commit comments