Repository navigation
fix(cli): os init / os create emit overrides.vitest so new projects install on npm 10 - #23850
Conversation
…nstalls on npm 10 vitest 4.1.12 peers @vitest/browser-webdriverio ^4.1.11, whose only release peers vitest 4.1.11 exactly. npm 10 reaches that contradiction through optional vitest peers in the @objectstack/cli closure and crashes (edgesOut of null) on every os init template, app and empty included. Every standalone os init / os create manifest now emits overrides.vitest from SCAFFOLD_VITEST_RANGE (SCAFFOLD_NPM_OVERRIDES), and the stamp script gains an overrides.vitest POLICY_STAMPS row so the blank template's override is generated rather than hand-kept. Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
…affold-vitest-override
…po path check:cross-package-test-inputs reads a quoted repo-relative path in a test source as an input the test reads; the comment only describes where the override is generated, so it names the script without spelling the path. Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f44475186840ba7a85fe06d09d3eed603f794c56 && git checkout f44475186840ba7a85fe06d09d3eed603f794c56
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3c080a65c8c17af97403fed1c26cddac05f43a6a f8d18b09257057d12893525766d1d4b292e1c020 && git checkout -B drift-repro 3c080a65c8c17af97403fed1c26cddac05f43a6a && git merge --no-ff f8d18b09257057d12893525766d1d4b292e1c020
node scripts/docs-audit/affected-docs.mjs --json 3c080a65c8c17af97403fed1c26cddac05f43a6a
|
席位核对ACCEPT · #23805 ·
Landing: once every check on the head is green or an expected skip, this seat flips the PR ready and arms auto-merge through the relay, then follows it to Generated by Claude Code |
Fixes #23805
Clause-②: no
os init(all three templates) andos create pluginnow emit"overrides": { "vitest": "~4.0.0" }, built fromSCAFFOLD_VITEST_RANGEthrough one new export,SCAFFOLD_NPM_OVERRIDES. The scaffolded project now installs on npm 10, the npm that Node 22 bundles.scripts/sync-scaffold-emission-policy.mjsgains anoverrides.vitestPOLICY_STAMPSrow, so the blank template's override is now generated from the same constant instead of being kept by hand. The template file's bytes do not change, because it already reads~4.0.0.Upstream re-measured first, as the card asks
Read 2026-10-16T06:46Z with
npm view:vitestdist-tags:V4=4.1.12,latest=5.0.5.vitest@4.1.12peers"@vitest/browser-webdriverio": "^4.1.11"(optional). Every other@vitest/*peer is pinned to exactly4.1.12.@vitest/browser-webdriveriohas no4.1.12. Its newest 4.x is4.1.11, which peers"vitest": "4.1.11".Every emission site, with its verdict
Measured 2026-10-16T06:57Z. Each manifest was installed in an empty directory with a fresh
--cache, against the published18.0.0-next.2. "Before" is today'smainemission.vitestdirectlyos init -t app(init.ts,renderScaffoldPackageJson)edgesOutos init -t plugin(same renderer)~4.0.0edgesOutos init -t empty(same renderer)edgesOutos create pluginstandalone (create.ts)~4.0.0os create plugin --in-repo~4.0.0overridesonly from the root manifest and pnpm never reads it, so here it would do nothingcreate-objectstackblank template~4.0.0POLICY_STAMPSrowos createhas one template (plugin), andos inithas three (app,plugin,empty), all rendered throughrenderScaffoldPackageJson. No otherpackages/clicode writes a scaffoldpackage.json.Why
-t appand-t emptytoo, against the card's plugin-only criterionThe dispatch assumed that a site emitting no
vitestneeds no override. The measurement above shows otherwise:-t appand-t emptycrash the same way, because what all three templates share is@objectstack/cliindevDependencies, and its closure carries optionalvitestpeers (@objectstack/metadata-corepeers^4.0.0, better-auth peers^2 || ^3 || ^4).-t appis also the docs.objectos.ai quickstart's command, measured failing 3 of 3 in the card's thread.os create pluginhas no@objectstack/cliin its closure, which is why it installed without the override.The installs after the change
Each emitted manifest was produced by driving the real CLI (
tsx bin/run-dev.js init NAME -t T --no-installandcreate plugin NAME), then installed in an empty directory with a fresh--cache:os init -t appos init -t pluginos init -t emptyos create pluginCost, measured
On an emitted
-t appwith the override (npm 10.9.9):npm install -D vitestis refused withEOVERRIDE: Override for vitest@* conflicts with direct dependency.npm install -D vitest@~4.0.0installs (vitest 4.0.18).The refusal is loud and names the conflict; without the override the project could not install at all. The changeset gives the one-line fix.
"$vitest"is not used: it needs a direct dependency, and two of the threeos inittemplates have none.The stamp row
POLICY_STAMPSgains{ key: 'overrides.vitest', block: 'overrides', field: 'vitest', constant: 'SCAFFOLD_VITEST_RANGE' }. The engine already locates any"BLOCK": { … }object and one field inside it, so no engine change was needed. The self-test:overrides.vitest.devDependencies.vitestagrees. It checks that--checkexits 1 namingoverrides.vitest, that the agreeing key is not blamed, that the rewrite touches only that span byte for byte, and that a template dropping theoverridesblock exits 1 naming it.--checkon the real template, before and after a mutation (throughscripts/ablation-replace.mjs, which restores and checks the blob hash):overrides.vitestwas mutated to^4.1.0.--checkthen exits 1 withpackages/create-objectstack/src/templates/blank/package.json: overrides.vitest is "^4.1.0" but the policy declares "~4.0.0".1bbbff43ce0fequals HEAD, andgit diff HEADis empty.main's script (1ecb08a1c, no row) exits 0 (2 carrier(s) emit the shared policy). So onmain, an override drift that npm refuses withEOVERRIDEpassed the gate.Pins
packages/cli/test/scaffold-npm-overrides.test.tsis a unit-tier file. Every expectation is derived from the exported constants, with no literal range:os inittemplate andos create plugin(standalone) emitoverridesequal toSCAFFOLD_NPM_OVERRIDES, whosevitestisSCAFFOLD_VITEST_RANGE;EOVERRIDE);--in-repoemits nooverrides;Ablation, through
scripts/ablation-replace.mjs. Each leg mutates, runs the file, restores, and proves the restored blob equals HEAD:renderScaffoldPackageJsonfails the 3os initcases (3 failed / 8 passed).create.tsfailsos create plugin(1 failed / 10 passed).Verification
All results below are at head
f8d18b092unless a line says otherwise. Exit codes were captured before any pipe.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstacknames 85 commands, derived from merge base43567a8fc. Run atf8d18b092: 85 of 85 exit 0.--ranreconciliation: exit 0,85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN.e521b5709, found one real red:check:cross-package-test-inputsexit 1. A comment intemplate-consistency.test.tsspelled the stamp script's repo path, which the gate reads as a cross-package input. The comment was reworded inf8d18b092.node scripts/sync-scaffold-emission-policy.mjs --self-test: exit 0, 60 assertions, 10 batteries.--check: exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2over the 23 non-e2e test files that importcommands/initorcommands/create, the new pin included: exit 0, 22 files / 501 tests passed, atd8a9a54f3. The later commits touch nopackages/clifile.test/generate-stack-reach.test.tsis in theintegrationproject. That tier and the nightlyscaffold-emission-policy.e2e.test.tsare declared to CI.pnpm --filter @objectstack/cli typecheck: exit 0 (tsc --noEmitpluscheck:test-typecheckOK), atd8a9a54f3.pnpm --filter create-objectstack test: exit 0, 18 files / 275 tests.typecheck: exit 0.pnpm exec eslint --no-inline-configover the five changed source and test files: exit 0. The repo-wide lint run belongs to theLint & Repo Gatescheck-run.Acceptance notes
skills/objectstack-platform/references/bootstrap.md, the "Complete Working Example"package.jsonfence (@objectstack/cli ^17.0.0, no override). Itsnpm installon npm 10.9.9 exits 1 withCannot read properties of null (reading 'edgesOut'), and withoverrides.vitest: ~4.0.0added it exits 0 (measured 2026-10-16T08:10Z).skills/**is a governed surface, and the stamp script'sPUBLISHED_RESTATEMENTSleg only reads that fence and checks onlydevDependencies.typescript. So the fix is a hand edit landed the governed way, and addingoverrides.vitestto that row'skeyscomes with it. This is reported for the seat to file, not filed from this PR.npm install -g @objectstack/clicrashes in npm's peer resolution ("Cannot read properties of null (reading 'edgesOut')") #23801) and the optionalvitestpeers ofplugin-auth/metadata-core(finding: existing projects cannotnpm install @objectstack/plugin-author@objectstack/metadata-coreon npm 10 since vitest@4.1.12 (edgesOut); metadata-core's own optionalvitestpeer is one path in #23806) are untouched; both remain open. The blank template's override from the PR for release(next): the Version Packages PR #23481 is red — npm 10 crashes withedgesOuton two lockfile-less installs (validate-deps vendor-export resolve, scaffold-e2e); fix on main, never on the generated branch #23788 is unchanged in bytes and is now generated.create-objectstack's shipped bytes do not change (filesisdist, and the template is byte-identical), so the changeset names@objectstack/clialone.Generated by Claude Code