After any npm operation that changes the lockfile, run these two validation checks before touching the build or committing:
a) Lockfile self-consistency
Run npm ci --dry-run. If it fails with "Missing: @ from lock file", the lockfile is inconsistent and must be fixed before proceeding. Do not commit a lockfile that fails npm ci.
b) Silent dedup detection
If the lockfile shrank significantly (many deletions, few or no insertions), nested package entries may have been removed by npm's deduplication. Removed nested entries are usually fine — unless a package's declared dependency range is no longer satisfied by the hoisted version.
Run: git diff -- package-lock.json | grep "^-" | grep '"node_modules/.*/node_modules/' | grep -o '"[^"]*node_modules/[^"]*"' | sort -u
For each removed nested path (e.g. node_modules/@eslint/config-array/node_modules/minimatch), extract the parent package and transitive package name. Then check:
npm ls <transitive-package> 2>&1 | grep -E "UNMET|invalid|extraneous"
Also inspect the parent's own package.json on disk (node_modules/<parent>/package.json) to find its declared range for the transitive dep. If the hoisted version does NOT satisfy that range (different major), the dedup is silent but incorrect.
Fix: Add a scoped override in the directory's package.json to make the upgrade intentional:
"overrides": {
"<parent-package>": {
"<transitive-package>": "^<hoisted-safe-version>"
}
}
Then run npm install and re-run npm ci --dry-run to confirm.
c) Jest peer alignment (authui only)
After any npm operation in authui/, run:
npm ls jest-environment-jsdom jest-runtime 2>&1 | grep -E "jest-environment-jsdom|jest-runtime"
Both must resolve to the same major.minor. If jest-runtime is at 30.4.x but jest-environment-jsdom is still pinned to ^30.2.0 in package.json, update the pin to ^30.4.0 (matching the runtime minor), then run npm install. Misalignment causes a runtime error (clearMocksOnScope is not a function) that only surfaces when tests run — not during install.