groundy
developer tools

NPM v12 Breaking Changes: Auditing Your Lockfiles Before the Upgrade

npm v12 removes npm-shrinkwrap.json, reshapes JSON output from view/pack/publish, and deletes four CLI commands. An eight-step audit checklist to run before upgrading.

10 min · · · 5 sources ↓

npm v12.0.0-pre.0.0 shipped on May 20, 2026 with ten documented breaking changes. The highest-impact ones are silent: lockfiles that stop resolving, JSON payloads that change shape, and CI commands that simply disappear. Teams running npm v11.16.0 (the current stable, released May 27) have a window to audit before v12 lands in Node LTS bundles. That window is narrower than the changelog makes it sound.

What Changed: The Ten Breaking Changes

The npm v12 pre-release changelog lists ten breaking changes. Not all of them carry the same blast radius. Here is the full list, ranked roughly by impact on a typical CI pipeline:

  1. npm-shrinkwrap.json no longer loaded or honored. Neither at the project root nor inside dependency tarballs.
  2. npm view --json always returns an array, breaking scripts that assumed a single-object return for single-version packages.
  3. npm pack and npm publish JSON output reformatted to a consistent schema, invalidating any parser keyed to the v11 shape.
  4. npm pkg output no longer forced to JSON. Single-value queries return plain strings instead of JSON-wrapped output, breaking any parser that expects valid JSON from every call.
  5. adduser command removed entirely. Authentication flows must go through npm login with an account created on the website.
  6. star, stars, and unstar commands removed. No replacement CLI path.
  7. whichnode path resolution dropped. npm now relies solely on process.execPath, which behaves differently when Node is invoked through a PATH symlink.
  8. Man pages no longer registered on global install. man npm-install stops working; npm help install still works.
  9. npm sbom --sbom-format=cyclonedx name field changed. The name, bom-ref, and purl of the root component and aliased dependencies now come from package.json instead of the on-disk directory name.
  10. Twitter and Freenode profile fields removed. npm profile no longer accepts these fields; stored values are dropped.

The rest of this article walks through the four changes most likely to break existing pipelines, with a concrete audit step for each.

The Lockfile Landmine: npm-shrinkwrap.json Is Gone

This is the highest-impact change for any team that has been publishing packages since before npm v5. npm-shrinkwrap.json was the original lockfile mechanism. It persisted in enterprise repos and published tarballs long after package-lock.json became the default, partly because it was the only way to ship a locked dependency tree inside a published package.

In v12, npm no longer reads npm-shrinkwrap.json at all: not at the project root, not inside dependency tarballs. The two migration paths the changelog documents are renaming it to package-lock.json, or using bundleDependencies to ship a locked dependency tree instead.

The danger here is not a loud failure. It is a silent one. If your published package ships a npm-shrinkwrap.json and no package-lock.json, a v12 consumer will resolve dependencies freshly against the registry instead of using the pinned versions. In a deterministic-build pipeline, that is the thing you were trying to avoid.

Audit step: Run find . -name "npm-shrinkwrap.json" across every repo and every published tarball in your internal registry. If you find any, plan the rename to package-lock.json or the bundleDependencies migration before touching v12.

CI Scripts That Will Break on First v12 Install

Three of the ten breaking changes produce immediate CI failures rather than silent drift.

The node resolution path

npm v12 drops whichnode, the internal mechanism that searched for the Node binary along the PATH. The replacement, process.execPath, resolves to whatever binary is executing the current npm process. This is fine in most cases. It breaks when your CI invokes npm through a wrapper script or version manager that symlinks Node into the PATH but runs npm under a different Node binary.

Teams using nvm or similar version managers should verify that process.execPath inside npm points to the same binary that which node resolves to. If they diverge, lifecycle scripts (postinstall, prepare) may execute under the wrong Node version.

Audit step: Add a debug step to your CI that prints node -e "console.log(process.execPath)" alongside which node. If the paths differ, you need to fix the resolution before upgrading.

