Skip to content

fix(arborist): skip lockfile entries for optional deps with incomplete manifests#9343

Open
ecanturk wants to merge 1 commit into
npm:latestfrom
ecanturk:fix/optional-missing-version
Open

fix(arborist): skip lockfile entries for optional deps with incomplete manifests#9343
ecanturk wants to merge 1 commit into
npm:latestfrom
ecanturk:fix/optional-missing-version

Conversation

@ecanturk
Copy link
Copy Markdown

What

When using a proxy/upstream registry (e.g. Azure Artifacts), npm 11 writes lockfile entries for platform-specific optional dependencies without version, resolved, or integrity fields. Subsequent npm ci fails with Invalid Version: errors.

Example broken lockfile entry:

"node_modules/@esbuild/aix-ppc64": {
  "optional": true
}

Why

#fetchManifest() in build-ideal-tree.js fetches manifests for ALL platform variants of optional dependencies. Proxy registries that haven't cached packages for non-current platforms return incomplete metadata (missing version field). npm creates a Node with empty pkg.version, and metaFromNode() writes {"optional": true} to the lockfile without version.

npm 10 never attempted to resolve non-current-platform variants, so this only affects npm 11+.

How

Two-layer defense:

  1. build-ideal-tree.js (#nodeFromSpec): When a registry manifest lacks a version field, treat it as an EINCOMPLETEMANIFEST load failure so that #pruneFailedOptional() marks it inert. Only applies to spec.registry specs (not file: dependencies which may legitimately omit version).

  2. shrinkwrap.js (commit()): Skip writing entries where the node is optional, has no version, and is not the root. This acts as a defense-in-depth layer.

Testing

  • Unit test added to workspaces/arborist/test/shrinkwrap.js — simulates proxy registry scenario where an optional dep node has no version
  • All existing tests pass (shrinkwrap.js 45/45, build-ideal-tree.js 114/114)
  • Manually verified with Azure Artifacts upstream proxy feed:
    • Before fix: 46 broken lockfile entries (optional deps without version)
    • After fix: 0 broken entries
    • Output matches npm 10 behavior

Closes #9342

…e manifests

When using a proxy/upstream registry (e.g. Azure Artifacts), fetching
manifests for platform-specific optional dependencies that the proxy
hasn't cached can return incomplete metadata (missing version field).

This caused npm to write lockfile entries like:
  "node_modules/@esbuild/aix-ppc64": { "optional": true }

without a version, resolved, or integrity field.  Subsequent
`npm ci` runs would fail with 'Invalid Version:' errors.

Fix:
1. build-ideal-tree: When a registry manifest lacks a version field,
   treat it as a load failure (EINCOMPLETEMANIFEST) so that
   #pruneFailedOptional() marks it inert. Only applies to registry
   specs (range/version/tag), not file: dependencies which may
   legitimately omit version.
2. shrinkwrap: In commit(), skip writing entries where the node is
   optional, has no version, and is not the root. This acts as a
   defense-in-depth layer.

Closes: npm#9342
@ecanturk ecanturk requested review from a team as code owners May 12, 2026 07:55
// the package for non-current platforms).
// Writing them without version produces invalid lockfile entries
// like {"optional": true} that cause "Invalid Version:" errors.
if (meta.optional && !meta.version && !node.isTop) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard is too broad, it'll drop optional deps without a version like legitimate file or directory deps that omit the version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] npm 11 writes lockfile entries without version for platform-specific optional deps when using a non-npmjs registry

2 participants