-
Baseline before touching anything. Run npm test and npm run build, and record the suite/test/snapshot counts. You can't call an upgrade safe without a before to compare against, and you can't reconstruct one once the tree has moved. If the baseline is already failing, stop and surface that rather than upgrading on top of it.
-
Make one kind of change. Don't combine these in a single pass:
- Advisories →
npm audit fix
- Move within existing ranges →
npm update
- Major bump →
npm i <pkg>@<version>, one package at a time
Install with npm ci. Never rm package-lock.json && npm install — that re-resolves the entire tree unaudited and discards a known-good state to solve a problem you haven't diagnosed yet.
-
Confirm audit fix stayed in the lockfile. git diff package.json must come back empty. If a declared range moved, audit fix escalated into something bigger than what was asked for. Stop and report it.
-
Review what moved. Read git diff package-lock.json. For each package: is the maintainer the established owner, and did it gain an install script?
npm view <pkg>@<ver> --json
npm audit signatures
Install scripts are the main way a compromised package executes on this machine. npm blocks them and lists them during install. One blocked package, fsevents, is expected — it's optional, macOS-only, and ships a prebuilt binary, so the blocked build is unnecessary. Leave it blocked; don't run npm approve-scripts to silence it. Anything else in that list is worth investigating before accepting the upgrade.
-
Verify against the baseline. Re-run npm test and npm run build. The counts must match step 1 exactly. "Still green" isn't the bar — a silently skipped suite is also green.
-
Report. What moved and why, what you checked in step 4, and the before/after numbers. Lead with anything unexpected rather than burying it under a green result.
Don't relitigate these mid-upgrade.