Man page registration

npm v12 no longer registers man pages with the system during global installs. man npm-install stops working; npm help install continues to work because it reads from npm’s own bundled docs, not the system manpath.

This will not break builds. It will break documentation lookup workflows, and it will generate support tickets if your team relies on man pages for on-call runbooks or CI script references.

Audit step: Grep your CI scripts and internal docs for man npm invocations. Replace them with npm help equivalents.

The JSON Output Shape-Shift: view, pack, and publish

This is the change most likely to surface as a runtime error in CI scripts that parse npm output programmatically.

npm view --json always returns an array

In v11, npm view --json returns a single object when a package has exactly one version, and an array when it has multiple. This is a well-known footgun, and plenty of scripts paper over it with defensive parsing. In v12, the output is always an array, per the npm v12 changelog.

There is no opt-out flag. Any script that does const data = JSON.parse(output) and then accesses data.version without checking Array.isArray(data) will break on every call, not just the multi-version case.

Audit step: Search your CI codebase for invocations of npm view piped through JSON.parse. For each one, verify that the consuming code handles an array. A quick test:

Terminal window
npm view express --json | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const j=JSON.parse(d);console.log(Array.isArray(j)?'array':'object')})"

If this prints object on v11, that script will break on v12.

npm pack and npm publish JSON output reformatted

The --json output for both commands has been restructured in v12 for consistency. The changelog does not document the exact field-level diff, but the stated goal is a unified schema between the two commands. Any CI step that parses these outputs to extract tarball filenames, integrity hashes, or version numbers should be treated as broken until tested against v12.

Audit step: Run npm pack --json and npm publish --dry-run --json under both v11 and v12 (side-by-side, using nvm or a similar version switcher). Diff the JSON. Update parsers accordingly.

Removed Commands: adduser, star, stars, unstar

Four commands are gone in v12: adduser, star, stars, and unstar.

