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

@ecanturk ecanturk requested review from a team as code owners May 12, 2026 07:55
Comment thread workspaces/arborist/lib/shrinkwrap.js Outdated
@ecanturk ecanturk force-pushed the fix/optional-missing-version branch 3 times, most recently from e988ac0 to 4ce71e2 Compare May 14, 2026 06:12
…e manifests

When using a proxy/upstream registry, fetching manifests for platform-specific
optional dependencies that the proxy has not 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 (both layers are independently needed):

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, not file: dependencies which may
   legitimately omit version.

2. shrinkwrap: In commit(), inventory iteration does not filter inert nodes, so
   a separate guard skips writing entries where the node is optional, has no
   version, is not the root, and has a registry-resolved URL (https://). Local
   deps (file: or resolved=null) are preserved.

Closes: npm#9342
@ecanturk ecanturk force-pushed the fix/optional-missing-version branch from 4ce71e2 to 8f34f13 Compare May 14, 2026 06:41
@ecanturk ecanturk requested a review from owlstronaut May 14, 2026 07:08
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