chore: pin the dev Node floor at 22.12, distinct from the consumer floor - #47
chore: pin the dev Node floor at 22.12, distinct from the consumer floor#47dangrondahl wants to merge 1 commit into
Conversation
vitest 4 (#31) pulls Vite 8, which requires ^20.19.0 || >=22.12.0. The consumer floor stays engines.node >=22 — that field constrains users, and nothing under devDependencies ships — but development now needs 22.12. The CI matrix said '22', which resolves to the latest 22.x, so the bottom of the range was never actually tested: the one band that breaks (22.0-22.11) was invisible to CI. Pinned to '22.12' so the tested floor is the real floor. @types/node moves to ^22.12.0 to match Vite 8's peer range (^20.19.0 || >=22.12.0); ^22.0.0 permitted versions outside it. The resolved version is unchanged at 22.19.17. .nvmrc and a CONTRIBUTING prerequisites note make the dev floor discoverable rather than something found via a mystery EBADENGINE.
|
Claude finished @dangrondahl's task in 2m 50s —— View job Review complete
No What's genuinely good hereThe core insight is correct and non-obvious: FindingsThree inline comments, none blocking:
One suggestion beyond the diff
Two process notes: Verdict: the substance is right and I'd merge it — after #31, as the PR body says, since the docs describe a floor that isn't real until then. |
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^22.0.0", | ||
| "@types/node": "^22.12.0", |
There was a problem hiding this comment.
This bump quietly changes which floor @types/node tracks, and it's the opposite direction from the rest of the PR.
CLAUDE.md's rule is "@types/node tracks the floor, not the newest release — otherwise tsc accepts APIs that don't exist on the minimum runtime." That rationale is about the consumer floor (22.0): a published server type-checked against 22.12 types can call something absent on a consumer's 22.0.x. Everywhere else in this PR you argue devDependency constraints shouldn't tighten what consumers see; here a devDependency's optional peer range (Vite declares @types/node under peerDependenciesMeta as optional, so a mismatch is a warning, not a failure) is what moves the declared range up.
Two things worth deciding explicitly rather than leaving implicit:
- Is the tightening buying anything? The resolved version is unchanged at 22.19.17 and satisfies both ranges, so this only narrows a range that was never the binding constraint.
- A caret range doesn't pin the floor anyway.
^22.0.0already resolved to 22.19.17 —tschas been seeing 22.19 types all along. If the CLAUDE.md rule is meant literally, only~22.0.0(or an exact pin) enforces it; if it isn't, the line deserves a hedge.
Whichever way you go, the CLAUDE.md sentence should now say which of the two floors @types/node follows, since the PR makes that ambiguous for the first time.
| # '22.12' rather than '22': the dev toolchain (Vite 8, via vitest) needs | ||
| # >=22.12, and a bare '22' resolves to the latest 22.x, so the bottom of | ||
| # the supported range would never actually be tested. | ||
| node-version: ['22.12', '24'] |
There was a problem hiding this comment.
The unchanged job-level comment on lines 13–14 is now stale and contradicts this one: "22 is the floor declared in package.json engines" was true when the low end was '22', but engines.node is >=22 while the matrix low end is now 22.12. Reading top-down you learn the low end tracks engines, then two lines later learn it doesn't. Worth folding both into a single comment that states the real invariant:
| # '22.12' rather than '22': the dev toolchain (Vite 8, via vitest) needs | |
| # >=22.12, and a bare '22' resolves to the latest 22.x, so the bottom of | |
| # the supported range would never actually be tested. | |
| node-version: ['22.12', '24'] | |
| # Low end is the dev floor (22.12), not the consumer floor | |
| # (engines.node >=22): the dev toolchain (Vite 8, via vitest) needs | |
| # >=22.12. Pinned rather than a bare '22' because '22' resolves to the | |
| # latest 22.x, so the bottom of the range would never be tested. | |
| node-version: ['22.12', '24'] |
(and drop the now-redundant "22 is the floor declared in package.json engines" sentence above).
Separately — worth being precise about what this buys, since the PR frames it as "the tested floor is the real floor". It makes the dev floor tested. The consumer floor (22.0–22.11, still allowed by engines.node) remains untested, and can't be tested by this job, because the dev toolchain won't run there. That's not a regression (a bare '22' never tested 22.0 either), but the gap this PR names as the problem still exists one level down for consumers.
|
|
||
| ## Getting started | ||
|
|
||
| **Node ≥ 22.12** (`.nvmrc` pins it — `nvm use` picks it up). The published server only needs Node ≥ 22, but the dev toolchain is stricter: vitest pulls Vite 8, which requires `^20.19.0 || >=22.12.0`. On Node 22.0–22.11 `npm install` reports `EBADENGINE` and the test run is likely broken. |
There was a problem hiding this comment.
This paragraph is not true on this branch yet — package.json still has "vitest": "^3.1.0", so there is no Vite 8 and no >=22.12 requirement. On today's main + this commit, npm install on Node 22.5 emits no EBADENGINE and the tests run fine. The PR body already flags this ("merge after #31"), so this is just the concrete consequence: the docs are wrong for exactly as long as this sits ahead of #31.
Two follow-ons worth confirming at merge time rather than now:
- If chore: bump vitest from 3.2.4 to 4.1.10 #31 lands with a different vitest/Vite pair than assumed,
22.12is hardcoded in four places (.nvmrc, the CI matrix, this paragraph, CLAUDE.md) and all four need re-checking against the range Vite actually declares. .github/workflows/update-catalog.ymlstill usesnode-version: '22', which CLAUDE.md's own "Raising the Node floor" checklist lists. It happens to keep working (bare'22'resolves to the latest 22.x, which satisfies>=22.12), so this isn't a break — but it is the one remaining place where the dev floor is satisfied by accident rather than on purpose. Either pin it to22.12for consistency or add a word to the checklist saying that workflow intentionally floats.
Follows #31 (vitest 4), now merged — so the dev floor described below is the one in effect on
maintoday.Addresses the two open points from the review on #31.
The CI matrix wasn't testing the floor
This is the substantive one.
node-version: '22'resolves to the latest 22.x, so the band that actually breaks — 22.0–22.11, which Vite 8 rejects — was never exercised. The matrix looked like it covered the bottom of the supported range and didn't.Pinned to
'22.12', so the tested floor is the real floor.Two floors, on purpose
engines.node >= 22>= 22.12^20.19.0 || >=22.12.0)Deliberately not raising
engines.node: it constrains consumers, and nothing underdevDependenciesships (files: ["dist"]). Agreeing with the review on this — tightening the published floor for a test-only dependency would misrepresent what users need.Made discoverable rather than found via a mystery
EBADENGINE:.nvmrcpinning22.12(nvm usepicks it up)CONTRIBUTING.md, right above thenpm installblock, stating both floors and what goes wrong below 22.12@types/node→^22.12.0Vite 8 declares
"@types/node": "^20.19.0 || >=22.12.0"as a peer.^22.0.0permitted versions outside that. Resolved version is unchanged at 22.19.17 — this only tightens the declared range, keeping it consistent with CLAUDE.md's rule that@types/nodetracks the floor.CLAUDE.md
Records the two-floor distinction explicitly, so nobody "fixes" the apparent inconsistency by raising
engines.node. The "Raising the Node floor" checklist gains.nvmrcand the CONTRIBUTING note.Verification
npm run buildclean,npm testgreen (67 tests), lockfile regenerated withnpm install --package-lock-only— one line changed, no dependency re-resolved.