Skip to content

Commit cd7e34e

Browse files
committed
fix(cli): scaffolds declare vitest ~4.0.0 so npm install works on npm 10
SCAFFOLD_VITEST_RANGE moves from ^4.0.0 to ~4.0.0. With ^4.0.0, npm 10 (Node 22's bundled major) crashes in arborist #loadPeerSet with "Cannot read properties of null (reading 'edgesOut')": vitest 4.1.x admits vite 8, whose optional @vitejs/devtools peer leads to a vitest@* peer that resolves to vitest's latest and contradicts the root vitest. vitest 4.0.x admits only vite 6 or 7, which declare no such peer. The comment beside the constant records the measured cause, why ^5.0.0 is not the fix, and the re-check that lets it move back. Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7dbb89d commit cd7e34e

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
fix(cli): plugin scaffolds declare `vitest ~4.0.0`, so `npm install` works on npm 10
6+
7+
Clause-②: no
8+
9+
`objectstack init -t plugin` and `objectstack create plugin` now declare `vitest ~4.0.0` instead of `^4.0.0`. With `^4.0.0`, a fresh `npm install` of the emitted project crashed on npm 10, the npm that Node 22 bundles, with `Cannot read properties of null (reading 'edgesOut')`. npm 11 was not affected.
10+
11+
The cause is upstream. vitest 4.1.x accepts vite 8, and vite 8's optional devtools peers lead back to a `vitest@*` peer that npm 10 cannot resolve against an older vitest at the root. vitest 4.0.x accepts only vite 6 or 7, which carry no such peer. A scaffolded project now installs vitest 4.0.18 with vite 7.3.7 on npm 10 and npm 11.
12+
13+
Projects that were already scaffolded are unchanged. If one of them fails to install on npm 10, change its `vitest` devDependency to `~4.0.0`, or install with npm 11.

‎packages/cli/src/commands/init.ts‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,10 @@ export const SCAFFOLD_PNPM_RANGE = '>=10.15';
342342
//
343343
// `^4.0.0` over `^4.0.18`: no recorded decision exists for either, both were
344344
// written in the same 2026-02-07 commit, and `^4.0.18` claims a PATCH-level
345-
// floor nothing justifies while being strictly the narrower of the two.
345+
// floor nothing justifies while being strictly the narrower of the two. (The
346+
// vitest range has since moved to `~4.0.0` because `^4.0.0` stopped
347+
// installing on npm 10. That was an install failure, not a support decision;
348+
// the comment on `SCAFFOLD_VITEST_RANGE` holds the reading.)
346349
//
347350
// ⚠️ Neither choice changes what a scaffolded project INSTALLS. Measured
348351
// against the registry: `^5.3.0` and `^5.8.0` both resolve to typescript
@@ -379,8 +382,38 @@ export const SCAFFOLD_PNPM_RANGE = '>=10.15';
379382
/** The TypeScript range every scaffolded project declares. */
380383
export const SCAFFOLD_TYPESCRIPT_RANGE = '^5.3.0';
381384

382-
/** The vitest range a scaffolded project declares when its template tests. */
383-
export const SCAFFOLD_VITEST_RANGE = '^4.0.0';
385+
/**
386+
* The vitest range a scaffolded project declares when its template tests.
387+
*
388+
* `~4.0.0`, not `^4.0.0`, because `^4.0.0` does not INSTALL on npm 10, the npm
389+
* that Node 22 bundles. Measured 2026-10-12, in an empty directory with a fresh
390+
* `--cache` and a manifest holding only this one devDependency (the full matrix
391+
* is in the PR that set this value): on npm 10.9.4 and 10.9.9, `npm install`
392+
* exits 1 with `Cannot read properties of null (reading 'edgesOut')` from
393+
* arborist's `#loadPeerSet`, and npm 11.21.0 installs it. `~4.0.0` installs on
394+
* all three (vitest 4.0.18, vite 7.3.7), and so do both CLI-emitted plugin
395+
* manifests. The cost: a scaffold gets vitest 4.0.18 rather than 4.1.11.
396+
*
397+
* The cause is upstream, in the peer graph. Every vitest 4.1.x admits vite 8.
398+
* vite 8 declares an optional `@vitejs/devtools` peer, which reaches
399+
* `@vitejs/devtools-vitest`, which peers `vitest@*`. npm 10 resolves that peer
400+
* to vitest's `latest` release, whose exact-version peers then contradict the
401+
* vitest at the root, and npm 10's arborist crashes on it. vitest 4.0.x admits
402+
* only vite `^6 || ^7`, and no vite 6 or 7 release declares the devtools peer,
403+
* so the `vitest@*` peer never enters the graph.
404+
*
405+
* ⛔ Not `^5.0.0`, although it installs today. It installs only because it
406+
* resolves to exactly the version `latest` names: pinned to 5.0.0 or 5.0.2 it
407+
* crashes the same way. So it would break again, with no change here, the day
408+
* `latest` moves past 5.x. ⛔ Not a shipped `.npmrc` with `legacy-peer-deps`
409+
* either, which changes peer resolution for every npm user of the scaffold.
410+
*
411+
* The re-check that lets this move back: run `npm install` of
412+
* `{"devDependencies":{"vitest":"^4.0.0"}}` (or the range you want) in an empty
413+
* directory with a fresh `--cache`, on the newest npm 10 and on npm 11. When
414+
* both exit 0, widen this.
415+
*/
416+
export const SCAFFOLD_VITEST_RANGE = '~4.0.0';
384417

385418
/** The `@types/node` range a scaffolded project declares. */
386419
export const SCAFFOLD_TYPES_NODE_RANGE = '^22.0.0';

0 commit comments

Comments
 (0)