SIGN IN SIGN UP

feat: packageExtensions for root-owned dependency manifest repairs (#9496)

Implements package manifest extensions per [RFC
#889](https://github.com/npm/rfcs/pull/889): a root-only
`packageExtensions` field in `package.json` that applies declarative
repairs to third-party dependency manifests **before** Arborist
finalizes the ideal tree. It lets a project add missing
`dependencies`/`optionalDependencies`, add or correct
`peerDependencies`, and mark peers optional via `peerDependenciesMeta`,
without forking and republishing a package.

```json
{
  "packageExtensions": {
    "broken-package@1": {
      "dependencies": { "missing-runtime-dep": "^2.0.0" }
    },
    "typescript-plugin@4.3.0": {
      "peerDependencies": { "typescript": ">=5" },
      "peerDependenciesMeta": { "typescript": { "optional": true } }
    }
  }
}
```

## Why

`install-strategy=linked` gives installs strong package boundaries,
which is also what makes adoption hard: a package only sees what it
actually declared, so one that worked under a hoisted layout because a
dependency happened to be hoisted above it can fail. A root-level
dependency masks this under hoisting but does not make the package
available inside the isolated boundary of the importer — the repair has
to be attached to the broken package's manifest before its edges are
resolved. This is the pre-resolution complement to `overrides` (which
needs an existing edge to retarget) and to [native dependency patching
#9439](https://github.com/npm/cli/pull/9439) (which edits package
contents after resolution).

## The field

Each key is a package selector: a name with an optional semver range
(`foo`, `foo@1`, `@scope/foo@^2.3.0`). Selectors match a candidate's own
manifest `name`/`version` (the underlying name for aliases) and reject
dist-tag, git, file, URL, and `npm:` specs. At most one selector may
match a candidate. Honored only in the root `package.json` (the
workspace root); the field in dependencies and non-root workspaces, and
selectors matching a workspace member, are ignored with a warning —
matching the root-authority model of `overrides`.

## Merge semantics

Only the four resolution-affecting fields may be extended.

- `dependencies`/`optionalDependencies` add a missing name only;
providing a name already declared in either field is an error (use
`overrides` to change a version), which also forbids moving a name
between the two.
- `peerDependencies` shallow-merges by name, replacing an existing
range.
- `peerDependenciesMeta` merges by name then key (e.g. add `optional:
true`); every meta entry must have a corresponding `peerDependencies`
entry.
- Deletion (`null`/`false`/`"-"`) is not supported.

The extension applies to a per-tree manifest copy: the shared
pacote/cache manifest is never mutated, the installed
`node_modules/<pkg>/package.json` is not rewritten, and
`bundleDependencies` is unchanged. `overrides` still controls the final
resolution target of an extension-created edge.

## Lockfile

The root entry stores a canonical `packageExtensionsHash`, and each
affected entry stores minimal provenance (`packageExtensionsApplied`);
effective dependency metadata is recorded as usual. Extension state
forces `lockfileVersion: 4` so older npm clients abort rather than
silently dropping the repaired graph. `npm install` re-resolves affected
packages when the rule set changes; `npm ci` validates the hash,
selector conflicts, and stale provenance before trusting the locked
metadata.

## Visibility

`npm explain` appends `(added by
packageExtensions["foo@1"].dependencies.bar)` to the edge; `npm ls`
annotates the node and `npm ls --json` includes
`packageExtensionsApplied`. Publishing a non-private package containing
the field warns that it does not affect consumers.

## Notes

- `lockfileVersion: 4` is shared with native dependency patching
([#9439](https://github.com/npm/cli/pull/9439)) as a common "older npm
must not silently drop this" tripwire; both bump only when their own
state is present. Whichever lands second should reuse the same
`maxLockfileVersion`/bump constants rather than introduce a competing
version.
- Opt-in and additive, so it can ship in a minor release.

## References

Implements https://github.com/npm/rfcs/pull/889
M
Manzoor Wani committed
ce7681fe7dbcc20abb5f1379558e14ddd069654f
Parent: 5ddf6cc
Committed by GitHub <noreply@github.com> on 6/18/2026, 5:54:56 PM