The adduser removal affects CI pipelines that automate registry authentication. The replacement is to create accounts on the npm website and use npm login on the CLI. For automation, this typically means switching to token-based auth via .npmrc entries (//registry.npmjs.org/:_authToken) rather than interactive login flows, which is what most production CI systems already do. Legacy pipelines that still call npm adduser in a provisioning step will fail.

The star/unstar removal has a narrower blast radius. These were niche commands for bookmarking packages on the registry. If any CI script calls them (unlikely but possible in custom release workflows), those calls will produce a “command not found” error.

Audit step:

Terminal window
grep -r "npm adduser\|npm star\|npm stars\|npm unstar" . --include="*.sh" --include="*.yml" --include="*.yaml" --include="*.json" --include="Dockerfile*"

If you get hits, replace adduser with token-based auth and remove any star/unstar usage.

Security Hardening Preview: allow-* Configs in v11.16.0

npm v11.16.0 shipped Phase 1 of the allowScripts opt-in policy and four new dependency-source restriction configs, documented in the npm v11 config reference:

  • allow-scripts: comma-separated package allowlist for install-time lifecycle scripts
  • allow-directory: restricts file: dependencies to specific directories (all|none|root)
  • allow-file: controls whether file: protocol dependencies are resolved at all
  • allow-git: restricts git: and git+https: dependencies
  • allow-remote: controls https: tarball URLs in dependencies

These configs shipped in v11.16.0 (May 27, 2026) and can be set in .npmrc without upgrading to the v12 pre-release. They give teams a way to lock down dependency sources and lifecycle scripts on the current stable line, before v12 introduces whatever its full enforcement model looks like.

The practical move: set allow-directory=root and allow-git=none in your project .npmrc today. If any dependency breaks, you have found a supply-chain exposure you should evaluate independently of the v12 upgrade. Running these restrictions on v11 also means you can test your team’s tolerance for the tighter posture in a version you can roll back from trivially.

Pre-Upgrade Audit Checklist

Each step below maps to a specific breaking change from the v12 changelog. Run these on v11 before upgrading.

StepWhat to checkCommandBreaking change
1Find shrinkwrap filesfind . -name "npm-shrinkwrap.json"#1: shrinkwrap removed
2Test npm view JSON parsingnpm view <pkg> --json | node -e "..."#2: array-always
3Diff pack/publish JSONnpm pack --json on v11 vs v12#3: output reformat
4Grep for removed commandsgrep -r "npm adduser|npm star" .#5-6: command removal
5Verify node resolution pathnode -e "console.log(process.execPath)" vs which node#7: whichnode drop
6Grep for man page callsgrep -r "man npm" .#8: man page removal
7Enable allow-* configsSet allow-directory=root, allow-git=none in .npmrcSecurity preview
8Pin CI npm versionSet explicit npm-version in CI configGeneral

Steps 1 through 3 are the ones most likely to produce silent failures (wrong dependency resolution, wrong JSON shape, wrong tarball metadata). Steps 4 and 5 produce loud errors that are easier to catch but still break builds.

What to Watch in Subsequent Pre-Releases

npm v12.0.0-pre.0.0 is the first pre-release. The changelog notes ten breaking changes today, but additional ones may land before the stable tag. Two things to track specifically:

The shrinkwrap migration path. The current guidance is rename or use bundleDependencies. Neither is automated. Watch for a migration tool or a deprecation warning in later pre-releases.

The allow-scripts Phase 2 surface. The Phase 1 implementation in v11 is an allowlist. Phase 2 in v12 may introduce a denylist, default-deny behavior, or config syntax changes. The v11 config docs are the current reference, but do not assume they describe the v12 model.

The npm releases page is the authoritative source for changes as they land. Monitor it if you are tracking toward the stable release, because the pre-release numbering (pre.0.0) signals that the interface is not frozen yet.

Frequently Asked Questions

What does the npm pkg plain-string output change break specifically?

Breaking change #4 is the only one that can fail on every invocation. In v11, npm pkg get version returns {"version":"1.2.3"} (a JSON object). In v12, it returns the bare string "1.2.3". Scripts that pipe this through JSON.parse() and then access .version will throw a TypeError, because the parsed result is a string, not an object. Unlike the npm view array change, there is no defensive Array.isArray() check you can add: you must restructure how the consuming code reads the output entirely.

How do npm’s allow-* configs compare to pnpm’s supply-chain controls?

pnpm has defaulted to strict dependency isolation since its early releases: packages can only import what they explicitly declare, and lifecycle scripts run in isolated contexts. Yarn offers similar gating through enableScripts and related yarnrc.yml flags. npm’s v11 allow-* configs are catching up to that posture but remain opt-in rather than default-deny, so teams must explicitly configure each restriction in .npmrc instead of getting protection out of the box.

Which breaking change affects CycloneDX SBOM compliance tracking?

Breaking change #9 re-roots the name, bom-ref, and purl fields in npm sbom --sbom-format=cyclonedx output to come from package.json rather than the on-disk directory name. In monorepo setups where the directory is packages/foo/ but the package name is @scope/foo, every SBOM identifier will change. Compliance systems that key off bom-ref to track vulnerabilities across releases will see the same package appear as a new entry, breaking historical linkage.

Why does the pre.0.0 version number matter for audit planning?

The pre.0.0 suffix follows semver’s pre-release convention: the public API is not considered stable, and npm can add, remove, or modify breaking changes before the first stable tag. Any audit run against this build may need to be repeated for each subsequent pre-release, because the ten breaking changes documented today are a floor, not a ceiling. Teams planning migration timelines should budget for at least one re-audit when the release candidate phase begins.

sources · 5 cited

  1. npm CLI Releases primary accessed 2026-06-10
  2. Node.js Downloads vendor accessed 2026-06-10
  3. nvm: Node Version Manager community accessed 2026-06-10
  4. npm Registry vendor accessed 2026-06-10
  5. npm v11 Config Documentation vendor accessed 2026-06-10