Skip to content

[pull] latest from npm:latest#14

Open
pull[bot] wants to merge 943 commits into
Graybar-codespace:latestfrom
npm:latest
Open

[pull] latest from npm:latest#14
pull[bot] wants to merge 943 commits into
Graybar-codespace:latestfrom
npm:latest

Conversation

@pull

@pull pull Bot commented Oct 19, 2024

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot]

Can you help keep this open source service alive? 💖 Please sponsor : )

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The pull request #14 has too many files changed.

We can only review pull requests with up to 300 changed files, and this pull request has 587.

@pull pull Bot added the ⤵️ pull label Oct 19, 2024
@owlstronaut owlstronaut force-pushed the latest branch 2 times, most recently from 85ec0c9 to 26b6454 Compare March 27, 2025 18:03
manzoorwanijk and others added 26 commits March 3, 2026 10:29
#9051)

When `legacyPeerDeps` is enabled and a workspace has a peer dependency
on another workspace, `node.resolve()` in `assignCommonProperties`
returns the Link node (at `node_modules/<ws>`) rather than its target
(at `packages/<ws>`). The normal edge-based path correctly unwraps via
`e.to.target`, but the legacyPeerDeps fallback pushed the raw resolved
node directly.

This caused `workspaceProxy` to be called with the Link (a different
object from the fsChild), creating a second workspace proxy with
`localLocation="node_modules/<ws>"`. Store links for that workspace's
deps were then placed at `node_modules/<ws>/node_modules/<dep>` instead
of `packages/<ws>/node_modules/<dep>`, racing with the workspace symlink
at `node_modules/<ws>` and hitting EEXIST on subsequent installs.

Fix: use `resolved.target` to unwrap Link nodes, consistent with the
edge-based path.

## References

Fixes #9050
This started as a removal of the Symbols left in isolated reifier and
ended as a bit of a cleanup/refactor.

Inline functions were moved to either static ones or class functions,
forEach was changes to for loops where possible, comments were
unwrapped, and more.
We build a virtual tree from the lockfile before we build the ideal tree
so that we can compare the two and error if package.json differs from
what was used to build the lockfile.

The virtual tree includes optional deps in it that would otherwise be
ignored when building the ideal tree. In order not to have `npm ci` fail
in this situation we need to build the virtual tree separately, and
start clean when building the ideal tree.

This also brings in a few linting fixups.
owlstronaut and others added 30 commits June 18, 2026 07:04
`npm audit fix` left a package on its vulnerable version, with no
warning, when the only patched version was newer than the
`min-release-age`/`before` cutoff. It now warns which fix was blocked
and exits non-zero. Honors `min-release-age-exclude`.
BREAKING CHANGE: The default license for `npm init` has been changed from "ISC" to an empty string. If not set, the license field will be omitted from new packages.
…9439)

