Context
#7613 added engine-strict=true to a root .npmrc, so npm install/npm ci now hard-fails on a Node
version outside engines.node. That closes the most common trigger (fresh clone, pulling main,
switching branches, CI-style setup) -- confirmed directly: npm install on Node 26 now fails with
EBADENGINE.
It does not close every path, though: engine-strict fires during npm's dependency-resolution step, not
on every subsequent npm run. If node_modules is already installed (built while on the pinned Node)
and someone simply switches their shell's active node afterward -- nvm use 26, a homebrew default
changing, etc. -- with no reinstall, every later npm run sails straight past engine-strict silently.
Confirmed directly: npm run engines-nvmrc:check succeeded on Node 26 despite engine-strict=true and a
tightened engines.node, because it never re-triggers npm's install-time check. That's uncomfortably
close to the actual mechanism that let the Node 26 jsdom/localStorage bug (#7592/#7597, #7612) go
unnoticed twice: confusing downstream test failures instead of one clear "wrong Node version" message.
Requirements
- Add a small
scripts/check-node-version.mjs that checks process.version against root
package.json's engines.node and fails with a clear, actionable message (not npm's more cryptic
EBADENGINE format) when they don't match.
- Wire it as a
pretest* hook (npm's standard pre-script convention) on the commands that actually run
vitest: test, test:ci, test:coverage, test:workers, ui:test -- so each fires the check
immediately, before doing any real work, regardless of node_modules' install state.
Deliverables
Test Coverage Requirements
scripts/** and test/** are outside Codecov's src/**-only include glob, so this owes no
patch-coverage number -- but the new script ships its own unit suite (satisfying/failing/no-engines-field
cases, a real-repo-state regression guard, and a subprocess smoke test for the passing case), matching
this repo's existing check-script test convention.
Expected Outcome
Running any of the guarded commands on a Node version outside .nvmrc's pin fails immediately and
clearly, whether or not node_modules was already installed under a different Node.
Links & Resources
Context
#7613 added
engine-strict=trueto a root.npmrc, sonpm install/npm cinow hard-fails on a Nodeversion outside
engines.node. That closes the most common trigger (fresh clone, pullingmain,switching branches, CI-style setup) -- confirmed directly:
npm installon Node 26 now fails withEBADENGINE.It does not close every path, though:
engine-strictfires during npm's dependency-resolution step, noton every subsequent
npm run. Ifnode_modulesis already installed (built while on the pinned Node)and someone simply switches their shell's active
nodeafterward --nvm use 26, a homebrew defaultchanging, etc. -- with no reinstall, every later
npm runsails straight pastengine-strictsilently.Confirmed directly:
npm run engines-nvmrc:checksucceeded on Node 26 despiteengine-strict=trueand atightened
engines.node, because it never re-triggers npm's install-time check. That's uncomfortablyclose to the actual mechanism that let the Node 26 jsdom/localStorage bug (#7592/#7597, #7612) go
unnoticed twice: confusing downstream test failures instead of one clear "wrong Node version" message.
Requirements
scripts/check-node-version.mjsthat checksprocess.versionagainst rootpackage.json'sengines.nodeand fails with a clear, actionable message (not npm's more crypticEBADENGINEformat) when they don't match.pretest*hook (npm's standard pre-script convention) on the commands that actually runvitest:
test,test:ci,test:coverage,test:workers,ui:test-- so each fires the checkimmediately, before doing any real work, regardless of
node_modules' install state.Deliverables
scripts/check-node-version.mjs(+.d.mts+ unit tests).pretest,pretest:ci,pretest:coverage,pretest:workers,preui:testwired inpackage.json.npm test,npm run test:ci, andnpm run ui:testeach failimmediately with the clear message, without running any real work first.
npm run test:cigreen on Node 22 (no regression for the compliant case).Test Coverage Requirements
scripts/**andtest/**are outside Codecov'ssrc/**-only include glob, so this owes nopatch-coverage number -- but the new script ships its own unit suite (satisfying/failing/no-engines-field
cases, a real-repo-state regression guard, and a subprocess smoke test for the passing case), matching
this repo's existing check-script test convention.
Expected Outcome
Running any of the guarded commands on a Node version outside
.nvmrc's pin fails immediately andclearly, whether or not
node_moduleswas already installed under a different Node.Links & Resources
engine-strict/engines.nodework this closes the remaining gap in