[pull] latest from npm:latest#14
Open
pull[bot] wants to merge 943 commits into
Open
Conversation
There was a problem hiding this comment.
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.
85ec0c9 to
26b6454
Compare
#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.
`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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot]
Can you help keep this open source service alive? 💖 Please sponsor : )