Implements native dependency patching per [RFC
#862](npm/rfcs#862): a first-class way to apply
small, local modifications to an installed dependency and have them
re-applied automatically on every install, with no external tooling or
postinstall scripts.

Patches are declared in a new `patchedDependencies` field of the root
`package.json`, stored as plain unified diffs under `patches/`, and
recorded with a content hash in `package-lock.json`. Because the patch
is applied during the install itself, it works for transitive
dependencies, across every `install-strategy`, and is **not** disabled
by `--ignore-scripts`.

## The `npm patch` command

A new command with five subcommands (and a bare `npm patch <pkg>`
shorthand for `add`):

- **`npm patch add <pkg>[@<version>]`** — extracts a clean copy of the
resolved registry tarball into a temp directory outside `node_modules`
and prints the path to edit. Ambiguous when multiple versions are
installed; the error lists the exact selectors to retry with.
- **`npm patch commit <edit-dir>`** — diffs the edited directory against
a fresh copy of the original tarball, writes
`<patches-dir>/<name>@<version>.patch`, adds the `patchedDependencies`
entry, and reifies to apply the patch and record its integrity in the
lockfile. `package.json` is excluded from the diff — Arborist resolves
the pre-patch manifest, so a patched manifest would change
resolution-affecting fields on disk without being honored (silent
partial application); `commit` warns when an edit only touches it.
- **`npm patch update <pkg>[@<old-version>] [--to <new-version>]`** —
rebases an existing patch onto a new version. It reads the target from
`--to` or the lockfile, 3-way-merges the existing patch onto the new
tarball in a throwaway git repo, and rewrites `package.json` +
`package-lock.json` **without touching `node_modules`** (so it works
from a failed-install state). On conflict it leaves an edit dir with
`<<<<<<<` markers, finalized by `npm patch commit`. Exact selectors are
renamed; range/name-only selectors gain a new exact entry and keep the
old one while it still wins another installed node.
- **`npm patch ls`** — lists registered patches and how many installed
nodes each matches (flagging overlapping range selectors that conflict
on a node).
- **`npm patch rm <pkg>[@<version>]`** — removes the matching entries,
deletes the patch file when no other entry references it, and reifies to
revert the files.

## Install-time apply pipeline

Patch resolution and application live in Arborist so every install path
honors them:

- **`resolvePatchedDependencies`** resolves the root
`patchedDependencies` map against the ideal tree, attaching
`node.patched = { path, integrity }` to each matched node. Selector
precedence is exact > range-subset > name-only, with ambiguous
overlapping ranges surfaced as a hard error.
- **reify** applies the diff after extraction and records the patched
integrity in the lockfile. `diff.js` forces re-extraction when a node's
patch integrity changes, and re-extracts to revert when a
previously-patched node loses its selector (`patchRemoved`).
- **`install-strategy=linked`** is supported via a content-addressed
side-store: the store key is suffixed with the patch identity (`+patch`)
so a patched and unpatched copy of the same version coexist without
collision. A failed patch under linked strategy is always a hard error
(the side-store cannot represent unpatched contents at a patched key
without later installs silently trusting it).

## Lockfile

Patches require `lockfileVersion: 4` so that older npm clients abort
rather than silently installing unpatched code. When any node is
patched, npm writes version 4 and **warns** if this upgrades a lower
pinned `lockfile-version` (the safety gate cannot be honored otherwise).
`npm ci` revalidates each patch's existence and integrity against the
lockfile before installing.

## Failure modes

By default any patch problem is a hard error that aborts the install: a
patch that fails to apply, a registered patch that matches no installed
package, a missing patch file, or a patch whose hash does not match the
lockfile. Two **CLI-only** relax flags cover one-off cases —
`--allow-unused-patches` and `--ignore-patch-failures` — and are
rejected in `npm ci` and when set anywhere other than the command line.

## Non-registry dependencies

Patches need a stable registry tarball as their baseline, so a
dependency reached through a non-registry consumer edge (`file:`,
`git:`, `http(s):`) is rejected with `EPATCHNONREGISTRY`, both by `npm
patch add` and at install time. The check is edge-based (the consuming
spec's type), not node-based, so it does not falsely reject edgeless
nodes such as linked-store entries or extraneous installs, which are
still registry deps. `npm:` registry aliases are correctly classified as
registry deps and are supported by the install engine; the `npm patch
add <alias>` ergonomics will land in a fast-follow.

## Publish / pack

`patchedDependencies` is stripped from the published **registry
manifest** (libnpmpublish) so the field never leaks to the packument.
Stripping it from the **tarball's own `package.json`** and excluding the
`patches/` directory from the tarball is a coordinated follow-up in
`pacote` + `npm-packlist` (those packages own the packed file list and
the manifest written into the tarball, neither editable from the CLI) —
see Follow-up work.

## Other surfaces

- `npm ls` annotates patched dependencies in its output.
- New config: `patches-dir`, `edit-dir`, `ignore-existing`,
`keep-edit-dir`, plus the two relax flags.
- New `npm-patch` man page and nav entry.

## Tests

Unit and integration coverage for every subcommand (including `update`'s
clean rebase, conflict→commit, and selector-rename/range-fork paths),
the apply pipeline, selector matching, linked-strategy apply/removal,
lockfile validation, publish stripping, and the relax flags. Arborist
and CLI suites pass at 100% coverage.

## Follow-up work

A few additive pieces are deliberately deferred — nothing in this PR
depends on them.

- **Tarball-side strip for publish/pack** — stripping
`patchedDependencies` from the tarball's own `package.json` and
excluding the `patches/` directory from the published tarball. This
can't be done in the CLI: the tarball's file list and manifest come from
`pacote` (packs the raw on-disk files) and `npm-packlist`, so it needs
coordinated changes there. Raised in the RFC review; the
registry-manifest strip in this PR already prevents the field from being
honored or appearing in the packument.

- **`npm patch add <alias>` ergonomics for `npm:` registry aliases** —
the install engine already treats `npm:` aliases as registry
dependencies and applies a hand-written `<alias>@<version>` selector
correctly today. What remains is the `add`/`commit` convenience:
resolving the alias to its real `name@version` tarball as the baseline
and keying the written selector on the alias name. Currently `npm patch
add <alias>` resolves the alias name as a real package and fails.

- **Binary files** — patches are unified text diffs, so binary files
(images, wasm, native addons) cannot be patched. This is a limitation of
the whole feature (shared with `patch-package`), not a regression; a
binary-aware path could be added later.

## References

Implements npm/rfcs#862
…izes (#9567)

A conflicted `npm patch update` leaves an edit dir and a
`.npm-patch-update.json` marker that the finalizing `npm patch commit`
reads to finish the update — a metadata-only finalize that drops the
renamed-from selector and tolerates the new version not being installed
yet.

`commit()` deleted that marker before checking whether the edit dir
produced a diff. So a first commit that did no net work — the conflict
resolved to the new version verbatim, giving "nothing to commit" —
consumed the marker. A corrected retry then found none, ran a full
reify, and failed `EPATCHUNUSED` (the rebased-to version isn't
installed), leaving both the old and new selector in the manifest. The
same loss happened on any non-finalizing path (e.g. the `EPATCHUNSAFE`
throw).

## Fix

The marker was deleted eagerly only to keep it out of the generated
patch. Instead, keep it and teach `diffDirs` to skip it:

- `diffDirs(originalDir, editedDir, ignore = new Set())` skips a set of
root-relative filenames, like it already skips the root `package.json`.
- `commit()` no longer deletes the marker — it reads/parses it (still
throwing `EPATCHBADMARKER` on bad JSON before any diff), and passes `new
Set([UPDATE_MARKER])` to `diffDirs`.

The marker now survives any non-finalizing path, so a corrected retry
still finalizes. A successful commit removes the whole edit dir (unless
`--keep-edit-dir`), so nothing lingers.

## References

Fixes #9566
Follow-up to #9439 (native dependency patching).
…9496)

Implements package manifest extensions per [RFC
#889](npm/rfcs#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](#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](#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 npm/rfcs#889
…9569)

Follow-up of #9496

Under `install-strategy=linked`, a root `packageExtensions` rule that
adds a missing dependency installs and works at runtime, but every
command that reads the actual tree loses the edge:

- `npm ls --all --json` omits the extension-created dependency and its
provenance.
- `npm explain <dep>` fails with `No dependencies found matching <dep>`.
- `npm patch add <dep>` fails with `EPATCHNOTINSTALLED`.

Hoisted installs and normally-declared transitive deps are unaffected.

## References

Fixes #9568
…ch (#9570)

A workspace `packageExtensions` warning was printed twice per matching
workspace. A workspace appears in the inventory as two `isWorkspace`
nodes — the Link and its target — and `#warnWorkspacePackageExtensions`
warned for both. The fix skips the link (`node.isLink`) so each
workspace is warned once via its target node; the selector/`wouldMatch`
checks are otherwise unchanged.

## References

Follow up of #9496
#9576)

When `--ignore-patch-failures` skips a broken patch, the package
installs unpatched and the lockfile records no `patched` entry, but
`package.json` still declares it. A later `npm ci` then fails with a
generic out-of-sync `EUSAGE` whose "run `npm install`" advice is wrong
(a plain install just re-fails with `EPATCHFAILED`).

The state can't be reconciled automatically — recording the patch would
be a lie, and rewriting `package.json` would discard the user's intent.
So this PR makes the out-of-sync state self-explanatory:

- `reify.js`: when a patch is skipped, warn that the lockfile is now out
of sync with `package.json` and `npm ci` will fail until the patch is
fixed or its `patchedDependencies` entry is removed.
- `validate-lockfile.js`: make the patch mismatch error direction-aware.
A patch declared in `package.json` but missing from the lockfile names
`--ignore-patch-failures` and gives remediation; a patch in the lockfile
that `package.json` dropped says so; integrity/path drift keeps the
existing message. This flows into the `npm ci` `EUSAGE`.

## References

Fixes #9573
…ion (#9575)

`npm patch update <pkg> --to <version>` finalizes with
`allowUnusedPatches: true`, so a `--to` version absent from the tree is
recorded silently with only a success line. The next plain `npm install`
then fails with `EPATCHUNUSED`, far from the command that caused it.

## Fix

`#resolveNewVersion` now warns when the validated `--to` version is not
in the installed tree. It stays silent when the lockfile cannot be read
(the power-user upgrade path); the command still succeeds.

## References

Fixes #9571
Follow-up to #9439
…CHUNUSED (#9574)

`npm uninstall <pkg>` failed with `EPATCHUNUSED` when the package had a
registered patch, because uninstall left the now-orphaned
`patchedDependencies` entry in `package.json`.
This also wedged the project: every subsequent `npm install` failed the
same way until the user manually ran `npm patch rm`.

`resolvePatchedDependencies` now receives the `rm` list from
`buildIdealTree` and drops an unused selector whose package is being
removed, and `reify` persists that removal to `package.json`.
Selectors unused for any other reason still throw `EPATCHUNUSED`, and a
package that survives as a transitive dependency keeps its patch.

## References

<!-- List any relevant issues, pull requests, or external references -->
Fixes #9572
…ry tarballs (#9550)

With `allow-remote=none` (the default on npm 12) or `allow-remote=root`,
a `npm install` fails with `EALLOWREMOTE` on ordinary registry
dependencies when the configured registry origin differs from the
lockfile `resolved` origin.

```
npm error code EALLOWREMOTE
npm error Fetching packages of type "remote" have been disabled
```

This is the common proxy/mirror case: a committed `package-lock.json`
whose `resolved` URLs point to `https://registry.npmjs.org/...`, while
the machine (or CI) is configured to use a private registry proxy/mirror
with a different origin.
It affects both the hoisted and the linked install strategy.

## Why

When extracting a registry-resolved package, reify hands pacote a
`name@URL` spec, which pacote re-parses as `type=remote` and gates with
allow-remote.
To avoid mis-firing on registry tarballs, `#isRegistryResolvedTarball`
exempts them — but it compared the raw lockfile `resolved` URL against
the configured registry origin.
With a proxy/mirror configured, `resolved` is the canonical
`registry.npmjs.org` URL while the configured registry is the proxy, so
the origins never matched, the exemption returned `false`, and the
registry tarball was rejected as remote.

Crucially, reify already fetches a different URL than the raw
`resolved`: `#registryResolved` applies `replace-registry-host` (default
`npmjs`), rewriting the `registry.npmjs.org` host to the configured
registry while preserving the path.
So npm fetches the tarball from the proxy correctly; only the
allow-remote check was evaluating the wrong (pre-rewrite) URL.

## How

Evaluate the effective URL npm actually fetches, not the raw lockfile
value.

`#isRegistryResolvedTarball` now parses
`this.#registryResolved(node.resolved)` — the host-rewritten URL —
before the same origin + registry-path-prefix comparison.
After rewriting, a public-registry-pinned tarball resolves to the
configured registry and is correctly recognized as registry-mediated.

The existing security boundary is preserved: under the default
`replace-registry-host`, a same-origin tarball pointing outside the
registry path is not rewritten and is still rejected, and a genuinely
URL-declared dependency still fails the `node.isRegistryDependency`
guard.
Under `replace-registry-host=always`, every tarball is routed through
the configured registry, so registry dependencies are no longer treated
as remote — consistent with what `always` means.

## References

Fixes #9548
…ategy install scripts (#9551)

In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.

Under `install-strategy=linked`, a package whose install script invokes
a separate helper bin to locate a native binding fails (e.g.
`unrs-resolver` via `napi-postinstall`, and the wider `napi-postinstall`
family). The helper bin is reached via a `.bin` symlink, so Node
resolves it to its own realpath in its own store dir, from which it
can't `require.resolve` the host's binding — so it declares it missing
and re-downloads (fatal on a restricted registry/proxy). Hoisted works
because the binding is hoisted onto the helper's resolution path.

## How

When running a store node's lifecycle script, set `NODE_PATH` to the
host package's store `node_modules` so a script-invoked bin can resolve
the host's sibling deps:

```js
if (isInStore) {
  const storeNodeModules = resolve(path, ...name.split('/').map(() => '..'))
  env.NODE_PATH = [storeNodeModules, process.env.NODE_PATH].filter(Boolean).join(delimiter)
}
```

`NODE_PATH` is fallback-only (can't shadow working resolution), added
only to the script's env (never `process.env`), and scoped to the
store-local `node_modules` — install-time only, runtime isolation
untouched.

## References

Fixes #9549
This prevents changing URLs from `https` and `git+https` into `git+ssh`,
but keeps the fall-back to `git+ssh` when the protocol is not specified.

The change in [pacote](npm/pacote#434) is
necessary in order to have this fully working.

## References
Supersedes #5256
Blocked by npm/pacote#434
Fixes #4305
Fixes #2610

Signed-off-by: Oldřich Jedlička <oldium.pro@gmail.com>
)

Adds a regression test for #8875. The fix is in npm-profile
(npm/npm-profile#191).

This test is expected to be red until bundled `npm-profile` is bumped to
the release with the fix, and turns green after that.

## Why

`npm login --auth-type=web` silently fails behind a proxy/mirror: the
returned `doneUrl` points at the canonical `registry.npmjs.org` instead
of the proxy, so npm polls the wrong host, gets a `403`, and falls back
to couch auth (which also fails). Fixed in npm-profile by rewriting
`doneUrl` to the configured registry origin.

## How

- `@npmcli/mock-registry`: `weblogin()` gains an optional `doneRegistry`
to emulate a proxy advertising a `doneUrl` on another origin.
- `test/lib/commands/login.js`: proxy registry whose `doneUrl` is on
`registry.npmjs.org`; asserts the token is saved with no couch fallback.
Fails with the current bundled npm-profile, passes once it is bumped.

## References

Fixes #8875
Depends on: npm/npm-profile#191
Related: #9550
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.