typescript-docs-node-engines
The drift and the prescribed fix, applied to a reproduction repo (/tmp/node-engines-repro):
Drift found: README.md said "Node.js 20 or newer", AGENTS.md said "Node.js 22+", and package.json had no engines field at all (verified — lockfile root entry had no engines either).
1. Unify docs at 22+ — README edited; AGENTS already aligned:
## Requirements
-- **Node.js 20 or newer** is required to build and run this project.
+- **Node.js 22 or newer** is required to build and run this project.
AGENTS.md already said Node.js 22+ — no change needed, docs now agree.
2. Add engines to package.json:
"type": "module",
+ "engines": {
+ "node": ">=22"
+ },
"scripts": {
3. Sync the lockfile (the PM-prescribed command):
npm install --package-lock-only --ignore-scripts
This adds engines to the lockfile root entry only — it touches no dependency tree:
"packages": {
"": {
"name": "node-engines-repro",
"version": "1.0.0",
+ "engines": {
+ "node": ">=22"
+ },
"devDependencies": { ... }
4. CI 20.x job — no change needed: it stays a warning. The repo has no engine-strict in any .npmrc/CI config, so the 20.x job emits EBADENGINE but exits 0.
Verification performed on Node 22.22.3 / npm 10.9.8 (with a `NODE_OPTIONS` preload shim overriding `process.version` to `v20.19.0` to simulate the CI Node 20.x runtime — npm's engine check reads `process.version` in-process, so the real warning path is exercised).
| # | Check | Result |
|---|-------|--------|
| 1 | Pre-fix drift grep: README `20 or newer` vs AGENTS `22+` vs no `engines` in package.json/lockfile | Drift confirmed |
| 2 | Post-fix docs grep: residual `Node.js 20`/`Node 20` in README/AGENTS | none — both say 22+ |
| 3 | `require('./package.json').engines` | `{"node":">=22"}` |
| 4 | Lockfile root entry includes `"engines": {"node": ">=22"}` | present, deps unchanged |
| 5 | `npx tsc --noEmit` on Node 22 | passes |
| 6 | **Test A** — simulated Node 20, default config (the CI 20.x job): `npm install --package-lock-only` | `npm warn EBADENGINE … required: {node: '>=22'}, current: {node: 'v20.19.0'}` → **exit 0** |
| 7 | **Test B** — simulated Node 20 with `--engine-strict` (control): same command | `npm error code EBADENGINE … Required: {"node":">=22"}` → **exit 1** |
| 8 | **Test C** — real Node 22 install | no EBADENGINE, exit 0 |
| 9 | **Test D** — `npm ci --ignore-scripts` on Node 22 (CI-style) | `added 3 packages`, exit 0 |
| 10 | `grep -rIn "engine-strict"` across yml/yaml/.npmrc/json | none — confirms CI cannot hard-fail on engines |
**Edge cases tested:** exact boundary Node 20.19.0 vs the `>=22` range (Test A/B); warn-vs-error depends solely on `engine-strict` presence (Tests A/B prove the contrast); `npm install --package-lock-only` (sync command) vs `npm ci` (CI install) both behave correctly; lockfile regen is non-destructive to the dependency graph; docs consistency check catches residual "20" mentions. Caveat: Test A/B used a version shim rather than a real Node 20 binary — the engines gate itself is genuinely exercised, but a true Node 20 job in CI remains the ultimate confirmation.
---{"model": "deepseek-v4-flash", "problem_class": "typescript-docs-node-engines", "result": "passed", "tests": 10}