Vitest 5 upgrade with Node 22 CI migration and lockfile audit
Discovered from README cross-links · today
Bumped vitest to 5, moved the test CI job to Node 22, and verified the lockfile.
What they did
The author took over dependabot PR #101, which only bumped vitest from 4.1.12 to 5.0.5. Reading the lockfile's vitest entry (5.1.0) showed vitest 5 declares node ^22.12.0 || ^24.0.0 || >=26.0.0, while the only job that runs unit tests (ci-guardrails.yml) was on Node 20. So the change became three files in one commit: package.json (vitest ^4.1.11 -> ^5.0.5, plus a new engines field), package-lock.json regenerated with npx npm@11 install --package-lock-only --ignore-scripts, and ci-guardrails.yml line 27 node-version 20 -> 22. They then audited every file in .github/workflows with grep for vitest, npm test, npm run test, yarn/pnpm test and npx vitest|vite, and confirmed only ci-guardrails.yml runs vitest, so the other Node 20 jobs were deliberately left alone. They checked peer ranges from the lock (vite stays 6.4.5 inside vitest 5's peer range; @types/node ^26.6.6 is in range; better-auth's optional peer dedupes to vitest 5.1.0 with npm ls clean). They ran npm ci --legacy-peer-deps --ignore-scripts --dry-run on Node 20.20.2 for main and the branch to show the four remaining Node 20 jobs only emit EBADENGINE warnings (13 on main, 15 on the branch), not failures. Verification was run against the branch lockfile: npx vitest run gave 11 files / 174 tests passed on main with vitest 4.1.12 and Node 26.7.0, and the same 174 on the branch with vitest 5.1.0 on both Node 26.7.0 and Node 22.23.2, with --reporter=verbose confirming nothing was dropped. On Node 22.23.2 with the CI job's env vars and order they ran guardrails:check, typecheck, vitest, lint (0 errors, 2 pre-existing warnings in untouched files) and build, all passing. They also trial-merged the two open Stripe dependabot branches in a throwaway worktree to prove merge order does not matter, and documented 59 changed lock entries.
How it went
A single-commit branch (deps/vitest-5-node-22) with a thorough PR write-up: 174 tests pass on vitest 5 under both Node 22 and 26, typecheck, lint, build and guardrails pass on Node 22, npm ci exits 0 with no ERESOLVE, and npm audit totals are unchanged (25, no high or critical). The author explicitly flagged what they could not verify: the Vercel project's Node.js Version dashboard setting, and whether engines.node overrides it, because the web search tool was refused and AGENTS.md forbids agents running the Vercel workflows. A later update rebased onto the merged Stripe PR #108 and tightened engines.node to >=22.12 <23 after review.
Worth knowing
The Vercel Node version is a dashboard setting the author could not read, so the deploy-side effect of engines.node is unverified; the owner must confirm 22.x/24.x and check the preview build log. The real dependency floor is higher than the declared >=22.12: e2b, @e2b/code-interpreter and nested undici 8.12.2 declare >=22.19.0. Four other workflows remain on Node 20 and already run below what several dependencies declare. bun.lock was left untouched and is already out of step with package.json. The Turbopack dev-server smoke steps and ci-smoke.yml were not run locally. The branch was committed locally only with no upstream, so it must be pushed by name.
Wiring
Order matters: read vitest 5's declared engines from the installed package or lock entry first, because that determines both the engines field and which CI job must move. Edit package.json (vitest range plus engines), then regenerate package-lock.json with npm 11 in package-lock-only mode so no install scripts run. Grep the workflows to map which jobs actually invoke vitest before touching any node-version, so unrelated Node 20 jobs stay untouched. Peer compatibility is checked from the lock: vite must satisfy vitest 5's peer range (the existing overrides.vite ^6.4.2 does), @types/node must satisfy its range, and npm ls must show no invalid peers for better-auth. Verification runs against the branch lockfile with npm ci --ignore-scripts (and the CI flag --legacy-peer-deps) before running vitest on both Node majors, then the full CI step sequence on the target major. The Vercel side is out of band: vercel.json holds only crons, there is no .nvmrc or .vercel directory, so the Node version lives in the dashboard and must be confirmed by the owner against the preview build log.
Try it yourself
In this repo, upgrade vitest to v5 and make CI match its Node requirement. Steps: (1) read node_modules/vitest/package.json or the lockfile entry to get vitest 5's engines.node range; (2) bump devDependencies.vitest in package.json and add an engines.node field matching the strictest dependency floor you can find in the lock; (3) regenerate the lockfile with npx npm@11 install --package-lock-only --ignore-scripts; (4) grep -rn 'vitest|npm test|npm run test|npx vitest' .github scripts package.json to find every workflow that actually runs tests, and bump only those jobs' node-version to 22; (5) run npx vitest run on both the old and new Node major and compare file/test counts with --reporter=verbose; (6) run the CI job's other steps (guardrails, typecheck, lint, build) in the same order with the same env vars; (7) run npm ci --ignore-scripts and npm ci --legacy-peer-deps --ignore-scripts to confirm no ERESOLVE; (8) list every lock entry that changed and say which are vitest's own deps versus unrelated drift. Report anything you could not verify (e.g. hosting provider Node settings) as owner action items instead of asserting it.