Significantly improved and sped up getComputedStyle(). Computed value rules are now applied across a broader set of properties, and include fixes related to inheritance, defaulting keywords, custom properties, and color-related values such as currentcolor and system colors. (@asamuzaK)
Fixed CSS 'background' and 'border' shorthand parsing. (@asamuzaK)
Node.js v22.13.0+ is now the minimum supported v22 version (was v22.12.0+).
Other changes:
Overhauled the CSSOM implementation, replacing the @acemir/cssom and cssstyle dependencies with fresh internal implementations built on webidl2js wrappers and the css-tree parser. Serialization, parsing, and API behavior is improved in various ways, especially around edge cases.
Added CSSCounterStyleRule and CSSNamespaceRule to jsdom Windows.
Added cssMediaRule.matches and cssSupportsRule.matches getters.
Added proper media query parsing in MediaList, using css-tree instead of naive comma-splitting. Invalid queries become "not all" per spec.
e2e3c81: Added the issues command as an alias of bugs, so pnpm issues opens the package's bug tracker URL in the browser.
8491f8e: Added the prefix command which prints the current package prefix directory (or global prefix directory if -g / --global is used).
3425e80: Added an _auth setting for configuring registry authentication as a single structured (URL-keyed) value. It can be set in the global pnpm config (config.yaml) or, for CI, via the pnpm_config__auth environment variable. The env form sidesteps the GitHub Actions / bash / zsh limitation that broke the existing pnpm_config_//host/:_authToken=… form (env var names containing /, :, or . are silently dropped). Closes #12314.
The value is keyed by registry URL so each secret is explicitly bound to the host that may receive it. Registry URL keys must use http or https and must not include credentials, query strings, or fragments:
Within each registry URL, @ means registry-wide/default credentials and package scopes like @org bind credentials to that scope on the same host. The only supported credential field is authToken (maps to _authToken / bearer auth); the deprecated basicAuth / username + password forms are intentionally not accepted here.
Each entry also infers a trusted registry route: @ routes the default registry (and pnpm add <pkg> resolves there), and @org routes that scope. Because the credential and destination host arrive in one trusted value, repo-controlled pnpm-workspace.yaml or project .npmrc cannot redirect the token to a different host. _auth is honored only from the env var and the global config — it is ignored in a project pnpm-workspace.yaml / .npmrc, so repo-controlled config can never supply registry auth. Precedence: CLI flags (--registry, --@​scope:registry) > pnpm_config__auth > global config.yaml_auth > pnpm-workspace.yaml.
Both pnpm_config__auth (lowercase, documented form) and PNPM_CONFIG__AUTH (all-caps, the shell convention some CI runners apply) are honored. If both are set, lowercase wins unless it is empty, in which case uppercase is used. The env var wins over the global config.yaml_auth on a conflicting key. tokenHelper is not supported in _auth. Parsing is strict: a malformed value (bad JSON, wrong shape, invalid registry URL or scope, an unsupported credential field) fails fast with an error rather than being silently dropped.
Pacquet parity note: the pacquet (Rust) port supports the same single credential field as the TS CLI: authToken.
a33eeec: pnpm self-update and packageManager version-switching can now install and link pnpm v12 (the Rust port), published with equal content under both the pnpm and @pnpm/exe names on the next-12 dist-tag. Its native binaries ship as @pnpm/exe.<platform>-<arch> packages, which pnpm's built-in installer links directly — no Node.js launcher, so the command pays no Node startup cost. v12 is initialized exactly like @pnpm/exe, including per-platform global-virtual-store hashing. From v12 onward the install converges on the unscoped pnpm package (the Rust exe) — even when updating from the SEA @pnpm/exe build.
1dd12bd: When resolving through a pnpr install-accelerator server, pnpm no longer forwards its own upstream registry credentials in the resolve request. Only the Authorization header identifying the caller to pnpr is sent. The pnpr server now selects upstream credentials from its own route policy (operator-configured upstream credential aliases), so private dependencies resolve through a pnpr-managed alias the caller is authorized to use, rather than by sending the client's registry tokens to the server.
1e81761: Expose web authentication authUrl and doneUrl in JSON error output when OTP is required in a non-interactive terminal #12724.
Patch Changes
2f389d6: Added the Node.js release team's new signing key (Stewart X Addison, 655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD) to the embedded Node.js release keys, so runtimes whose SHASUMS256.txt is signed by the new releaser verify successfully.
acbdb94: Fixed shell tab completion not suggesting workspaces after the -F alias for --filter option.
dcabb78: Fixed pnpm up -r <pkg> bumping unrelated packages that have open semver ranges. Previously, any update mutation nullified the lockfile-derived preferredVersions globally, so packages with ^x.y.z ranges could re-resolve to newer compatible versions even though the user only asked to update a specific package. The install layer now always seeds preferredVersions from the lockfile, and caller-supplied preferred versions (such as the vulnerability penalties of pnpm audit --fix) layer on top of the seed instead of replacing it. The targeted package still bumps: the per-resolve updateRequested flag makes the resolver ignore the target's own lockfile pins.
On Windows, removing or updating a global package now also cleans up the node.exe flavor of a bin, so a stale node.exe no longer survives on PATH after uninstall, and a new global install no longer silently overwrites an existing node.exe.
pnpm add -g pnpm@<version> (and @pnpm/exe@<version>) is now rejected like the bare pnpm form, pointing to pnpm self-update.
Dependency aliases read from a global package's manifest are validated before being joined onto node_modules paths, preventing a tampered manifest from escaping the install directory.
Each global install group is created in its own freshly-made directory (no longer reusing a colliding or pre-existing path).
Removing or updating a global package no longer unlinks a bin that belongs to a different globally installed package.
25c7388: pnpm now rejects jsr: specifiers whose package name is not a valid npm package name — an empty scope or name (e.g. jsr:@​scope/), path separators inside the name, or any other shape validate-npm-package-name rejects — with ERR_PNPM_INVALID_JSR_PACKAGE_NAME instead of silently converting them into a malformed @jsr/... npm package name.
25c7388: pnpm now rejects named-registry specifiers (e.g. gh:) whose package name is not a valid npm package name — an empty scope (e.g. gh:@​/bar), path separators inside the name (e.g. gh:@​scope/../name), or any other shape validate-npm-package-name rejects — with ERR_PNPM_INVALID_NAMED_REGISTRY_PACKAGE_NAME instead of passing the name through to registry URLs and metadata cache file paths.
96da7c5: node-gyp's gyp_main.py and gyp entrypoints are now packed with the executable bit in the pnpm and @pnpm/exe tarballs. Without it, building native addons from source could fail with a permission error.
99982b9: Sped up resolution and reduced memory use against registries that ignore npm's abbreviated metadata format and always return the full package document (for example, Azure DevOps Artifacts). pnpm now strips such documents down to the abbreviated field set before caching them. Resolution output is unchanged, and registries that honor the abbreviated format (such as the npm registry) pay no extra cost.
11a7fdd: Sped up offline and --prefer-offline resolution on large workspaces (e.g. pnpm dedupe --offline, pnpm install --offline). Package metadata loaded from the local cache is now kept in memory, so each package's metadata is parsed once per command instead of once per dependent that references it.
2c7369d: pnpm pack-app now rejects --entry / pnpm.app.entry and --output-dir / pnpm.app.outputDir values that are absolute paths or escape the project directory via .. (or a symlink that resolves outside it), and refuses to write the produced executable when its target path already exists as a symlink (or other non-regular file). This prevents a repository-controlled package.json from embedding host files (such as an SSH key) into the produced executable, writing build artifacts outside the project, or overwriting an arbitrary file through a committed symlink. The new error codes are ERR_PNPM_PACK_APP_ENTRY_OUTSIDE_PROJECT, ERR_PNPM_PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT, and ERR_PNPM_PACK_APP_OUTPUT_FILE_NOT_REGULAR.
When ad-hoc signing macOS targets, pnpm pack-app now runs the system codesign by absolute path and resolves ldid to a location outside the project, so a repository-controlled node_modules/.bin on PATH cannot hijack the signer.
ce5d5a5: Relative paths in patchedDependencies are now resolved against the lockfile directory when computing patch file hashes, so running pnpm install from a subdirectory no longer fails with ENOENT looking for the patch file in the wrong location #12762.
ebb4096: pnpm peers no longer reports a conflict for a missing peer dependency that is ignored via pnpm.peerDependencyRules.ignoreMissing.
dcabb78: Fixed a prototype-pollution hazard when seeding preferred versions: a dependency named __proto__ in a manifest or in pnpm-lock.yaml could write through Object.prototype (or crash the install) while the preferred-versions map was being built. The maps are now null-prototype objects, so crafted package names land as plain keys.
f38e696: Hardened pnpm deploy --force so it refuses unsafe deploy targets such as workspace roots, parent directories, out-of-workspace paths, and symlinked target parents.
806c3ec: pnpm no longer warns about ignored project-level auth settings when PNPM_CONFIG_NPMRC_AUTH_FILE points at the project .npmrc — setting it to that file is an explicit opt-in to trusting it, so auth env variables in it are expanded pnpm/pnpm#12480.
991405e: Restore differential rendering (ansi-diff) to fix duplicated output lines introduced by #12351.
c121235: Fixed the topological order of --filtered commands (pnpm run, pnpm exec, pnpm publish, pnpm pack, pnpm rebuild) when the selected projects depend on each other only transitively through projects that were not selected. Previously such selected projects could run concurrently or in the wrong order; now a project always runs after the selected projects it transitively depends on, while projects without a real dependency relationship still run concurrently. This now also holds for prod-only filters (--filter-prod), which resolve order through the production dependency graph so transitive production dependencies are respected without pulling back the dev dependencies the filter drops, and for selections that mix --filter with --filter-prod#8335.
d539172: pnpm pack and pnpm publish no longer follow a symlinked workspace LICENSE file when injecting it into a package that has no license of its own. Following the symlink could pack bytes from outside the workspace into the published tarball.
dcabb78: Fixed pnpm up <pkg> producing a different result than a fresh install of the same manifests would. The resolver now distinguishes updateRequested (true only for packages that match the user's update target) from the broader update flag, and for the targeted package ignores only its own lockfile-derived preferred-version pins — so the target re-resolves exactly as if its lockfile entries were deleted and pnpm install ran. Preferred versions a fresh install applies (manifest pins, versions propagated down the dependency chain, and the vulnerability-avoidance penalties of pnpm audit --fix) stay in effect, so an update never installs duplicate versions that a reinstall from scratch would not reproduce. When a preferred version holds the update target below the newest version its range admits, pnpm now prints a warning explaining that reaching the newer version everywhere requires an override.
dcabb78: pnpm update <dep>@​<version> now prints a warning when <dep> is only present as a transitive dependency: the requested version cannot be applied there (updates resolve the target the way a fresh install would), and the warning recommends adding the version to pnpm.overrides instead, which is the mechanism that does pin transitive dependencies. Closes #12744.
a6c4d5f: When a dependency cannot be found in the registry (404) or the registry has no matching version, and a workspace project with the same name exists only at non-matching versions, the error now reports the available workspace versions (ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE) instead of the raw registry failure pnpm/pnpm#1379. Other registry failures (authorization, network, server errors) still propagate unchanged. The pacquet (Rust) resolver applies the same behavior.
bae694f: Some registries generate tarballs on-demand and cannot provide an integrity checksum in their package metadata. In that case pnpm now computes the integrity from the downloaded tarball and stores it in the lockfile, so the entry is verifiable on subsequent installs instead of being written without an integrity (which would fail the next install). This also applies to --lockfile-only: the tarball is downloaded so its integrity can be computed. A lockfile entry that is still missing its integrity is rejected as a ERR_PNPM_MISSING_TARBALL_INTEGRITY lockfile verification violation (the install fails closed) rather than being silently re-fetched.
6c35a43: Added --exclude-peers to pnpm sbom. With auto-install-peers (the default), peer dependencies resolve into the lockfile and are otherwise indistinguishable from the package's own dependencies. The flag drops peer dependencies (and any transitive subtree reachable only through them) from the SBOM. CycloneDX 1.7 has no scope or relationship that expresses "consumer-provided peer", so omission is the only spec-clean handling. The flag name matches pnpm list --exclude-peers; note the SBOM flag prunes a peer's exclusive subtree, which is stricter than pnpm list (which only hides leaf peers).
Patch Changes
25a829e: pnpm audit --fix now writes a single combined minimumReleaseAgeExclude entry per package (e.g. axios@0.18.1 || 0.21.1) instead of one entry per version, matching the format documented for the setting. Existing per-version entries in pnpm-workspace.yaml are merged into the combined form rather than left as duplicates. Installs that auto-collect immature versions into minimumReleaseAgeExclude now report the same combined entries, so the "Added N entries" message matches what is written to the manifest #12534.
1cbb5f2: Fixed non-deterministic peer resolution that could add or remove an optional transitive peer — for example @babel/core, reached through styled-jsx — from a package's peer-dependency suffix across otherwise identical installs, churning the lockfile and causing intermittent pnpm dedupe --check failures in CI. When a package's children are resolved by one occurrence (the "owner") and reused by a deeper consumer, whether that consumer inherited the owner's missing peers depended on whether the owner's resolution had finished yet — a race under concurrent resolution. The decision is now a function of the dependency graph's structure rather than resolution-completion order.
d577eea: Fixed a Windows flakiness in pnpm dlx where a failed install could surface a spurious EBUSY: resource busy or locked error. The cleanup of a partially-populated dlx cache is now best-effort with retries and no longer masks the original error.
ec7cf70: Shortened the pnpm dlx cache path so deep dependency trees no longer overflow Windows' MAX_PATH, which could make a dependency's lifecycle script fail with spawn cmd.exe ENOENT.
05b95ab: Fixed pnpm hanging (and crashing with an unhandled promise rejection) when a non-retryable network error such as SELF_SIGNED_CERT_IN_CHAIN occurs while fetching from a registry. The error is now rejected through the returned promise instead of being thrown inside the detached retry callback.
d3f68e2: Fix a pnpm audit performance regression on lockfiles that contain dependency cycles. The reachable-vulnerability pruning added in pnpm 11.5.1 only memoized acyclic subtrees, so any node whose subtree touched a cycle — together with all of its ancestors — was recomputed on every query, making the path walk quadratic. Reachability is now computed once per node using Tarjan's strongly-connected-components algorithm, so cyclic graphs are handled in linear time #12212.
The audit path walk also no longer recurses, so a deeply nested dependency graph can no longer overflow the call stack, and the install path to each finding is tracked without per-node copying, keeping memory linear in the graph depth.
322f88f: Fix failed optional dependency updates so they don't rewrite unrelated dependency specs #11267.
1488db1: When enableGlobalVirtualStore is toggled on for a project that was previously installed without it, stale hoisted symlinks under node_modules/.pnpm/node_modules are now replaced instead of being left pointing at the old per-project virtual store location #9739.
6545793: Fixed pnpm install --ignore-workspace overwriting the allowBuilds map in pnpm-workspace.yaml. The ignored builds of a package with a build script were auto-populated into allowBuilds even though --ignore-workspace was passed, clobbering committed true/false values with the set this to true or false placeholder #12469.
fbdc0eb: Fixed minimumReleaseAgeExclude and trustPolicyExclude so multiple exact-version entries for the same package behave the same as a single || disjunction entry. Previously only the first matching rule's versions were honored, so a config like [form-data@4.0.6, form-data@2.5.6] could still flag form-data@2.5.6 as violating minimumReleaseAge, while [form-data@4.0.6 || 2.5.6] worked as expected #12463.
fa7004b: The in-memory package metadata cache is now populated on the exact-version disk fast path, so repeated resolutions of the same package within one install no longer re-read and re-parse the on-disk metadata. In large monorepos this brings the time for adding a new package down from minutes to seconds. The in-memory cache key now also includes the registry, so a package of the same name served by two different registries in a single install can no longer share a cache slot and resolve the wrong tarball.
0a154b1: Fixed pnpm patch dropping the package name (and leaking internal option fields) when the patched dependency resolves to a single git-hosted version.
4d3fe4b: The pnpr resolver endpoints moved under the reserved /-/pnpr namespace: POST /v1/resolve is now POST /-/pnpr/v0/resolve and POST /v1/verify-lockfile is now POST /-/pnpr/v0/verify-lockfile. The capability handshake at GET /-/pnpr advertises protocol version 0 to match. This keeps every pnpr-proprietary route in npm's reserved namespace, so it can never collide with a package path.
0ec878d: Removing a runtime dependency now removes the matching devEngines.runtime or engines.runtime entry that was materialized from it. Blank runtime selectors are normalized to latest.
17e7f2c: pnpm sbom now emits a CycloneDX issue-tracker external reference for components (and the root) whose package.json declares a bugs URL. Email-only bugs entries are skipped, since the reference requires a URL.
a84d2a1: Add @pnpm/resolving.tarball-url, which builds and recognizes the canonical npm tarball URL of a package. It vendors getNpmTarballUrl (previously the external get-npm-tarball-url package) and adds isCanonicalRegistryTarballUrl, the predicate the lockfile writer uses to decide whether a tarball URL is derivable from name+version+registry (and can therefore be omitted from pnpm-lock.yaml).
Exposing isCanonicalRegistryTarballUrl lets a custom resolver (pnpmfile resolvers) fronting a proxy that serves tarballs on a non-canonical path (e.g. an ephemeral localhost:<port>) rewrite the resolved tarball to the canonical form, so nothing host-specific is persisted to the lockfile. Previously this logic was private to @pnpm/lockfile.utils.
Two correctness fixes are included while consolidating the logic: the scoped-package unescape now handles uppercase %2F as well as %2f (percent-encoding is case-insensitive), and protocol-insensitive comparison strips only a leading http(s):// scheme instead of splitting on the first :// (which could truncate URLs containing a later ://).
852d537: Lockfile verification no longer reports a registry metadata fetch failure (for example a 403/401 on a private registry, or a network error) as ERR_PNPM_TARBALL_URL_MISMATCH. When the registry can't be reached to verify an entry, the install now aborts with the registry's own fetch error (such as ERR_PNPM_FETCH_403, which already explains the authentication situation) instead of mislabeling a transport failure as lockfile tampering. Registry fetch errors no longer leak basic-auth credentials embedded in the registry URL (https://user:pass@host/) into their message.
c112b61: Added a --dry-run option to pnpm install. It runs a full dependency resolution and reports what an install would change, but writes nothing to disk (no lockfile, no node_modules) and always exits with code 0. This mirrors the preview semantics of npm install --dry-run#7340.
179ebc4: pnpm run --no-bail now exits with a non-zero exit code when any of the executed scripts fail, while still running every matched script to completion. This makes the exit-code behavior of --no-bail consistent between recursive and non-recursive runs (recursive runs already failed at the end). Previously, a non-recursive pnpm run --no-bail always exited with code 0, even when a script failed #8013.
0474a9c: Added support for generating Node.js package maps at node_modules/.package-map.json during isolated and hoisted installs. Added the node-experimental-package-map setting to inject the generated map into pnpm-managed Node.js script environments, and the node-package-map-type setting to choose between standard and loose package maps.
dcededc: pnpm sbom now marks components reachable only through devDependencies with CycloneDX scope: "excluded" and the cdx:npm:package:development property. The excluded scope documents "component usage for test and other non-runtime purposes", which matches the semantics of a devDependency; the property is the CycloneDX npm-taxonomy marker emitted by @cyclonedx/cyclonedx-npm, so both modern (scope) and existing (property) consumers are covered. Components reachable at runtime (including installed optionalDependencies) omit scope and default to required.
1495cb0: Added per-package SBOM generation with --out and --split flags. Use --out out/%s.cdx.json to write one SBOM per workspace package to individual files, or --split for NDJSON output to stdout. When --filter selects a single package, the SBOM root component now uses that package's metadata. Workspace inter-dependencies (workspace: protocol) and their transitive dependencies are included. Author, repository, and license fall back to the root manifest when the package doesn't define them.
293921a: feat(view): support searching project manifest upward when package name is omitted
When running pnpm view without a package name, the command now searches
upward for the nearest project manifest (package.json, package.yaml, or package.json5) and uses its name field.
If the manifest exists but lacks a name field, an error is thrown.
This change also replaces the find-up dependency with empathic for
improved performance and consistency across workspace tools.
Patch Changes
29ab905: Fixed pnpm update overriding the version range policy of a named catalog whose name parses as a version (e.g. catalog:express4-21). The catalog: reference carries no pinning of its own, so the prefix from the catalog entry (such as ~) is now preserved instead of being widened to ^#10321.
bee4bf4: Security: validate config dependency names and versions from the env lockfile (pnpm-lock.yaml) before using them to build filesystem paths. A committed lockfile with a traversal-shaped configDependencies name (such as ../../PWNED) or version (such as ../../../PWNED) could previously cause pnpm install to create symlinks or write package files outside node_modules/.pnpm-config and the store. Names must now be valid npm package names and versions must be exact semver versions; the same validation is applied to optional subdependencies of config dependencies, and to the legacy workspace-manifest format before any lockfile is written. See GHSA-qrv3-253h-g69c.
96bdd57: Fix link: workspace protocol switching to file: after pnpm rm is run from inside a workspace package whose target workspace dependency has its own dependencies, when injectWorkspacePackages: true is set. Follow-up to #10575, which fixed the same symptom for workspace packages without dependencies.
302a2f7: No longer warn about using both packageManager and devEngines.packageManager when the two fields pin the same package manager at the same version with the same integrity hash (e.g. both pnpm@11.5.1+sha512.…). Previously the hash was stripped from the legacy packageManager field but not from devEngines.packageManager, so even identical specifications looked like a mismatch #12028.
The warning still fires on any genuine divergence, and several cases now state the specific reason instead of a single generic message: a different package manager, a different version, or contradictory integrity hashes for the same version.
3f0fb21: Fixed the progress line showing leftover characters from external processes that write to the terminal between progress updates (e.g. an SSH passphrase prompt would leave a fragment like added 0sa':). The interactive reporter now redraws each frame in place, erasing to the end of the display before reprinting, so any such remnants are cleared #12350.
564619f: Fixed pnpm approve-builds reporting "no packages awaiting approval" when a build-script dependency whose approval was revoked (e.g. after git stash drops the allowBuilds from pnpm-workspace.yaml) is re-added. The revoked packages are now correctly recorded in .modules.yaml so approve-builds can find them. #12221
3d1fd20: Skip the redundant "target bin directory already contains an exe called node" warning on Windows when the existing node.exe already matches the target (same hard link or identical content) pnpm/pnpm#12203.
1b02b47: Fix macOS Gatekeeper blocking native binaries (.node, .dylib, .so) by removing the com.apple.quarantine extended attribute after importing them from the store.
When pnpm imports files from its content-addressable store into node_modules, macOS preserves extended attributes, including com.apple.quarantine. If this xattr is present on a store blob (e.g. it was first written under a Gatekeeper-enabled app such as a Git client), it propagates to node_modules, and Gatekeeper blocks the native binary from loading even though pnpm already verified the file's integrity against the lockfile.
After importing a package, pnpm now strips com.apple.quarantine from its native binaries, matching Homebrew's behaviour of dropping quarantine from verified downloads. The cleanup is macOS-only, runs in a single batched xattr call per package, is restricted to native binaries (other files are untouched), and is non-fatal (it logs a warning on unexpected errors).
61969fb: Fix pnpm install with optimisticRepeatInstall incorrectly reporting Already up to date when pnpm-lock.yaml changed but project manifests did not. This affected workflows such as checking out or restoring only the lockfile #12100.
Also fixes checkDepsStatus to use the correct lockfile path when useGitBranchLockfile is enabled, so the optimistic fast-path and lockfile modification detection work with pnpm-lock.<branch>.yaml files instead of always stat'ing pnpm-lock.yaml. Merge-conflict detection now reads the resolved lockfile name as well, and with mergeGitBranchLockfiles enabled every pnpm-lock.*.yaml is scanned for modifications and conflicts. The git branch is now resolved by reading .git/HEAD directly (no process spawn) and uses the workspace directory rather than process.cwd().
5c12968: Fix recursive updates of transitive dependencies when the update command mixes transitive dependency patterns with direct dependency selectors. For example, pnpm up -r "@​babel/core" uuid now updates matching transitive @babel/core dependencies even when uuid is a direct dependency selector #12103.
9d79ba1: Register the pnpm update --no-save flag in the CLI help and option parser.
0474a9c: Fixed pnpm import for Yarn v2 lockfiles when js-yaml v4 is installed.
9e0c375: Fixed pnpm install repeatedly prompting to remove and reinstall node_modules in a workspace package when enableGlobalVirtualStore is enabled. The post-install build step recorded a per-project node_modules/.pnpm virtual store directory in node_modules/.modules.yaml, overwriting the global <storeDir>/links value the install step had written. The next install then detected a virtual-store mismatch (ERR_PNPM_UNEXPECTED_VIRTUAL_STORE). The build step now derives the same global virtual store directory as the install step #12307.
223d060: Document the --cpu, --os and --libc flags in the output of pnpm install --help. These flags were already supported but were only documented on the website #12359.
e85aea2: Avoid reading README.md from disk when publishing if the publish manifest already provides a readme field. The README is now only read lazily, inside createExportableManifest, when it is actually needed.
3188ae7: Fixed pnpm peers check to accept loose peer dependency ranges such as >=3.16.0 || >=4.0.0- when the installed peer version satisfies the range #12149.
531f2a3: Fixed pnpm update rewriting a workspace: dependency that points at a local path (e.g. workspace:../packages/foo/dist) into a normalized link: or version-range specifier. Such specifiers are now preserved verbatim when the workspace protocol is preserved #3902.
fe66535: Fixed a lockfile non-convergence bug where an incremental install kept a duplicate transitive dependency that a fresh install would not produce. When a package is reused from the lockfile, its child edges are taken verbatim and bypass the preferred-versions walk, so a transitive dependency could stay pinned to an older version even after a direct dependency resolved to a higher version that satisfies the same range. The resolver now refreshes such a stale pin to the higher direct-dependency version during resolution — so the older version is never resolved or fetched, and the incremental result converges to the fresh one.
6d35338: pnpm install detects changes inside local file dependencies again. The optimistic repeat-install fast path only tracks manifest and lockfile modification times, so edits inside a local dependency's directory (or a repacked local tarball) were reported as "Already up to date". Projects with local file dependencies (file: and bare local path or tarball specifiers, declared directly or through pnpm.overrides) now always run a full install, which refetches those dependencies, matching pnpm v10 behavior #11795.
4ca9247: Preserve the existing Node.js runtime version prefix when resolving node@runtime:<range> to a concrete version.
30c7590: Create shorter CAFS temporary package directories to leave room for lifecycle scripts that create IPC socket paths under TMPDIR.
13815ad: Reporter output (warnings, progress) for pnpm store and pnpm config subcommands now goes to stderr instead of stdout. This fixes scripts that capture their stdout (e.g. PNPM_STORE=$(pnpm store path), pnpm config list --json | jq) from getting warnings mixed into the result.
1c05876: Avoid relinking unchanged child dependencies and remove stale child links during warm installs.
817f99d: Fixed lockfile churn where a package's transitivePeerDependencies could be dropped (and shift between packages) when the package participates in a dependency cycle. A cycle re-entry resolves against truncated children, so it must not be cached as "pure"; otherwise sibling occurrences of the same package short-circuit and lose transitive peers depending on traversal order #5108.
eba03e0: Fix pnpm install reporting "Already up to date" after a catalog entry in pnpm-workspace.yaml was reverted to a previous version. After an update modified a catalog, the workspace state cache stored the pre-update catalog versions, so reverting the entry back to its original version was not detected as an outdated state #12418.
3b54d79: pnpm update now keeps lockfile overrides that resolve through a catalog in sync with the catalog. Previously, when an override referenced a catalog (e.g. overrides: { foo: 'catalog:' }) and pnpm update bumped that catalog entry, the lockfile's catalogs advanced while the resolved overrides kept the old version. The resulting lockfile was internally inconsistent, so a later pnpm install --frozen-lockfile failed with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.
9d0a300: Fixed pnpm version --recursive so it honors the workspace selection. In recursive mode the version bump now applies to the packages resolved from the workspace filter (selectedProjectsGraph), matching the behavior of pnpm publish --recursive, instead of always bumping every workspace package #11348.
Added a new setting frozenStore (--frozen-store) that lets pnpm install run against a package store on a read-only filesystem (e.g. a Nix store, a read-only bind mount, an OCI layer). When enabled, pnpm opens the store's SQLite index.db through the immutable=1 URI — bypassing the WAL/-shm sidecar creation that otherwise fails on a read-only directory — and suppresses every store-write path (the index.db writer and the project-registry write). Pair it with --offline --frozen-lockfile against a fully-populated store. Under the global virtual store, package directories live inside the store, so if the store is missing the build output of a package whose lifecycle scripts are approved (or that has a patch), pnpm fails up front with ERR_PNPM_FROZEN_STORE_NEEDS_BUILD rather than crashing mid-build on a read-only write — seed the store with those builds first. Incompatible with --force and with a configured pnpr server, since both write into the store; the side-effects cache is likewise not written under frozenStore. If the store is missing its content directory, the install fails fast with ERR_PNPM_FROZEN_STORE_INCOMPLETE rather than attempting to initialize it. The read-only immutable=1 open requires Node.js >=22.15.0, >=23.11.0, or >=24.0.0; on older runtimes --frozen-store fails with a clear ERR_PNPM_FROZEN_STORE_UNSUPPORTED_NODE error. Bin-linking also tolerates a read-only store: under the global virtual store a package's bin source lives inside the store, so the chmod that makes it executable would be refused — with EPERM/EACCES, or with EROFS on a genuinely read-only filesystem. That chmod is redundant when the seed already ships its bins executable with a normalized shebang, so it is now skipped in that case, while a non-executable bin (or one still carrying a Windows CRLF shebang) on a read-only store still errors.
When pacquet (the Rust port of pnpm) is declared in configDependencies, pnpm now delegates dependency resolution to it too — not just materialization — provided the installed pacquet is new enough to support full resolving installs (>= 0.11.7).
Previously pacquet only ran in frozen-install mode: pnpm always resolved the dependency graph itself (writing pnpm-lock.yaml) and handed pacquet a finished lockfile to fetch / import / link. With pacquet >= 0.11.7, a non-frozen pnpm install (default isolated nodeLinker, plain install) is delegated to pacquet end-to-end in a single pass — pacquet resolves the manifests, writes the lockfile, and materializes node_modules. pnpm detects the capability from the installed pacquet's version; older pacquet releases keep the resolve-then-materialize split, and add / update / remove still resolve in pnpm (it has to mutate the manifests first). This remains an opt-in preview of the Rust install engine #11723.
Added a new opt-in --batch flag to pnpm publish --recursive that sends all selected packages to the registry in a single PUT /-/pnpm/v1/publish request instead of one request per package. The target registry has to implement the batch publish endpoint (pnpr does); registries that don't are reported with a clear ERR_PNPM_BATCH_PUBLISH_UNSUPPORTED error. The batch is processed all-or-nothing by pnpr: if any package in the batch fails validation, none of the packages are published.
Patch Changes
Reject path-traversal and reserved dependency aliases (such as ../../../escape, .bin, .pnpm, or node_modules) that come from a lockfile rather than a freshly resolved manifest. A crafted lockfile alias could otherwise be joined directly under a hoisted node_modules directory, letting package files be written outside the intended install root or overwrite pnpm-owned layout.
The fix adds two layers:
The nodeLinker: hoisted graph builder now validates each alias at the directory sink (safeJoinModulesDir), matching the validation pnpm already performs when resolving aliases from manifests.
The lockfile verification gate (verifyLockfileResolutions) now runs an always-on, policy-independent check that rejects any importer or snapshot dependency alias that is not a valid package name, failing the install early — before any fetch or filesystem work — for every node linker at once.
Made shared package child resolution deterministic when the same package is reached through multiple contexts. pnpm now chooses the shallowest occurrence, then importer order, then parent path, instead of letting request timing decide the child context and missing-peer report pnpm/pnpm#12358.
Fix garbled summary line after submitting pnpm update -i and pnpm audit --fix -i. The interactive checkbox prompt previously printed every selected choice's full table row (label, current/target versions, workspace, URL) joined by commas, producing a wall of text after pressing Enter. The summary now lists only the selected package names (or vulnerability keys) by setting an explicit short per choice; the in-progress selection UI is unchanged.
Prevent pnpm patch-remove from removing files outside the configured patches directory.
Fixed pnpm publish ignoring strictSsl: false when publishing to registries with self-signed certificates. The strictSSL option is now forwarded to libnpmpublish / npm-registry-fetch so that strict-ssl=false in .npmrc or strictSsl: false in pnpm-workspace.yaml is respected during publish, the same way it is for pnpm installpnpm/pnpm#12012.
Fixed Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined regression introduced in 11.6.0 when running pnpm add <pkg> outside a workspace on Windows. selectProjectByDir was keying the resulting ProjectsGraph by opts.dir instead of project.rootDir, so downstream manifestsByPath lookups missed when the two paths normalized differently (typically drive-letter casing). pnpm/pnpm#12379
Git dependencies that point to a subdirectory of a repository (repo#commit&path:/sub/dir) keep their path in the lockfile again. Since the integrity of git-hosted tarballs started being pinned in the lockfile, any install that actually downloaded the tarball rebuilt the lockfile resolution as { integrity, tarball, gitHosted } and dropped the path field, while installs served from the store kept it — so the field disappeared seemingly at random. Without path, later installs from that lockfile silently unpacked the repository root instead of the subdirectory #12304.
Fixed nondeterministic lockfile output that made pnpm dedupe --check fail intermittently in CI. When a locked peer provider was pinned for a dependency that has no child dependencies of its own, the pinned provider leaked into the shared parent scope, so siblings resolved after it could pick up an optional peer they should not see. Which siblings were affected depended on resolution order, which varies with network timing.
Sped up pnpm install with a frozen lockfile by running lockfile verification (the policy revalidation gate added for minimumReleaseAge/trustPolicy and the tarball-URL anti-tamper check) concurrently with fetching and linking instead of blocking the whole install on it. Dependency lifecycle scripts are still held back until verification succeeds, so no script runs on an unverified lockfile: if verification fails the install aborts before any dependency build, and if linking finishes first the install waits for the verification verdict before completing.
User-defined npm_config_* environment variables are now preserved during lifecycle script execution. Previously, all npm_-prefixed env vars were stripped, which caused user-set variables like npm_config_platform_arch to be lost pnpm/pnpm#12399.
pnpm can now use different auth tokens for different package scopes, even when those scopes use the same registry URL.
Previously, auth was selected only by registry URL. If @org-a and @org-b both used https://npm.pkg.github.com/, they had to share the same token. This caused problems for registries that issue tokens per organization or per scope.
Configure a scope-specific token by adding the package scope after the registry URL in the auth key:
pnpm login --registry=https://npm.pkg.github.com --scope=@​org-a writes the token to the same scope-specific auth key.
When installing or publishing @org-a/*, pnpm uses ORG_A_TOKEN. For @org-b/*, pnpm uses ORG_B_TOKEN. Packages without a matching scope continue to use the registry-wide fallback token.
pnpm setup no longer prompts to approve build scripts for @pnpm/exe when installing the standalone executable. pnpm links the platform-specific binary itself, so the package's install scripts are skipped during the global self-install #12377.
Close lockfile reads deterministically before rewriting lockfiles and keep pacquet's virtual store directory length aligned with pnpm on Windows.
A 304 Not Modified answer from the registry now renews the cached metadata file's mtime, so the minimumReleaseAge freshness shortcut keeps serving resolutions from the cache. Previously, once a cached packument grew older than minimumReleaseAge, every subsequent install re-validated it against the registry forever, because a 304 never rewrites the file.
Updated dependency ranges. Notably:
@pnpm/logger peer dependency range moved to ^1100.0.0.
msgpackr 1.11.8 → 2.0.4 (store index files remain byte-compatible in both directions).
Fixed a Windows-only hang where a failed command could take 20–46 seconds to exit. On error, pnpm enumerates descendant processes (via pidtree) to terminate them, which on Windows shells out to wmic/PowerShell Get-CimInstance Win32_Process — a lookup that is extremely slow on some machines. The lookup is now bounded by a short timeout so it can no longer stall the process exit.
pnpm install completes without re-resolving when pnpm-lock.yaml was deleted but node_modules is intact: the up-to-date check now treats the current lockfile (node_modules/.pnpm/lock.yaml) — the record of what the previous install materialized — as the wanted lockfile, verifies the manifests still match it, restores pnpm-lock.yaml from it, and reports "Already up to date". Previously this scenario triggered a full resolution and a re-verification of every locked package against the registry.
615c669: Added support for configuring URL-scoped registry settings through npm_config_//… and pnpm_config_//… environment variables, for example:
This provides a file-free way to supply registry authentication. Because the registry a value applies to is encoded in the (trusted) environment variable name, it is host-scoped by construction and cannot be redirected to another registry by repository-controlled config. The environment value is treated as trusted config: it takes precedence over a project/workspace .npmrc but is still overridden by command-line options. When the same key is provided through both prefixes, pnpm_config_ wins.
Raised the default network concurrency from min(64, max(cpuCores * 3, 16)) to min(96, max(cpuCores * 3, 64)). Package downloads are I/O-bound, not CPU-bound, so deriving the floor from the core count left machines with few cores (for example 4-vCPU CI runners) downloading only 16 tarballs at a time and unable to saturate a low-latency registry. The networkConcurrency setting still overrides the default.
Patch Changes
Improved the warning printed when a project .npmrc uses an environment variable in a registry/proxy URL or in registry credentials. The message now explains why the setting was ignored and how to migrate it to a trusted source — for example by moving the line to the user-level ~/.npmrc or running pnpm config set "<key>" <value> — with a link to https://pnpm.io/npmrc. The pnpm config set example is only suggested when the key has no ${...} placeholder, so the snippet is always safe to copy-paste.
Print a "Lockfile passes supply-chain policies (verified 2h ago)" message when lockfile verification is skipped because a cached verdict for the same lockfile content and policy is reused. Previously the cached short-circuit was completely silent, which made it look like the policy gate never ran #12324.
Platform-specific optional dependencies are now skipped even when their os/cpu/libc fields are missing from the registry metadata or the lockfile. Some registries strip these fields from the package metadata, which made pnpm download and install the binaries of every platform regardless of supportedArchitectures. The missing platform fields of an optional dependency are now inferred from its name (e.g. @nx/nx-win32-arm64-msvc → os: win32, cpu: arm64), so foreign-platform binaries are skipped without even downloading them #11702.
Stopped expanding environment variables in repository-controlled registry/proxy request destinations and registry credential values from .npmrc, and in workspace registry URLs from pnpm-workspace.yaml. Move dynamic registry URL and token configuration to trusted user, global, CLI, or environment config.
Resolve package-manager bootstrap dependencies with trusted user or CLI registry and network config, and reject package-manager env-lockfile records that do not use registry package paths with integrity-only resolutions before auto-switch execution.
Avoid writing packageManagerDependencies to pnpm-lock.yaml when package manager policy is set to onFail: ignore or pmOnFail: ignore#12228.
Avoid running dependency-status auto-install when the dependency status is unavailable without a project manifest.
Using the $ version reference syntax in overrides (e.g. "react": "$react") now prints a deprecation warning. The syntax still works, but catalogs are the recommended way to keep an overridden version in sync with the rest of the workspace. Reference a catalog entry with the catalog: protocol instead.
Fixed pnpm config get globalconfig to return the global config.yaml path again pnpm/pnpm#11962.
Fixed bare --color so it does not consume the following CLI flag, allowing command shorthands like --parallel to expand correctly and forms like pnpm --color with current <command> to dispatch the inner command instead of failing with MISSING_WITH_CURRENT_CMD.
Fix pnpm install ignoring enableGlobalVirtualStore toggle by including it in the workspace state settings check #12142.
Security: pnpm now verifies the npm registry signature of a package-manager binary before spawning it, so a cloned repository cannot make pnpm download and execute an arbitrary native binary.
This covers two paths that select an executable from repository-controlled input:
pacquet install engine — declaring pacquet (or @pnpm/pacquet) in configDependencies opts in to pnpm's Rust install engine. pnpm now verifies that the installed pacquet shim and the host's @pacquet/<platform>-<arch> binary carry a valid npm registry signature for their exact name@version, and refuses to run pacquet (failing the command) if the signature does not verify or cannot be checked. The only graceful fallback to pnpm's own engine is when pacquet has no binary for the current platform.
automatic version switch / self-update — the packageManager / devEngines.packageManager field makes pnpm download and run a specific pnpm version. pnpm now verifies the registry signature of pnpm, @pnpm/exe, and the host platform binary before installing/spawning them, and refuses to run an engine whose signature does not match a published, signed release. The check runs only on an actual download (store cache miss), so it does not add a network round trip to every command.
In both cases the signature is verified over the installed integrity, against npm's public signing keys that ship embedded in the pnpm CLI (like corepack), so bytes substituted via a tampered lockfile or a repository-controlled registry fail verification — and a registry the user did not vouch for cannot supply its own signing keys. The signed packument is fetched from the configured registry, so an npm mirror works transparently. Verification fails closed: if it cannot be completed (for example, the registry is unreachable), the command fails rather than running an unverified binary. The embedded keys are kept current by a release-time check against npm's signing-keys endpoint.
Made peer-dependent deduplication deterministic. When a peer-suffixed package variant was a subset of two or more mutually incompatible larger variants, the variant it collapsed into depended on the order importers were resolved in, which varies between machines. This could resolve the same workspace to different lockfiles on different platforms and make pnpm dedupe --check alternate between passing and failing.
Reject invalid package names and versions from staged tarball manifests before deriving filenames for pnpm stage download.
Clarified in CLI help that the pnpm store is trusted shared state and store integrity checks are corruption detection, not a tamper boundary for untrusted store writers.
Reject reserved manifest bin names ("", ".", "..", and scoped forms such as @scope/..) when resolving a package's bins. These names previously passed the bin-name guard and, when joined to the global bin directory during global remove/update/add operations, could resolve to the global bin directory itself or its parent and have it recursively deleted.
Require trusted package identity before package-name allowBuilds entries can approve lifecycle scripts for git, git-hosted tarball, direct tarball, and local directory artifacts. To approve one of those artifacts explicitly, use its peer-suffix-free lockfile depPath as the allowBuilds key. Lockfile verification now rejects lockfiles where a registry-style dependency path (name@semver) is backed by a git, directory, or git-hosted tarball resolution (ERR_PNPM_RESOLUTION_SHAPE_MISMATCH), so the dependency path is a reliable artifact identity by the time scripts can run.
Security: pnpm now verifies the OpenPGP signature of a downloaded Node.js runtime's SHASUMS256.txt before trusting its integrity hashes.
When a repository requests a Node.js runtime (e.g. via devEngines.runtime / useNodeVersion), the download mirror is repository-configurable through node-mirror:<channel>. The integrity of the downloaded binary was only checked against SHASUMS256.txt fetched from that same mirror — a circular check that a malicious mirror could satisfy by serving a tampered binary together with a matching SHASUMS256.txt. pnpm then executes the binary (for example to run lifecycle scripts).
pnpm now fetches SHASUMS256.txt.sig and verifies the detached OpenPGP signature against the Node.js release team's public keys, which ship embedded in the pnpm CLI. A mirror that serves a tampered binary cannot also produce a valid signature, so the download fails to verify. The embedded keys are kept current by a release-time check against the canonical nodejs/release-keys list.
The musl variants from the hardcoded unofficial-builds.nodejs.org mirror are not repository-configurable and are signed by a different key, so they continue to be trusted over TLS.
Peer dependency resolution now reuses the peer contexts already recorded in the lockfile when those providers are still present in the dependency graph and still satisfy the peer ranges. This avoids unnecessary peer-context rewrites during lockfile regeneration. Current manifest choices remain authoritative: a newly added, explicitly updated, or aliased direct provider, a changed nested provider, or a locked version that no longer satisfies the range still takes precedence.
The lockfile verifier now checks that a registry entry pinning an explicit tarball URL points at the artifact the registry's own metadata lists for that name@version. Previously a tampered lockfile could pair a trusted name@version with an attacker-chosen tarball URL (and a matching integrity for those bytes), so the install fetched the attacker's bytes. A mismatch — or any entry that can't be confirmed against the registry — is rejected with ERR_PNPM_TARBALL_URL_MISMATCH. Non-registry resolutions (file:, git-hosted, etc.) and registry entries without an explicit tarball URL (the URL is reconstructed from name+version+registry, so it is inherently bound) are unaffected; non-standard registry tarball URLs (npm Enterprise, GitHub Packages) still pass because they match the metadata.
Fix pnpm update --recursive --lockfile-only <pkg>@​<version> crashing with Invalid Version when the catalog entry for <pkg> is a version range (e.g. ^21.2.10) and catalogMode is strict or prefer. The catalog–version comparison now skips the equality check when either side is a range rather than passing a range to semver.eq(), so range specifiers fall through to the existing mismatch handling instead of throwing #11570.
Avoided a Node.js crash when pnpm exits after network requests on Windows.
Fixed packages being materialized into the virtual store without their root-level files (package.json, LICENSE, README, root entrypoints) when multiple pnpm install processes ran against the same store/workspace concurrently. The fast import path used to destructively empty the shared target directory, so a concurrent importer could wipe files another importer had already written; if the surviving files included the package.json completion marker, every later install treated the broken directory as complete and never repaired it. The fast path now imports directly only when it can create the target directory exclusively, and otherwise builds the package in a private temp directory and atomically renames it into place #12197.
Fix dependency build scripts not running under the global virtual store (enableGlobalVirtualStore).
In a workspace install, dependency build scripts are deferred to a single rebuild pass (buildProjects). That pass resolved each package's location from the classic node_modules/.pnpm/<depPathToFilename> layout, which does not exist under the global virtual store — so native dependencies (e.g. packages using node-gyp / prebuild-install) were never built and failed to load at runtime (Cannot find module .../build/Release/*.node).
buildProjects now resolves the global-virtual-store projection directory (<storeDir>/links/<hash>, computed with the same graph hash the installer uses) when enableGlobalVirtualStore is set, and serializes concurrent builds of the same shared projection so parallel workspace projects don't race on the same directory.
Don't promote a runtime: dependency (such as the Node.js version from devEngines.runtime or pnpm runtime set) into a catalog when catalogMode is strict or prefer. A runtime: dependency round-trips to devEngines.runtime, which only recognizes the runtime: protocol; cataloging it rewrote the manifest entry to catalog:, which broke that round-trip, stranded it in devDependencies, and left devEngines.runtime untouched.
Skip lockfile minimumReleaseAge/trustPolicy verification for non-registry tarball protocols (for example file:), so local tarball dependencies are not incorrectly checked against npm registry metadata.
Improve pnpm audit performance by pruning non-vulnerable lockfile subtrees and stopping path enumeration once vulnerable findings reach the path cap.
Avoid crashing when the workspace state cache is partially written or malformed.
Set npm_config_user_agent for root lifecycle scripts during headless installs.
Preserve the integrity field of a remote (non-registry) tarball dependency when its lockfile entry is rebuilt. Re-resolving such a dependency without re-fetching it (for example via pnpm update, or when another dependency changes) produced a resolution with no integrity — URL/tarball resolvers only learn the integrity after the tarball is downloaded — so the previously recorded integrity was dropped, making later installs fail with ERR_PNPM_MISSING_TARBALL_INTEGRITY#12067.
Normalize a string repository field into the { type, url } object form when creating the publish manifest, matching npm's behavior. Some registries (e.g. Gitea/Codeberg) reject a string repository with a 500 Internal Server Error during pnpm publish#12099.
Preserve compatible optional peer versions already present in the lockfile when resolving dependencies.
Fixed inconsistent resolution of a peer dependency that is shared through a diamond. When a package peer-depends on both another package and one of that package's own peer dependencies (for example @typescript-eslint/eslint-plugin peer-depends on both @typescript-eslint/parser and typescript, and @typescript-eslint/parser peer-depends on typescript), pnpm no longer reuses a hoisted instance of the shared peer that was resolved against a different version #12079.
Added a new hoistingLimits setting for nodeLinker: hoisted installs, mirroring yarn's nmHoistingLimits. It accepts none (the default — hoist as far as possible), workspaces (hoist only as far as each workspace package), or dependencies (hoist only up to each workspace package's direct dependencies). Originally proposed in #6468, closing #6457.
Replaced enquirer with @inquirer/prompts for all interactive prompts. Fixes the update -i scrolling overflow bug where long choice lists were clipped in the terminal #6643.
User-facing changes:
pnpm update -i / pnpm update -i --latest: Scrolling now works correctly when many packages are available; the new library uses visual-line-aware pagination via usePagination
pnpm audit --fix -i: Same scrolling fix for vulnerability selection
pnpm run / pnpm exec (with verifyDepsBeforeRun=prompt): Confirmation prompt updated
Vim-style j/k keys still work for up/down navigation in all interactive prompts.
Internal: The OtpEnquirer and LoginEnquirer DI interfaces changed from { prompt } to { input } / { input, password } respectively. Plugins or custom builds that inject their own enquirer mock will need to update.
Staged publishes are now recognized in the trust scale. When a package version's registry metadata carries an approver field, it is treated as the strongest trust evidence (ranked above trusted publishers and provenance attestations), since staged publishes require 2FA publish approvals. This prevents false-positive trust downgrade errors when moving from a staged publish to a lower trust level #11887.
Patch Changes
Fix pnpm hanging during peer resolution when an aliased install pulls in transitive packages with mutual peer cycles at different depths in the dependency tree (for example, pnpm i nuxt@npm:nuxt-nightly@5x). Cycles whose members hit the findHit cache instead of running their own calculateDepPath are now short-circuited by sibling resolutions at the level where the cycle is detected, so the cached path promises no longer deadlock. #11999.
Fix pnpm dist-tag add and pnpm dist-tag rm against npmjs.org failing without --otp with [ERR_PNPM_UNAUTHORIZED] You must be logged in to set dist-tag … "You must provide a one-time pass. Upgrade your client to npm@latest in order to use 2FA.". pnpm now sends npm-auth-type: web on dist-tag writes and surfaces the resulting OTP challenge through the existing browser-based 2FA flow (the same withOtpHandling helper used by pnpm publish), so the browser opens, the user authenticates, and the dist-tag is set on retry. --otp=<code> continues to work via the classic flow.
Fix minimumReleaseAgeExclude handling in npm resolution fast paths so excluded packages do not get pinned to stale versions. Excludes are honored consistently during publishedBy metadata selection and cache-mtime shortcuts.
Fix the integrity field being dropped from the lockfile entry of a remote (non-registry) https-tarball dependency when an unrelated package is installed afterwards. URL/tarball resolvers do not return an integrity (it is only known after the tarball is downloaded), so when such a dependency was reused from the lockfile without being re-fetched, its integrity was lost. It is now carried over from the existing resolution. With pnpm's lockfile-integrity hardening, the missing integrity made subsequent --frozen-lockfile installs fail with ERR_PNPM_MISSING_TARBALL_INTEGRITY. #12001.
Skip dependency re-resolution when pnpm-lock.yaml is missing but node_modules/.pnpm/lock.yaml exists and still satisfies the manifest. pnpm install now reuses the materialized snapshot to regenerate pnpm-lock.yaml instead of walking the registry to rebuild it from scratch, turning the cache+node_modules variation into a near-no-op for users who deleted the lockfile but kept the install #11993.
--frozen-lockfile still refuses to proceed when pnpm-lock.yaml is absent — the regenerated lockfile must be committed, so failing loudly is the correct behavior for CI.
Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously, pnpm install (non-frozen) would log ERR_PNPM_TARBALL_INTEGRITY, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile.
pnpm install now exits with ERR_PNPM_TARBALL_INTEGRITY and a hint pointing at the new opt-in flag.
The only opt-in is pnpm install --update-checksums — narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable.
--force and pnpm update deliberately do not bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. --frozen-lockfile behavior is unchanged. --fix-lockfile keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass.
pnpm runtime set <name> <version> now saves the runtime to devEngines.runtime by default instead of engines.runtime. Pass --save-prod (or -P) to save it to engines.runtime instead #11948.
Patch Changes
Fix a credential disclosure issue where an unscoped _authToken (or _auth, or username + _password, or tokenHelper) defined in one source — ~/.npmrc, ~/.config/pnpm/auth.ini, a workspace .npmrc, CLI flags, etc. — would be sent as an Authorization header to whichever registry a different (potentially untrusted) source named. The same fix extends to client TLS credentials (cert, key) so they aren't presented to a registry their author didn't choose.
pnpm now rewrites each unscoped per-registry setting (_authToken, _auth, username, _password, tokenHelper, cert, key) to its URL-scoped form at load time, using the registry= value declared in the same source (or the npmjs default registry if the source declares none). A later layer overriding registry= therefore cannot pull an unscoped credential along, because it is already pinned to the URL its author intended. ca/cafile are intentionally not rescoped — they're trust anchors, not credentials, and corporate MITM-proxy setups rely on them applying globally.
Every rescope emits a deprecation warning telling the user where the setting was pinned and how to write it directly. npm has rejected unscoped credentials outright since npm@9, and pnpm intends to remove support in a future major release. To target a specific registry, write the setting URL-scoped (e.g. //registry.example.com/:_authToken=... or //registry.example.com/:cert=...).
@pnpm/network.auth-header: removed the defaultRegistry parameter from createGetAuthHeaderByURI and getAuthHeadersFromCreds. Now that credentials are URL-scoped at load time, the merged configByUri never contains the empty-string "default registry" placeholder slot, so re-keying it onto the merged default registry is no longer needed.
Fix pnpm deploy crashing with ENOENT: ... lstat '<deployDir>/node_modules' when configDependencies declares pacquet (pacquet or @pnpm/pacquet). The deploy directory never installs config dependencies, so the install engine they designate isn't on disk to invoke; the nested install now skips them.
Reject git resolutions whose commit field is not a 40-character hexadecimal SHA before invoking git. A malicious lockfile could otherwise smuggle a value such as --upload-pack=<command> through git fetch / git checkout, which on SSH or local-file transports executes the supplied command.
Limit concurrent project manifest reads while listing large workspaces to avoid EMFILE errors.
Reject patch files whose diff --git headers reference paths outside the patched package directory. Previously a malicious .patch file added via a pull request could write, delete, or rename arbitrary files reachable by the user running pnpm install.
Improve the log message that pnpm prints after auto-adding entries to minimumReleaseAgeExclude when minimumReleaseAge is set without minimumReleaseAgeStrict. The message previously referred to the internal "loose mode" terminology, which wasn't searchable in the docs; it now tells the user to set minimumReleaseAgeStrict to true if they want these updates gated behind a prompt instead #11747.
Reject dependency aliases that contain path-traversal segments (such as @x/../../../../../.git/hooks) when reading them from a package manifest or symlinking them into node_modules. A malicious registry package could otherwise use a transitive dependency key to make pnpm install create symlinks at attacker-chosen paths outside the intended node_modules directory.
Reject pnpm-lock.yaml entries whose remote tarball resolution: block is missing the integrity field. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that strips integrity:) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under --frozen-lockfile. pnpm now fails closed at lockfile-read time with ERR_PNPM_MISSING_TARBALL_INTEGRITY. Git-hosted tarballs (gitHosted: true or a URL on codeload.github.com / bitbucket.org / gitlab.com) and file: tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes.
Validate devEngines.runtime and engines.runtime version ranges for node, deno, and bun when onFail is set to error or warn. Previously these settings only had an effect with onFail: 'download' — the error and warn modes silently did nothing #11818. Violations now throw ERR_PNPM_BAD_RUNTIME_VERSION.
Require provenance before treating trusted publisher metadata as the strongest trust evidence.
Added pnpm stage with publish, list, view, approve, reject, and download subcommands for npm staged publishing.
Added a new setting trustLockfile. When true, pnpm install skips the supply-chain verification pass that re-applies minimumReleaseAge / trustPolicy='no-downgrade' to every entry in the loaded lockfile. The install treats the lockfile as already-trusted — useful for closed-source projects where every commit comes from a trusted author. Defaults to false; verification stays on by default. Set in pnpm-workspace.yaml.
Also cut the memory footprint of the verification pass itself: the per-(registry, name) trust-meta cache previously retained the full packument — dependency graphs, scripts, README, and per-version manifests — for the entire install. On large workspaces (~4k lockfile entries with minimumReleaseAge + trustPolicy: no-downgrade enabled) this could OOM CI runners with a 2GB heap cap. The cache now stores only the fields the trust check actually reads (time, per-version _npmUser.trustedPublisher, dist.attestations.provenance). The abbreviated-metadata cache is similarly projected to just the package-level modified field and the set of currently-listed version names. Fixes #11860.
Implemented pnpm pkg command natively, following npm pkg standards.
Implemented pnpm repo command natively, following npm repo standards.
Implemented pnpm set-script (alias ss) natively. Adds or updates an entry in the scripts field of the project manifest, supporting package.json, package.json5, and package.yaml formats.
Add a skip-manifest-obfuscation option for pnpm pack and pnpm publish. When enabled, the original packageManager field and publish lifecycle scripts are kept in the packed/published manifest instead of being stripped. The pnpm-specific pnpm field continues to be omitted.
Patch Changes
Fixed pnpm dlx failing with ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND when the installed package's CAS slot is missing its package.json. Observed in the wild for pnpm dlx node@runtime:<version> when the GVS slot was populated without the synthesized manifest runtime archives need (they don't ship a package.json of their own, so the synthesized one is the only way it gets there; an existing slot from an earlier code path that skipped the synthesis stays incomplete). The bin link itself is wired up from the resolution and remains valid, so dlx now falls back to the scopeless package name when the slot's manifest is unreadable — for single-bin packages (the dlx common case, including every runtime: spec) this matches what manifest.bin would have named. Multi-bin packages already require --package=<spec> <bin> to disambiguate and don't enter this code path.
Fixed non-determinism in pnpm dedupe and pnpm install when a dependency graph contains packages with transitive peer dependencies on each other (e.g. @aws-sdk/client-sts and @aws-sdk/client-sso-oidc) and auto-install-peers is enabled. The lockfile no longer flips between two equally-valid forms across consecutive runs. The root cause was that resolveDependencies pushed onto its pkgAddresses / postponedResolutionsQueue arrays from inside Promise.all-spawned callbacks, so completion-order timing leaked into the array order and downstream cyclic-peer suffix assignment. Fixes #8155.
Fixed a regression introduced by #11711 where pnpm add <github-shorthand> (and any other wanted-dependency whose alias can't be parsed from the user-supplied spec, e.g. tarball URLs or pnpm/test-git-fetch#sha) was silently dropped from the manifest update and from pendingBuilds. The alias-keyed lookup added in that PR couldn't find a wantedDependency whose alias was undefined at parse time but resolved to a package name only after fetching, so the entry never made it into specsToUpsert. Restored the original index-based pairing between directDependencies and wantedDependencies; the catalog-protocol preservation that PR was originally fixing is unaffected because it's driven by rdd.catalogLookup.userSpecifiedBareSpecifier, not by the lookup. Fixes the three rebuilds dependencies / rebuilds specific dependencies / rebuild with pending option failures in building/commands/test/build/index.ts.
Fixed pnpm add --config leaving orphan entries in pnpm-lock.env.yaml (the optional subdependencies of the previously resolved version of the updated config dependency).
When the install engine is delegated to pacquet via configDependencies, the user's CLI flags passed to pnpm install (e.g. --no-runtime, --prod, --dev, --no-optional, --node-linker, --cpu/--os/--libc, --offline, --prefer-offline) are now forwarded to pacquet's install subcommand verbatim. Previously pacquet was invoked with a fixed argument list, so flags like --no-runtime were silently dropped. Flag forwarding is gated on the command being install/i; add, update, and dedupe still don't forward (their flag surface doesn't line up with pacquet's install).
Fixed pnpm up (and pnpm add / pnpm remove) failing with pacquet_package_manager::outdated_lockfile when pacquet is declared in configDependencies. pnpm now passes --ignore-manifest-check to pacquet so its --frozen-lockfile check doesn't fire against the (pre-mutation) package.json pnpm hasn't written yet #11797. Requires a pacquet release that supports the flag — bump PACQUET_VERSION in the e2e tests once it ships.
Mark optional subdependency snapshots of config dependencies with optional: true in the env lockfile, matching how optional dependencies are recorded elsewhere in pnpm-lock.yaml. Previously, snapshots for the platform-specific subdeps pulled in via a config dep's optionalDependencies were written as empty objects, which was inconsistent with the rest of the lockfile and made it look like those non-host platform variants were required.
Fix pickRegistryForPackage returning the wrong registry for an unscoped npm: alias under a scoped local name. A manifest entry like "@​private/foo": "npm:lodash@^1" was routing the lodash fetch through registries["@​private"], even though lodash is unscoped and doesn't live on that registry. The npm-alias branch now returns the alias target's own scope (or null for an unscoped target, falling through to registries.default) instead of leaking into the local key's scope.
Don't print "Installing config dependencies..." when config dependencies are already installed and nothing needs to be fetched, re-linked, or removed.
Experimental: Adding @pnpm/pacquet (the Rust port of pnpm) to configDependencies in pnpm-workspace.yaml now delegates the materialization phase of pnpm install to the pacquet binary. pnpm still owns dependency resolution; pacquet only fetches and imports from the freshly-written lockfile. This is an opt-in preview of the Rust install engine #11723.
To configure pacquet in a project, run:
pnpm add @​pnpm/pacquet --config
You'll see changes in pnpm-workspace.yaml and pnpm-lock.yaml that should be committed. If you experience any issues with pacquet, please let us know by mentioning this in the GitHub issue you create.
configDependencies now resolve and install one level of optionalDependencies declared by the config dependency, with os/cpu/libc platform filtering applied at install time. This unlocks the esbuild/swc-style pattern where a package ships platform-specific binaries via optionalDependencies — a config dependency can now do the same and have the matching binary symlinked next to it in the global virtual store, so require('pkg-platform-arch') from inside the config dependency resolves correctly.
The env lockfile records all platform variants regardless of host platform, so it remains portable across machines. Each entry in a config dependency's optionalDependencies must declare an exact version — ranges and tags are rejected to keep installs reproducible.
Implement the documented pnpm login --scope <scope> flag. The scope is normalized (a leading @ is added if missing; blank values are ignored) and an @<scope>:registry=<registry> mapping is written to the pnpm auth file alongside the auth token. Subsequent installs of @<scope>/* packages then route to the chosen registry. Previously pnpm login --scope foo errored with Unknown option: 'scope' despite the flag being listed in the online documentation #11716.
pnpm outdated and pnpm update --interactive now report Node.js, Deno, and Bun runtimes installed as project dependencies (runtime: specifiers). Previously these were silently skipped.
Patch Changes
Fix cafile=<relative-path> in .npmrc being read from the wrong directory when pnpm is invoked from a different cwd (e.g. pnpm --dir <project> install from a CI wrapper or monorepo script). The path is now resolved against the directory of the .npmrc that declared it, not process.cwd(). Before this fix the CA file silently failed to load — the install proceeded without the configured CA and the user only saw TLS errors against a private registry, with no log line tying back to the wrongly resolved path #11624.
Fix config.registry getting a trailing slash appended when registry is set in .npmrc and no registries.default is provided by pnpm-workspace.yaml. The sync from registries.default to config.registry introduced in #11744 now only fires when the workspace manifest actually contributes a different default.
Fix global add/update to handle minimumReleaseAge policy violations instead of surfacing an internal resolver guardrail error.
Fix two crashes with injectWorkspacePackages: true when the lockfile has been pruned (e.g. by turbo prune --docker):
Cannot use 'in' operator to search for 'directory' in undefined: a peer-dependency-variant injected snapshot inherits its resolution from the base packages: entry; when a pruner drops that base entry the readers crash. convertToLockfileObject now reconstructs the directory resolution from the file: depPath at load time — a single normalization point, so every reader sees a fully-formed snapshot.
ERR_PNPM_ENOENT on node_modules/.bin/<tool>: after prepare/postinstall, runLifecycleHooksConcurrently re-imported each injected workspace package; the scanDir-into-filesMap workaround fed target-internal paths to the importer, which the makeEmptyDir fast path (#11088) then wiped. Drop the workaround and pass keepModulesDir: true so the importer preserves the target's existing node_modules (bin links + transitive deps) and source files keep their hardlinks.
Fixed pnpm login and pnpm logout ignoring registries.default from pnpm-workspace.yaml#10099.
Fix the minimumReleaseAge (publishedBy) maturity shortcut to be inclusive at the cutoff. Previously, abbreviated metadata whose modified field equalled the cutoff fell off the fast path and triggered a full-metadata re-fetch (or a MISSING_TIME error when full metadata wasn't permitted). Since modified is an upper bound on every version's publish time, modified == publishedBy already implies every version passes the per-version <= filter in filterPkgMetadataByPublishDate, so the shortcut now accepts the boundary case directly. Strictly > (was >=) at the rejection branch.
Honor publishConfig.access when publishing packages.
pnpm install now re-validates pnpm-lock.yaml entries against the active minimumReleaseAge and trustPolicy: 'no-downgrade' policies before any tarball is fetched. Lockfiles resolved elsewhere (committed to the repo, restored from a CI cache, produced by an older pnpm) under a weaker or absent policy can no longer install a freshly-published or trust-downgraded version silently. Violating entries abort the install with ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION, ERR_PNPM_TRUST_DOWNGRADE, or the generic ERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION when both policies trip in the same batch; minimumReleaseAgeExclude and trustPolicyExclude are honored. Verification results are cached so repeat installs against an unchanged lockfile take a fast path, and pnpm shows a transient progress line while the registry round-trip runs.
When fresh resolution picks an immature version, the behavior depends on minimumReleaseAgeStrict:
Loose mode — the default, in effect whenever minimumReleaseAge keeps its built-in 24-hour value — auto-adds the immature picks to minimumReleaseAgeExclude in pnpm-workspace.yaml and lets the install proceed. A single info message lists what was persisted.
Strict mode in an interactive terminal collects every immature direct AND transitive pick in one pass and prompts once with the full list. Approving adds them to minimumReleaseAgeExclude and the install continues; declining aborts before the lockfile, package.json, or node_modules is touched.
Strict mode in CI (or any non-TTY context) aborts with ERR_PNPM_NO_MATURE_MATCHING_VERSION listing every offending entry, instead of failing on the first one the resolver hit.
minimumReleaseAgeStrict auto-enables whenever the user explicitly sets minimumReleaseAge (CLI flag, env var, global config.yaml, or pnpm-workspace.yaml); set minimumReleaseAgeStrict: false to keep loose-mode auto-collect even with an explicit minimumReleaseAge value. Closes #10438, #10488, #11687.
Allow redundant trailing base64 padding in .npmrc auth values and report invalid auth base64 with a pnpm error.
Make pnpm self-update respect minimumReleaseAge (and minimumReleaseAgeExclude) when resolving which pnpm version to install.
When the latest dist-tag points to a version newer than the configured age threshold, self-update now selects the newest mature version instead unless excluded by minimumReleaseAgeExclude.
Also makes dlx and outdated surface invalid minimumReleaseAgeExclude patterns under the same ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE error code already used by install, instead of leaking the internal ERR_PNPM_INVALID_VERSION_UNION / ERR_PNPM_NAME_PATTERN_IN_VERSION_UNION codes.
Global installs respect global config build policy (e.g., dangerouslyAllowAllBuilds from config.yaml) when GVS is enabled #9249.
The global virtual-store (GVS) default allowBuilds = {} was applied before workspace manifest settings were read and before global config values (stripped by extractAndRemoveDependencyBuildOptions) were re-applied via globalDepsBuildConfig. This caused hasDependencyBuildOptions to return true (because {} is not null), blocking restoration of global config values like dangerouslyAllowAllBuilds. As a result, global installs skipped all build scripts even when the config explicitly allowed them.
This fix moves the GVS default to after workspace manifest reading and globalDepsBuildConfig re-application, so that:
Global config dangerouslyAllowAllBuilds is properly restored (if set and no workspace policy exists)
Empty {} is only applied as a last resort when no policy is configured anywhere
Honor --silent when verifyDepsBeforeRun: install auto-installs dependencies before pnpm run or pnpm exec, preventing install output from being written to stdout #11636.
Fix lockfile parsing failures when pnpm-lock.yaml contains CRLF line endings and multiple YAML documents #11612.
Anchor the side-effects-cache key and global-virtual-store hash to the project's script-runner Node — engines.runtime pin when present, shell node otherwise — instead of pnpm's own runtime.
ENGINE_NAME (the <platform>;<arch>;node<major> prefix used as the side-effects-cache key and the engine portion of the GVS hash) was computed from process.version — the Node that runs pnpm itself. That was wrong in two situations:
@pnpm/exe SEA bundle. The bundle has its own embedded Node, not the node on the user's PATH that actually spawns lifecycle scripts. Two pnpm installations on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store across two Node majors even though both installs would run scripts on the same shell node.
engines.runtime / devEngines.runtime pin. When a project pins a Node version via devEngines.runtime (pnpm v11+), pnpm downloads that Node into node_modules/node/ and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node — so two installs of the same project with two different runner Nodes would still disagree on the GVS slot path even though scripts run on the same pinned Node.
Three changes:
@pnpm/engine.runtime.system-node-version now exports engineName(nodeVersion?). Resolves the version in this order: explicit override → getSystemNodeVersion() (which already prefers node --version over process.version in SEA contexts) → process.version.
@pnpm/deps.graph-hasher now exports findRuntimeNodeVersion(snapshotKeys) — scans an iterable of lockfile snapshot keys for a node@runtime:<version> entry and returns its bare version string. calcDepState and calcGraphNodeHash/iterateHashedGraphNodes accept a nodeVersion? (in the options bag for the first, as a trailing parameter / ctx field for the others), forwarded to engineName(). The default (no override) preserves the pre-change behaviour. The legacy ENGINE_NAME constant in @pnpm/constants is unchanged so external consumers and existing tests keep working; in non-SEA, non-pinned contexts every value lines up.
Every install-side caller of the graph-hasher (@pnpm/installing.deps-resolver, @pnpm/installing.deps-restorer, @pnpm/installing.deps-installer, @pnpm/building.during-install, @pnpm/building.after-install, @pnpm/deps.graph-builder) now derives the project's pinned runtime via findRuntimeNodeVersion(Object.keys(graph)) once per invocation and threads it through.
On upgrade, two one-time GVS slot churns are possible:
SEA-pnpm users without a runtime pin: slots that previously hashed under the embedded-Node major (e.g. node26) now hash under the shell-Node major (e.g. node24), matching what pacquet, the npm-published pnpm package, and any other pnpm-compatible tool already produce.
Projects with a devEngines.runtime pin: slots that previously hashed under the runner's Node major now hash under the pinned Node major, matching what the lifecycle scripts will actually run on.
In both cases the old slots become prune-eligible.
Resolve the GVS hash's engine portion per-snapshot when a dependency declares its own engines.runtime, instead of using an install-wide value.
Pnpm's resolver desugars a dep's engines.runtime into dependencies.node: 'runtime:<version>', and the bin linker spawns that dep's lifecycle scripts through the pinned Node downloaded into <pkgDir>/node_modules/node/. The GVS hash and the side-effects-cache key prefix were still anchored to the install-wide runtime — so a pinning snapshot's slot encoded the wrong Node major, and a reinstall on the same host could read the cached side-effects under a key whose <platform>;<arch>;node<major> triple disagreed with the Node the build actually ran on.
Per-snapshot resolution now matches what bins/linker already does on a per-package basis:
@pnpm/deps.graph-hasher adds readSnapshotRuntimePin(children) — reads the node entry from one snapshot's graph children and extracts the version from a node@runtime: value. Pairs with the existing findRuntimeNodeVersion(snapshotKeys) install-wide fallback (also now exported from @pnpm/deps.graph-hasher rather than @pnpm/engine.runtime.system-node-version, where it was a poor fit — system-node-version is about probing the host Node, not parsing lockfile-derived strings).
calcDepState and calcGraphNodeHash consult readSnapshotRuntimePin(graph[depPath].children) first and only fall back to the install-wide nodeVersion parameter when the snapshot doesn't pin its own Node.
Pacquet mirrors the same precedence at the calc_graph_node_hash call site in package-manager/src/virtual_store_layout.rs — a new find_own_runtime_node_major(snapshot) helper reads each snapshot's dependencies for a node entry with Prefix::Runtime and overrides the install-wide engine when present.
On upgrade, snapshots of dependencies that declare their own engines.runtime re-hash under that dep's pinned Node instead of the install-wide value. The old slots become prune-eligible. Closes #11690.
Fixed pnpm publish failing with a 404 when authentication relied on OIDC trusted publishing alongside an .npmrc written by actions/setup-node (_authToken=${NODE_AUTH_TOKEN}) without NODE_AUTH_TOKEN being set. Unresolved ${VAR} placeholders in auth values are now treated as empty rather than passed through verbatim, so the literal placeholder no longer surfaces as a bearer token when OIDC fallback is the intended auth source #11513.
Fix devEngines.packageManager (singular form, without onFail) defaulting to onFail: "error" instead of the documented pmOnFail: "download". As a result, a project that pinned a different pnpm version via devEngines.packageManager and ran pnpm install from a mismatched pnpm version failed with a hard error, even though the migration table from managePackageManagerVersions: true to pmOnFail: download (default) promises the install would auto-download the wanted version #11676.
The array form of devEngines.packageManager keeps its existing per-element defaults (error for the last entry, ignore for the rest), since those reflect explicit prioritization by the user. Explicit onFail values continue to win.
Fix devEngines.packageManager not writing packageManagerDependencies to pnpm-lock.yaml when the lockfile lacks an env-doc entry. Previously the lockfile sync skipped resolution unless an existing packageManagerDependencies.pnpm entry needed refreshing, so a fresh install without onFail: "download" left the resolved pnpm version unrecorded — contradicting the documented behavior that the resolved version is stored in pnpm-lock.yaml#11674.
Warn when package.json contains a legacy pnpm field with settings pnpm no longer reads from package.json (e.g. pnpm.overrides, pnpm.patchedDependencies). Previously these were silently ignored after the upgrade from v10, leaving users unaware that their overrides/patched dependencies had stopped taking effect #11677.
convertEnginesRuntimeToDependencies: switch the runtime-dependency write to Object.defineProperty so the CodeQL js/prototype-polluting-assignment rule treats the assignment as safe regardless of the property name (follow-up to #11609).
Address CodeQL static-analysis findings: guard manifest dependency writes against prototype-polluting keys (__proto__, constructor, prototype), and replace a potentially super-linear semver-detection regex in registry 404 hints with an O(n) parser.
Strip sec-fetch-* headers from outgoing HTTP requests. These headers are automatically added by undici's fetch() implementation per the Fetch spec but cause Azure DevOps Artifacts to return HTTP 400 for uncached upstream packages, as ADO interprets them as browser requests #11572.
Fix minimumReleaseAge handling for cached abbreviated metadata.
The version-spec cache fast path no longer rethrows ERR_PNPM_MISSING_TIME under strictPublishedByCheck; it now falls through to the registry-fetch path, consistent with the adjacent mtime-gated cache block.
When the registry returns 304 Not Modified for a package whose cached metadata is abbreviated (no per-version time), pnpm now re-fetches with fullMetadata: true if minimumReleaseAge is active and the package was modified after the cutoff. The upgraded metadata is persisted to disk so subsequent installs don't repeat the fetch. Previously the abbreviated meta was used as-is and the maturity check fell back to its warn-and-skip path, silently bypassing the quarantine and emitting a misleading "metadata is missing the time field" warning.
Fix pnpm upgrade --interactive --latest -r not respecting named catalog groups. Previously, upgrading a dependency using a named catalog (e.g. "catalog:foo") would incorrectly rewrite package.json to "catalog:" and place the updated version in the default catalog instead of the named one #10115.
Fixed optimisticRepeatInstall skipping pnpm-lock.yaml merge conflict resolution when the existing node_modules state appears up to date.
Fix minimumReleaseAge / resolutionMode: time-based installs failing on lockfiles whose time: block is missing entries. The npm-resolver's peek-from-store fast path now surfaces publishedAt from the lockfile rather than discarding it, and falls through to a registry metadata fetch when the time-based cutoff can't be computed from the data on hand.
Skip installability validation when scanning workspace projects in checkDepsStatus (run by verifyDepsBeforeRun). Previously the status check called findWorkspaceProjects, which validates each project's engines and os/cpu/libc and warns about useless fields in non-root manifests — work that the install pipeline already performs. With no nodeVersion threaded through, the engine check also fell back to the system Node from PATH and emitted spurious "Unsupported engine" warnings before scripts ran. Status-only callers now use findWorkspaceProjectsNoCheck; install paths continue to validate.
Fixed pnpm add <alias>:@​scope/pkg for named registries. The local resolver was claiming any specifier containing / as a local directory, so pnpm add bit:@​teambit/bit (with bit configured under namedRegistries) installed a bogus link to bit:@​teambit/bit/ instead of resolving from the configured registry. The local resolver now runs after the named-registry resolver in the resolution chain.
Updated @zkochan/cmd-shim to 9.0.3. The sh shim it writes for .cmd / .bat targets now escapes the /C switch as //C, so it survives the path translation Git Bash applies when launching cmd.exe. Without this, a bare /C was rewritten to C:\ before reaching cmd.exe — the switch was dropped, cmd started interactively, and the calling script saw the cmd banner instead of the wrapped command's output. Affects any cmd-shim-wrapped batch script invoked from Git Bash / MSYS / Cygwin on Windows. See pnpm/cmd-shim#55.
Added pnpm audit signatures to verify ECDSA registry signatures for installed packages against keys from /-/npm/v1/keys#7909. Scoped registries are respected, and registries without signing keys are skipped.
Added support for installing packages from the GitHub Packages npm registry via a built-in gh: prefix (e.g. pnpm add gh:@​acme/private), and, more broadly, for arbitrary named registries in the style of vlt's named-registry aliases. Authentication is picked up from the existing per-URL .npmrc entries (e.g. //npm.pkg.github.com/:_authToken=...), so no separate auth mechanism is required.
Additional aliases — or an override for the built-in gh alias, for GitHub Enterprise Server — can be configured under namedRegistries in pnpm-workspace.yaml:
With this, work:@​corp/lib@^2.0.0 resolves against https://npm.work.example.com/. #11324.
Allow setting sbom spec version using --sbom-spec-version#11389.
Add --no-runtime flag (config: runtime=false) to skip installing runtime entries (e.g. Node.js downloaded via devEngines.runtime) without modifying the lockfile. The lockfile keeps the runtime entry so frozen-lockfile validation still passes; only the runtime fetch and .bin linking are skipped. Useful in CI matrices where the runtime is provisioned externally (e.g. via pnpm runtime -g set node <version>) before pnpm install runs.
Added the pnpm bugs command that opens a package's bug tracker URL in the browser. With no arguments, it reads the current project's package.json; with one or more package names, it fetches each package's metadata from the registry and opens its bug tracker. Falls back to <repository>/issues when the bugs field is missing #11279.
Added pnpm owner command to manage package owners on the registry.
Patch Changes
Added "published X ago by Y" information to the pnpm view command output, similar to npm view. This is useful when comparing against minimumReleaseAge.
For example, pnpm view pnpm now shows:
published 17 hours ago by GitHub Actions
pnpm publish now honors the configured HTTP/HTTPS proxy (including https_proxy/http_proxy/no_proxy environment variables) when polling the registry's doneUrl during the web-based authentication flow. Previously the poll bypassed the proxy, causing the registry to respond 403 from a different source IP and the login to never complete #11561.
pnpm add -g now installs each space-separated package into its own isolated directory by default. To bundle multiple packages into the same isolated install (so that they share dependencies and are removed together), pass them as a comma-separated list. For example:
pnpm add -g foo bar installs foo and bar as two independent globals — removing one does not affect the other.
pnpm add -g foo,bar qar bundles foo and bar into a single isolated install while qar is installed on its own.
pnpm runtime set <name> <version> no longer fails in the root of a multi-package workspace with the ADDING_TO_ROOT error. Installing the workspace root is a valid target for a runtime, so the command now bypasses that safety check.
Fix pnpm --version hanging for the lifetime of the worker pool after the version was printed. main.ts's --version short-circuit returned before reaching the command-handler finally that calls finishWorkers(), so the worker pool that switchCliVersion had spawned during integrity resolution stayed alive and held the Node event loop open. The CLI entry now runs finishWorkers() from its own finally, so every exit path tears the pool down.
Repro: pnpm --version in a workspace whose devEngines.packageManager version already matches the running pnpm + onFail: "download". switchCliVersion resolves the integrity (spawning workers), finds nothing to swap, returns. The version prints, then the process hangs.
Fixed installation of GitLab-hosted dependencies. pnpm now downloads the tarball from https://gitlab.com/<user>/<project>/-/archive/<sha>/<project>-<sha>.tar.gz instead of the GitLab API endpoint that contained an encoded slash (%2F) between user and project. The encoded slash both triggered 406 Not Acceptable responses from GitLab and produced virtual store directory names that Node refused to import (ERR_INVALID_MODULE_SPECIFIER) #11533.
Honor NPM_CONFIG_USERCONFIG (and its lowercase npm_config_userconfig form) as a low-priority fallback when locating the user-level .npmrc. This restores compatibility with environments that point npm at a custom auth file via that env var — most notably actions/setup-node, which writes registry credentials to ${runner.temp}/.npmrc and exports NPM_CONFIG_USERCONFIG to reference it. Without this, GitHub Actions workflows using actions/setup-node to authenticate to private registries broke after upgrading to pnpm v11. PNPM-prefixed env vars and npmrcAuthFile from the global config.yaml continue to take precedence #11539.
Fix pnpm pack not bundling dependencies listed in bundleDependencies (or bundledDependencies). The npm-packlist upgrade in pnpm 11 changed its API to require the caller to pre-populate the dependency tree, which the wrapper was not doing — bundleDependencies were silently dropped from the tarball #11519.
Fixed the pnpm CLI crashing with a confusing SyntaxError: Invalid regular expression flags instead of printing a clear "requires Node.js v22.13" error when launched on an unsupported Node.js version. The Node.js version check in bin/pnpm.mjs was effectively dead code because the static import of the bundled dist/pnpm.mjs was hoisted by the ES module loader and parsed before the check could run #11546.
Fixed pnpm --prefix=<dir> install overwriting the existing pnpm-workspace.yaml in <dir> with set this to true or false placeholders. The renamed --prefix option (which maps to dir) was not honored when locating the workspace root, so the workspace manifest's allowBuilds settings were not loaded into config and got clobbered when ignored builds were auto-populated #11535.
Fixed pnpm publish --provenance failing with a 422 from the registry when the package version contained semver build metadata (e.g. 1.0.0-canary.0+abc1234). The +<build> segment is now stripped before packing so that the version embedded in the tarball, the metadata sent to the registry, and the sigstore provenance subject all agree #11518.
Restored the heuristic that preserves tarball URLs in pnpm-lock.yaml when they cannot be derived from name+version+registry, even with the default lockfileIncludeTarballUrl: false. Without this, pnpm install --frozen-lockfile from an empty store fails with ERR_PNPM_FETCH_404 for packages on registries that serve tarballs from a non-standard path — most notably GitHub Packages (https://npm.pkg.github.com/download/<scope>/<name>/<version>/<hash>) and JSR. lockfileIncludeTarballUrl: true continues to force the URL into the lockfile for every package #11276.
Run preversion, version, and postversion lifecycle scripts for pnpm version.
Fixed ERR_PNPM_BAD_TARBALL_SIZE when a registry serves tarballs with an end-to-end Content-Encoding (e.g. gzip). Tarballs are already compressed, so the fetcher now requests them with Accept-Encoding: identity (matching pnpm v10's effective behavior) and, as defense in depth against misbehaving servers, no longer enforces the strict Content-Length check when the response declares a Content-Encoding — Content-Length in that case refers to the encoded payload, not the decoded bytes the fetch implementation yields #11506.
Restore the execute bit on the node-gyp shims packed inside @pnpm/exe (dist/node-gyp-bin/node-gyp, dist/node-gyp-bin/node-gyp.cmd, and dist/node_modules/node-gyp/bin/node-gyp.js). Without this, pnpm/action-setup's standalone path (used on runners with Node.js < 22.13) failed any install whose lifecycle script invoked node-gyp rebuild with sh: 1: node-gyp: Permission denied#11483.
Fixed the pn, pnpx, and pnx aliases failing in Git Bash / MSYS2 on Windows when pnpm was installed via @pnpm/exe (or after pnpm self-update) #11486. Running pnpx (or pnx) printed the cmd.exe banner and dropped the user into an interactive command prompt instead of running pnpm dlx. The bin field rewrite on Windows was pointing those aliases at .cmd files; cmd-shim's Bash shim for a .cmd target wraps it in exec cmd /C ..., and MSYS2 mangles /C into a Windows path before cmd.exe sees it. The aliases are now .exe hardlinks of the SEA binary, which detects which name it was launched as via process.execPath and prepends dlx for pnpx / pnx.
Fix pnpm install recreating node_modules after pnpm fetch. pnpm fetch records empty hoistPattern and publicHoistPattern in .modules.yaml; since v11 removed the explicit-config gate, the follow-up install treated those as a hoist-pattern change and purged the modules directory. The fetch step now flags the modules manifest with virtualStoreOnly: true so the next install skips the hoist-pattern comparison and completes the missing post-import linking in place #11488.
Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.
A new gitHosted: true field is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.
Allow user-level preferences in the global config.yaml. The following settings can now be set in ~/.config/pnpm/config.yaml (or via pnpm config set --location global) instead of being restricted to pnpm-workspace.yaml: agent, globalVirtualStoreDir, initPackageManager, initType, registrySupportsTimeField, scriptShell, shellEmulator, sideEffectsCache, sideEffectsCacheReadonly, stateDir, strictDepBuilds, trustPolicy, trustPolicyExclude, trustPolicyIgnoreAfter, updateNotifier, useStderr, verifyDepsBeforeRun, verifyStoreIntegrity, virtualStoreDir, virtualStoreDirMaxLength#11474.
Make trusted publishing (OIDC) take precedence over a configured static _authToken in pnpm publish, mirroring the npm CLI's behavior. When OIDC succeeds, the OIDC-derived token overrides any pre-configured _authToken; when OIDC is not applicable (no CI environment, exchange fails, registry has no trusted publisher configured), the static token is used as a fallback. This applies on every package during recursive publish, so each workspace package independently attempts trusted publishing.
Additionally, the NPM_ID_TOKEN env var is now honored as a CI-agnostic injection point for an OIDC ID token. Previously OIDC was only attempted on GitHub Actions or GitLab; now any CI provider that exposes its own OIDC mechanism (e.g. CircleCI's CIRCLE_OIDC_TOKEN_V2, Buildkite, etc.) can forward its token via NPM_ID_TOKEN and trusted publishing will work without pnpm needing to recognize the provider explicitly.
--pm-on-fail=ignore (and other universal options like --loglevel, --reporter) is now honored when combined with --help or --version. Previously the CLI argument parser short-circuited those flags before universal options were preserved, so pnpm audit --pm-on-fail=ignore --help and pnpm --pm-on-fail=ignore --version reported the strict packageManager mismatch instead of running the requested action #11487.
Fix a regression where pnpm --recursive --filter '!<pkg>' run/exec/test/add would include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative --filter arguments are provided, matching the documented behavior. To include the root, pass --include-workspace-root#11341.
Restore npm-CLI-compatible --json stdout output for pnpm publish (#11476). pnpm 11 reimplemented publish natively (#10591) and inadvertently dropped the per-package JSON object that pnpm 10 emitted transitively via the npm CLI, silently breaking downstream tooling — most notably nx release publish, which parses stdout JSON to confirm success (nrwl/nx#35575). On success, the output is now:
pnpm publish -r --json → array of those objects, mirroring pnpm pack --json's shape choice.
pnpm publish -r --report-summary → existing pnpm-publish-summary.json envelope { publishedPackages: [...] } is preserved, but each entry is upgraded to the same per-package shape (additive — name and version are still present).
pnpm config get @​<scope>:registry now reports the same URL that pnpm publish and the resolvers actually use. Previously, config get only consulted .npmrc, while publish/install used the merged map that includes pnpm-workspace.yaml's registries block — so the two could diverge silently and a publish could go to the wrong registry #11492.
Fix pnpm_config_npmrc_auth_file and pnpm_config_userconfig env vars not actually loading the custom .npmrc. The env vars were parsed and assigned to the resolved config, but only after loadNpmrcConfig had already read the default ~/.npmrc — so the custom file path was set but never read. The relevant env vars are now consulted before the user-level .npmrc is loaded #11465.
Preserve the original key order in pnpm-workspace.yaml when updating it. Existing keys keep their position, and new keys are inserted in alphabetical position when the existing keys are already sorted (with a leading packages key allowed) or appended at the end otherwise.
Fixed pnpm self-update on installations originally set up by pnpm v10. v10 added PNPM_HOME directly to PATH and wrote a pnpm bootstrap shim there. v11 setup writes shims under PNPM_HOME/bin instead, so when a v10 user upgrades to v11 the legacy shim at PNPM_HOME keeps pointing into the old .tools/<version> install — pnpm --version continues to report the pre-update version even though the new version was installed under global/v11. Self-update now detects this layout, refreshes the legacy shims so the upgrade actually takes effect, and prints a hint suggesting pnpm setup to migrate PATH to the v11 layout. #11464.
Print a warning when settings that are not allowed in the global config file (e.g. nodeLinker, hoistPattern) are present in config.yaml and silently ignored. Previously these settings were dropped without any feedback, leaving users unsure why their global configuration had no effect. The warning suggests moving those settings to a project-level pnpm-workspace.yaml, or sharing them across projects via config dependencies.
Throw a pnpm error when overrides has an invalid shape or contains a non-string value.
Validate all readPackage dependency map fields, including devDependencies, and reject falsy non-object invalid values instead of silently accepting them.
Prevent crashes during pnpm config, pnpm set, and pnpm get by tolerating configDependencies install failures. For these commands, a failure to install configDependencies (for example because the registry auth token has not been written yet) is now logged at debug level and the command proceeds. All other commands still surface the install error #10684.
Treat allowBuilds as an install-state input and clear previously ignored builds when they are explicitly disallowed.
Fixes #10594, catalogs not being read from the workspace when using the catalog: protocol with the pnpm dlx / pnpx command, resulting in a catalog entry not found error.
Accept PNPM_CONFIG_* (uppercase) environment variables in addition to pnpm_config_*. Previously, only the lowercase form was honored, so env vars renamed per the v11 migration guide (e.g. PNPM_CONFIG_USERCONFIG) silently had no effect on case-sensitive systems like macOS and Linux #11465.
Drop the darwin-x64 artifact from @pnpm/exe and from the GitHub release page. The Node.js SEA mechanism pnpm pack-app uses produces a binary that segfaults at startup on Intel Macs because of an upstream Node.js bug (nodejs/node#62893, tracked alongside #59553; the Node.js team has opted not to fix it on the grounds that x64 macOS is being phased out). Re-signing with codesign or ldid doesn't help — the corruption is in LIEF's Mach-O surgery, before signing.
Intel Mac users should install pnpm via npm install -g pnpm (uses the system Node.js, no SEA), or stay on pnpm 10.x. @pnpm/exe's preinstall on Intel Mac now exits with a clear error pointing at these alternatives.
pnpm dlx (and pnpx/pnx/pnpm create) now runs the same interactive approve-builds prompt as pnpm add -g when the package being launched depends on transitive packages with install scripts. Previously, the v11 strictDepBuilds default made dlx fail with ERR_PNPM_IGNORED_BUILDS and required users to re-run with --allow-build=<pkg> for every offending dependency. dlx also now removes the partially-populated cache directory when the install fails, so a subsequent run starts clean instead of reusing a broken install whose builds were silently skipped #11444.
72629fc: Fix pnpm -g ls --json and pnpm -g ls --parseable so they emit valid JSON and parseable output respectively, matching pnpm 10 behavior. Since the isolated global packages refactor in pnpm 11, the global list command had a custom path that always printed plain text and ignored --json/--parseable, which broke tools like npm-check-updates that parse the JSON output #11440.
pnpm -g ls --depth=<n> (with n > 0) now errors when more than one isolated global install would be involved, since each install has its own lockfile and merging their transitive trees would be incoherent. When the request can be narrowed to a single install group, the regular list flow is used and the full dependency tree is shown.
Fixed pnpm publish to honor publishConfig.registry from package.json when publishing a single package. The native publish flow introduced in v11 was reading the registry from .npmrc only, ignoring the per-package override #11419.
When strictPeerDependencies is true, the ERR_PNPM_PEER_DEP_ISSUES error once again renders the peer dependency issues inline using the same format as pnpm peers check, so users (and CI tools like Renovate) can see what failed without running pnpm peers check separately #11439.
The WARN and error code labels in pnpm's output now wrap in brackets ([WARN], [ERR_PNPM_FOO]). Previously the labels relied entirely on a colored background to stand out, which meant they blended into the surrounding text in terminals without color (e.g. when NO_COLOR is set or output is piped). The brackets are painted in the same color as the badge background, so they appear as ordinary padding in color-capable terminals — only the no-color rendering changes.
Fixed pnpm ci not reinstalling workspace package node_modules directories after the clean step #11427.
Remove pnpm's workspace state file when cleaning node_modules so pnpm ci performs a fresh install after the clean step.
Do not remove pnpm-lock.yaml during pnpm clean when lockfile: true is configured in pnpm-workspace.yaml. The lockfile is only removed when the --lockfile option is passed to pnpm clean.
pnpm self-update (with no version argument) no longer downgrades pnpm when the registry's latest dist-tag points to an older release than the currently active version. Run pnpm self-update latest to force a downgrade #11418.
minimumReleaseAgeStrict now defaults to true whenever the user explicitly sets minimumReleaseAge (via pnpm-workspace.yaml, the global config.yaml, the CLI, or pnpm_config_* env vars).
Fix too many open files error sometimes happening on Windows, when creating command shims in node_modules/.bin#11412.
Fix ERR_PNPM_FETCH_404 when installing a project whose lockfile depends on a file: tarball. The previous behavior dropped the tarball field from file: and git-hosted resolutions when lockfile-include-tarball-url=false (the default), even though those URLs cannot be reconstructed from the package name, version, and registry #11407.
Fix ENOENT symlink failure when pnpm add -g triggers the approve-builds prompt. The global add flow used to forward an absolute modulesDir (<installDir>/node_modules) into the install run by approve-builds. The install layer treated modulesDir as a path relative to lockfileDir and joined it again, producing a doubled path on Windows because path.join does not collapse an embedded absolute path. The hoist step then tried to mkdir and symlink under <installDir>\<installDir>\node_modules\.pnpm\node_modules\... and failed with ENOENT#11403.
Fixed packageManagerDependencies going stale when pnpm is invoked through corepack. The lockfile sync (and the devEngines.packageManager version check) previously ran only when pnpm was invoked directly; under corepack the entire block was skipped, so a stale entry would persist even after the running pnpm version changed. The lockfile sync now runs regardless of how pnpm was invoked, while the pnpm-managed version switch (onFail: 'download') remains skipped under corepack so it doesn't fight corepack's own version selection #11397.
Fix recursive publish summaries to report the manifest from publishConfig.directory when packages publish from a generated directory #11239.
Fix negated os / cpu entries (e.g. ["!win32"]) being incorrectly rejected when supportedArchitectures expands to multiple platforms #11375.
Report unknown top-level options before falling back to implicit pnpm run scripts.
Reject null named catalogs in workspace manifests with InvalidWorkspaceManifestError instead of crashing with a raw TypeError.
Populate download location for git-sourced dependencies in SBOM output. Previously pnpm sbom emitted NOASSERTION (SPDX) and omitted the distribution reference (CycloneDX) for git dependencies. Now emits the git URL with commit hash, e.g. git+https://github.com/user/repo.git#commit.
pnpm self-update now keeps package.json's packageManager and devEngines.packageManager in sync. When the legacy packageManager field pins pnpm, both fields are rewritten to the new exact pnpm version on update — packageManager to pnpm@<version> (without an integrity hash), and devEngines.packageManager.version to the same exact <version> (dropping any range operator). When only devEngines.packageManager is declared, the existing range-preserving behavior is unchanged #11388.
Sort the keys of the overrides object returned by pnpm audit --fix so that the log output order matches the order written to pnpm-workspace.yaml.
Update the env lockfile's packageManagerDependencies entry when devEngines.packageManager declares a pnpm version that the lockfile no longer satisfies. Previously, the stale entry was kept even though the running pnpm matched the declared version, silently breaking the integrity record #11387.
Node.js 22+ required — support for Node 18, 19, 20, and 21 is dropped, pnpm itself is now pure ESM, and the standalone exe requires glibc 2.27.
Supply-chain protection on by default — minimumReleaseAge defaults to 1 day (newly published packages are not resolved for 24h) and blockExoticSubdeps defaults to true.
allowBuilds replaces the old build-dependency settings — onlyBuiltDependencies, onlyBuiltDependenciesFile, neverBuiltDependencies, ignoredBuiltDependencies, and ignoreDepScripts have been removed.
Global installs are isolated and use the global virtual store by default — each pnpm add -g gets its own directory with its own package.json, node_modules, and lockfile.
New SQLite-backed store index (store v11) with bundled manifests and hex digests, reducing filesystem syscalls and speeding up installation.
pnpm audit uses npm's bulk advisories endpoint — the legacy /security/audits endpoints are gone. CVE-based filtering has been replaced with GHSA-based filtering: migrate auditConfig.ignoreCves entries to auditConfig.ignoreGhsas.
.npmrc is auth/registry only — all other settings must live in pnpm-workspace.yaml or the new global config.yaml, and environment variables use the pnpm_config_* prefix.
Runtime installs are slimmer — installing a Node.js runtime via node@runtime:<version> no longer extracts the bundled npm, npx, and corepack, roughly halving the files pnpm has to hash, write, and link.
ESM pnpmfiles via .pnpmfile.mjs, which takes priority over .pnpmfile.cjs when present.
pnpm audit --fix=update fixes vulnerabilities by updating packages in the lockfile instead of adding overrides, and pnpm audit --fix --interactive lets you select which advisories to fix.
pnpm pack-app packs a CommonJS entry into a standalone executable for one or more target platforms using Node.js Single Executable Applications.
Faster HTTP and I/O — undici with Happy Eyeballs, direct-to-CAS writes, skipped staging directory, pre-allocated tarball downloads, and an NDJSON metadata cache.
Major Changes
Requirements
pnpm is now distributed as pure ESM.
Dropped support for Node.js v18, 19, 20, and 21.
The standalone exe version of pnpm requires at least glibc 2.27.
Security & Build Defaults
Changed default values: optimisticRepeatInstall is now true, verifyDepsBeforeRun is now install, minimumReleaseAge is now 1440 (1 day), and minimumReleaseAgeStrict is false. Newly published packages will not be resolved until they are at least 1 day old. This protects against supply chain attacks by giving the community time to detect and remove compromised versions. To opt out, set minimumReleaseAge: 0 in pnpm-workspace.yaml#11158.
Use the allowBuilds setting instead. It is a map where keys are package name patterns and values are booleans:
true means the package is allowed to run build scripts
false means the package is explicitly denied from running build scripts
Same as before, by default, none of the packages in the dependencies are allowed to run scripts. If a package has postinstall scripts and it isn't declared in allowBuilds, an error is printed.
Removed allowNonAppliedPatches in favor of allowUnusedPatches.
Removed ignorePatchFailures; patch application failures now throw an error.
Store
Runtime dependencies are always linked from the global virtual store #10233.
Optimized index file format to store the hash algorithm once per file instead of repeating it for every file entry. Each file entry now stores only the hex digest instead of the full integrity string (<algo>-<digest>). Using hex format improves performance since file paths in the content-addressable store use hex representation, eliminating base64-to-hex conversion during path lookups.
Store version bumped to v11.
The bundled manifest (name, version, bin, engines, scripts, etc.) is now stored directly in the package index file, eliminating the need to read package.json from the content-addressable store during resolution and installation. This reduces I/O and speeds up repeat installs #10473.
The package index in the content-addressable store is now backed by SQLite. Instead of individual JSON files under $STORE/index/, package metadata is stored in a single SQLite database at $STORE/index.db with MessagePack-encoded values. This reduces filesystem syscall overhead, improves space efficiency for small metadata entries, and enables concurrent access via SQLite's WAL mode. Packages missing from the new index are re-fetched on demand #10500#10826.
Global Packages
Global installs (pnpm add -g pkg) and pnx now use the global virtual store by default. Packages are stored at {storeDir}/links instead of per-project .pnpm directories. This can be disabled by setting enableGlobalVirtualStore: false#10694.
Isolated global packages. Each globally installed package (or group of packages installed together) now gets its own isolated installation directory with its own package.json, node_modules/, and lockfile. This prevents global packages from interfering with each other through peer dependency conflicts, hoisting changes, or version resolution shifts.
Key changes:
pnpm add -g <pkg> creates an isolated installation in {pnpmHomeDir}/global/v11/{hash}/
pnpm remove -g <pkg> removes the entire installation group containing the package
pnpm update -g [pkg] re-installs packages in new isolated directories
pnpm list -g scans isolated directories to show all installed global packages
pnpm install -g (no args) is no longer supported; use pnpm add -g <pkg> instead
Globally installed binaries are now stored in a bin subdirectory of PNPM_HOME instead of directly in PNPM_HOME. This prevents internal directories like global/ and store/ from polluting shell autocompletion when PNPM_HOME is on PATH #10986. After upgrading, run pnpm setup to update your shell configuration.
Breaking changes to pnpm link:
pnpm link <pkg-name> no longer resolves packages from the global store. Only relative or absolute paths are accepted. For example, use pnpm link ./foo instead of pnpm link foo.
pnpm link --global is removed. Use pnpm add -g . to register a local package's bins globally.
pnpm link (no arguments) is removed. Use pnpm link <dir> with an explicit path instead.
Configuration
pnpm no longer reads all settings from .npmrc. Only auth and registry settings are read from .npmrc files. All other settings (like hoistPattern, nodeLinker, shamefullyHoist, etc.) must be configured in pnpm-workspace.yaml or the global ~/.config/pnpm/config.yaml#11189.
Network settings (httpProxy, httpsProxy, noProxy, localAddress, strictSsl, gitShallowHosts) are now written to config.yaml (global) or pnpm-workspace.yaml (local) instead of .npmrc/auth.ini. They are still readable from .npmrc for easier migration from the npm CLI #11209.
pnpm no longer reads npm_config_* environment variables. Use pnpm_config_* environment variables instead (e.g., pnpm_config_registry instead of npm_config_registry).
pnpm no longer reads the npm global config at $PREFIX/etc/npmrc.
pnpm login writes auth tokens to ~/.config/pnpm/auth.ini.
Auth tokens in ~/.npmrc still work — pnpm continues to read ~/.npmrc as a fallback for registry authentication. The new npmrcAuthFile setting can be used to point to a different file instead of ~/.npmrc.
Replace workspace project specific .npmrc with packageConfigs in pnpm-workspace.yaml.
A workspace manifest with packageConfigs looks something like this:
pnpm no longer reads settings from the pnpm field of package.json. Settings should be defined in pnpm-workspace.yaml#10086.
pnpm config get (without --json) no longer prints INI formatted text. Instead, it prints JSON for objects and arrays, and raw strings for strings, numbers, booleans, and nulls. pnpm config get --json still prints all types of values as JSON, as before.
pnpm config get <array> now prints a JSON array.
pnpm config list now prints a JSON object instead of INI formatted text.
pnpm config list and pnpm config get (without argument) now hide auth-related settings.
pnpm config list and pnpm config get (without argument) now show top-level keys as camelCase. Exception: keys that start with @ or // are preserved (their cases don't change).
pnpm config get and pnpm config list no longer load non-camelCase options from the workspace manifest (pnpm-workspace.yaml).
Removed Commands & npm Passthrough
pnpm no longer falls back to the npm CLI. Commands that were previously passed through to npm (access, bugs, docs, edit, find, home, issues, owner, ping, prefix, profile, pkg, repo, search, set-script, star, stars, team, token, unstar, whoami, xmas) and their aliases (s, se) now throw a "not implemented" error, with a suggestion to use the npm CLI directly #10642. Other previously passed-through commands — view (info, show, v), login (adduser), logout, deprecate, unpublish, dist-tag, and version — have been reimplemented natively in pnpm (see New Commands below).
If the registry requests OTP and the user has not provided it via the PNPM_CONFIG_OTP environment variable or the --otp flag, pnpm will prompt the user directly for an OTP code.
If the registry requests web-based authentication, pnpm will print a scannable QR code along with the URL.
Since the new pnpm publish no longer calls npm publish, some undocumented features may have been unknowingly dropped. If you rely on a feature that is now gone, please open an issue at https://github.com/pnpm/pnpm/issues. In the meantime, you can use pnpm pack && npm publish *.tgz as a workaround.
Removed support for the useNodeVersion and executionEnv.nodeVersion fields. devEngines.runtime and engines.runtime should be used instead #10373.
Removed support for hooks.fetchers. We now have a new API for custom fetchers and resolvers via the fetchers field of pnpmfile.
Lifecycle Scripts
pnpm no longer populates npm_config_* environment variables from the pnpm config during lifecycle scripts. Only well-known npm_* env vars are now set, matching Yarn's behavior #11116.
CLI Output
Cleaner output for script execution: pnpm now prints $ command instead of > pkg@version stage path\n> command, and shows project name and path only when running in a different directory. The $ command line is printed to stderr to keep stdout clean for piping #11132.
During install, instead of rendering the full peer dependency issues tree, pnpm now suggests running pnpm peers check to view the issues #11133.
Lockfile
Simplified patchedDependencies lockfile format from Record<string, { path: string, hash: string }> to Record<string, string> (selector to hash). Existing lockfiles with the old format are automatically migrated #10911.
Other
The default value of the type field in the package.json file of the project initialized by pnpm init command has been changed to module.
Added support for lowercase options in pnpm add: -d, -p, -o, -e#9197.
When using the pnpm add command only:
-p is now an alias for --save-prod instead of --parseable
-d is now an alias for --save-dev instead of --loglevel=info
The root workspace project is no longer excluded when it is explicitly selected via a filter #10465.
Audit
pnpm audit now calls npm's /-/npm/v1/security/advisories/bulk endpoint. The legacy /-/npm/v1/security/audits{,/quick} endpoints have been retired by the registry, so the legacy request/response contract is no longer supported.
The bulk endpoint does not return CVE identifiers. CVE-based filtering has been replaced with GitHub advisory ID (GHSA) filtering:
auditConfig.ignoreCves → auditConfig.ignoreGhsas (the previous key is no longer recognized)
pnpm audit --ignore <id> / pnpm audit --ignore-unfixable now read and write GHSAs instead of CVEs
GHSAs are derived from each advisory's url (https://github.com/advisories/GHSA-xxxx-xxxx-xxxx)
To migrate: replace each CVE-YYYY-NNNNN entry in your auditConfig.ignoreCves with the corresponding GHSA-xxxx-xxxx-xxxx value (visible in the More info column of pnpm audit output) and move it under auditConfig.ignoreGhsas.
Package Manager Settings
Breaking: removed the managePackageManagerVersions, packageManagerStrict, and packageManagerStrictVersion settings. They existed only to derive the onFail behavior for the legacy packageManager field, and the pmOnFail setting introduced alongside pnpm with subsumes all three — it directly sets the onFail behavior of both packageManager and devEngines.packageManager. The COREPACK_ENABLE_STRICT environment variable is no longer honored (it only gated packageManagerStrict); use pmOnFail instead.
Migration:
Removed setting
Replace with
managePackageManagerVersions: true
pmOnFail: download (default)
managePackageManagerVersions: false
pmOnFail: ignore
packageManagerStrict: false
pmOnFail: warn
packageManagerStrictVersion: true
pmOnFail: error
COREPACK_ENABLE_STRICT=0
pmOnFail: warn
Runtime Installs
Installing a Node.js runtime via node@runtime:<version> (including pnpm env use and pnpm runtime set node) no longer extracts the bundled npm, npx, and corepack from the Node.js archive. This cuts roughly half of the files pnpm has to hash, write to the CAS, and link during installation, making runtime installs noticeably faster. Users who still need npm can install it as a separate package.
Minor Changes
New Commands
Added native pnpm view (info, show, v) command for viewing package metadata from the registry #11064.
Added pnpm login (and pnpm adduser alias) command for authenticating with npm registries. Supports web-based login with QR code as well as classic username/password login #11094.
Added pnpm logout command for logging out of npm registries. Revokes the authentication token on the registry and removes it from the local auth config file #11213.
Added native pnpm deprecate and pnpm undeprecate commands for setting and removing deprecation messages on package versions without delegating to the npm CLI #11120.
Added native pnpm unpublish command. Supports unpublishing specific versions, version ranges via semver, and entire packages with --force#11128.
Added pnpm sbom command for generating Software Bill of Materials in CycloneDX 1.7 and SPDX 2.3 JSON formats #9088.
Added pnpm clean command that safely removes node_modules directories from all workspace projects #10707. Use --lockfile to also remove pnpm-lock.yaml files.
Added the ability to fix vulnerabilities by updating packages in the lockfile instead of adding overrides. Use pnpm audit --fix=update#10341.
Added pnpm ci command for clean installs #6100. The command runs pnpm clean followed by pnpm install --frozen-lockfile. Designed for CI/CD environments where reproducible builds are critical. Aliases: pnpm clean-install, pnpm ic, pnpm install-clean#11003.
Added pnpm peers check command that checks for unmet and missing peer dependency issues by reading the lockfile #7087.
Implemented the version command natively in pnpm to support workspaces and workspace: protocols correctly. The new command allows bumping package versions (major, minor, patch, etc.) with full workspace support and git integration #10879.
pnpm audit --fix now supports a new interactive mode via --interactive/-i.
Added the pnpm docs command and its alias pnpm home. This command opens the package documentation or homepage in the browser. When the package has no valid homepage, it falls back to https://npmx.dev/package/<name>.
Added native pnpm ping command to test registry connectivity. Provides a simple way to verify connectivity to the configured registry without requiring external tools.
Implemented native search command and its aliases (s, se, find).
Add pnpm with <version|current> <args...> command. Runs pnpm at a specific version (or the currently active one) for a single invocation, bypassing the project's packageManager and devEngines.packageManager pins.
Added support for a global YAML config file named config.yaml.
Configuration is now split into two categories:
Registry and auth settings, which can be stored in INI files such as the global rc file and local .npmrc.
pnpm-specific settings, which can only be loaded from YAML files such as the global config.yaml and local pnpm-workspace.yaml.
Added support for loading environment variables whose names start with pnpm_config_ into config. These environment variables override settings from pnpm-workspace.yaml but not CLI arguments.
Added support for reading allowBuilds from pnpm-workspace.yaml in the global package directory for global installs.
Added support for pnpm config get globalconfig to retrieve the global config file path #9977.
Added a new setting virtualStoreOnly that populates the virtual store without creating importer symlinks, hoisting, bin links, or running lifecycle scripts. This is useful for pre-populating a store (e.g., in Nix builds) without creating unnecessary project-level artifacts. pnpm fetch now uses this mode internally #10840.
Added support for specifying the pnpm version via devEngines.packageManager in package.json. Unlike the packageManager field, this supports version ranges. The resolved version is stored in pnpm-lock.yaml and reused if it still satisfies the range #10932.
Added a new dedupePeers setting that reduces peer dependency duplication. When enabled, peer dependency suffixes use version-only identifiers (name@version) instead of full dep paths, eliminating nested suffixes like (foo@1.0.0(bar@2.0.0)). This dramatically reduces the number of package instances in projects with many recursive peer dependencies #11070.
Config dependencies are now installed into the global virtual store ({storeDir}/links/) and symlinked into node_modules/.pnpm-config/. This allows config dependencies to be shared across projects that use the same store, avoiding redundant fetches and imports #10910. Config dependency and package manager integrity info is now stored in pnpm-lock.yaml instead of inlined in pnpm-workspace.yaml: the workspace manifest contains only clean version specifiers for configDependencies, while the resolved versions, integrity hashes, and tarball URLs are recorded in the lockfile as a separate YAML document. The env lockfile section also stores packageManagerDependencies resolved during version switching and self-update. Projects using the old inline-hash format are automatically migrated on install #10912#10964.
Added nodeDownloadMirrors setting to configure custom Node.js download mirrors in pnpm-workspace.yaml. This replaces the node-mirror:<channel>.npmrc setting, which is no longer read #11194:
pnpm dlx and pnpm create now respect security and trust policy settings (minimumReleaseAge, minimumReleaseAgeExclude, minimumReleaseAgeStrict, trustPolicy, trustPolicyExclude, trustPolicyIgnoreAfter) from project-level configuration #11183.
pnpm init now writes a devEngines.packageManager field instead of the packageManager field when init-package-manager is enabled.
Added a new setting runtimeOnFail that overrides the onFail field of devEngines.runtime (and engines.runtime) in the root project's package.json. Accepted values: ignore, warn, error, download. For example, setting runtimeOnFail=download makes pnpm download the declared runtime version even when the manifest does not set onFail: "download".
Added a new setting minimumReleaseAgeIgnoreMissingTime, which is true by default. When enabled, pnpm skips the minimumReleaseAge maturity check if the registry metadata does not include the time field. Set to false to fail resolution instead.
Store
When the global virtual store is enabled, packages that are not allowed to build (and don't transitively depend on packages that are) now get hashes that don't include the engine name (platform, architecture, Node.js major version). This means ~95% of packages in the GVS survive Node.js upgrades and architecture changes without re-import #10837.
Hooks & Pnpmfiles
Added support for pnpmfiles written in ESM, using the .mjs extension. When .pnpmfile.mjs exists, it takes priority over .pnpmfile.cjs and only one is loaded #9730.
CLI & Other
The built-in clean, setup, deploy, and rebuild commands now prefer user scripts over built-in commands. When a project's package.json has a script with the same name, pnpm executes the script instead of the built-in command. Added purge as an alias for the built-in clean command, which always runs the built-in regardless of scripts #11118.
Added -F as a short alias for the --filter option.
Added support for hidden scripts. Scripts starting with . are hidden and cannot be run directly via pnpm run. They can only be called from other scripts. Hidden scripts are also omitted from the pnpm run listing #11041.
pnpm approve-builds now accepts positional arguments for approving or denying packages without the interactive prompt. Prefix a package name with ! to deny it. Only mentioned packages are affected; the rest are left untouched #11030.
During install, packages with ignored builds that are not yet listed in allowBuilds are automatically added to pnpm-workspace.yaml with a placeholder value, so users can manually set them to true or false#11030.
Added pn and pnx short aliases for pnpm and pnpx (pnpm dlx) #11052.
pnpm store prune now displays the total size of removed files #11047.
pnpm audit --fix now adds the minimum patched version for each advisory to minimumReleaseAgeExclude in pnpm-workspace.yaml, so the security fix can be installed without waiting for minimumReleaseAge#11216.
pnpm now warns when optimisticRepeatInstall skips shouldRefreshResolution hooks #10995.
Performance
Replaced node-fetch with native undici for HTTP requests throughout pnpm #10537.
Eliminated redundant internal linking during GVS warm reinstall when no packages were added #11073.
Eliminated the staging directory when importing packages into node_modules, avoiding the overhead of creating a temp dir and renaming per package #11088.
CAS files are now written directly to their final content-addressed path instead of to a temp file and renamed. This eliminates ~30k rename syscalls per cold install #11087.
Optimized hot-path string operations in the content-addressable store and increased gunzipSync chunk size for fewer buffer allocations during tarball decompression #11086.
Improved HTTP performance with Happy Eyeballs (dual-stack), better keep-alive settings, and an optimized global dispatcher. Tarball downloads with known size now pre-allocate memory to avoid double-copy overhead #11151.
Switched to abbreviated metadata when checking minimumReleaseAge, reducing the amount of data fetched from the registry #11160.
Switched the metadata cache to NDJSON format, improving read/write performance #11188.
Patch Changes
Switched to process.stderr.write instead of console.error for script logging #11140.
Respected the frozen-lockfile flag when migrating config dependencies #11067.
Removed the --workspace flag from the version command #11115.
Handled ENOTSUP error in the clone import path during parallel I/O #11117.
Fixed pnpm audit command.
Updated dependencies to fix vulnerabilities.
pnpm now checks whether a package is installable for non-npm-hosted packages (e.g., git or tarball dependencies) after the manifest has been fetched.
pnpm now explicitly passes the path of the global rc config file to npm.
Fixed YAML formatting preservation in pnpm-workspace.yaml when running commands like pnpm update. Previously, quotes and other formatting were lost even when catalog values didn't change.
The parameter set by the --allow-build flag is now written to allowBuilds.
Fixed a bug in which specifying filter in pnpm-workspace.yaml would cause pnpm to not detect any projects.
Deferred patch errors until all patches in a group are applied, so that one failed patch does not prevent other patches from being attempted.
pnpm now fails on incompatible lockfiles in CI when frozen lockfile mode is enabled #10978.
Fixed strictDepBuilds and allowBuilds checks being bypassed when a package's build side-effects are cached in the store #11039.
In GVS mode, pnpm approve-builds now runs a full install instead of rebuild, ensuring that GVS hash directories and symlinks are updated correctly after changing allowBuilds#11043.
Fixed a crash in the lockfile merger when merging non-semver version strings (e.g. link:, file:, git URLs) #11102.
Handled ENOTSUP error in linkOrCopy during parallel imports #11103.
Skipped linking bins that already reference the correct target. This avoids redundant I/O during repeated installs and prevents permission errors when the store is read-only (e.g. Docker layer caching, CI prewarm, NFS) #11069.
Fixed _password handling for the default registry to decode from base64 before use, consistent with scoped registry behavior #11089.
Fixed a bug where the CAS locker cache was not updated when a file already existed with correct integrity #11085.
Prevented catalog entries from being removed by cleanupUnusedCatalogs when they are referenced only from workspace overrides#11075.
Resolved patch file paths during pnpm fetch#11054.
Fixed invalid specifiers for peers on all non-exact version selectors #11049.
Fixed false "Command not found" error on Windows when the command exists but exits with a non-zero exit code #11000.
Prepended Bearer to the authorization token generated by tokenHelper if it is missing, aligning with npm's behavior #11097.
Propagated error cause when throwing PnpmError in @pnpm/npm-resolver#10990.
Fixed SQLite race condition during store initialization on Windows.
Removed rimrafSync in importIndexedDir fast-path error handler #11168.
Fixed pnpm dedupe --check unexpectedly failing due to non-deterministic resolution #11110.
Fixed empty files not being rejected in isEmptyDirOrNothing#11182.
Fixed .bat/.cmd token helpers not working on Windows due to missing shell: true option.
352ae48: Security: validate config dependency names and versions before using them to build filesystem paths. A pnpm-workspace.yaml with a traversal-shaped configDependencies name (such as ../../PWNED) or version (such as ../../../PWNED) could previously cause pnpm install to create symlinks or write package files outside node_modules/.pnpm-config and the store. Names must now be valid npm package names and versions must be exact semver versions. See GHSA-qrv3-253h-g69c.
352ae48: Reject path-traversal and reserved dependency aliases (such as ../../../escape, .bin, .pnpm, or node_modules) that come from a lockfile rather than a freshly resolved manifest. A crafted lockfile alias could otherwise be joined directly under a hoisted node_modules directory, letting package files be written outside the intended install root or overwrite pnpm-owned layout.
The nodeLinker: hoisted graph builder now validates each alias at the directory sink (safeJoinModulesDir), matching the validation pnpm already performs when resolving aliases from manifests. See GHSA-fr4h-3cph-29xv.
352ae48: Prevent pnpm patch-remove from removing files outside the configured patches directory.
217fbe0: Hardened the warning printed when a project .npmrc uses environment variables in registry/auth settings: the suggested pnpm config set command is now only included for keys made up of shell-inert characters. Because the key comes from a repository-controlled .npmrc and a shell expands $(...), backticks, and $VAR even inside double quotes, a crafted key could otherwise have turned the suggested copy-paste command into command execution.
⚠️ Security fix — environment variables in a project .npmrc (action may be required)
Following GHSA-3qhv-2rgh-x77r, pnpm no longer expands ${ENV_VAR} placeholders that come from a repository-controlled config file, because a malicious repository could otherwise use them to leak your environment secrets (npm tokens, CI job tokens, etc.) to an attacker-controlled registry during install. This applies to:
This release also closes a bypass where a project .npmrc could set userconfig, globalconfig, or prefix to make pnpm load a repo-supplied file as trusted config (via @pnpm/npm-conf@3.0.3).
Environment variables are still expanded in trusted config: your user-level ~/.npmrc, the global config, CLI options, and environment config.
If your authentication broke after upgrading, move the token out of the committed .npmrc:
# Writes to your user/global config, not the repository:
pnpm config set"//registry.npmjs.org/:_authToken""$NPM_TOKEN"
Or keep the ${NPM_TOKEN} line but put it in your user-level ~/.npmrc instead of the repo. In GitHub Actions, actions/setup-node with registry-url already writes a user-level .npmrc, so NODE_AUTH_TOKEN keeps working. For other CI where editing each pipeline is hard, set NPM_CONFIG_USERCONFIG=.npmrc in the CI environment to declare the project .npmrc trusted.
Improved the warning printed when a project .npmrc uses an environment variable in a registry/proxy URL or in registry credentials. The message now explains why the setting was ignored and how to migrate it to a trusted source — for example by running pnpm config set "<key>" <value> to store it in the global config, or by keeping the ${...} line in the user-level ~/.npmrc — with a link to https://pnpm.io/npmrc.
A repository-controlled project or workspace .npmrc can no longer redirect which files pnpm loads as its trusted user and global configuration. Previously such a file could set userconfig, globalconfig, or prefix to point at an attacker-supplied file shipped in the repository, and pnpm would load it as a trusted config source — bypassing the protection that prevents repository config from expanding environment variables into registry request destinations and credentials, and allowing it to set tokenHelper. The user/global config file locations are now resolved only from trusted sources (CLI options, environment config, the npm builtin config, and defaults) before the project and workspace .npmrc files are read. Fixed by upgrading @pnpm/npm-conf to 3.0.3.
⚠️ Security fix — environment variables in a project .npmrc (action may be required)
Following GHSA-3qhv-2rgh-x77r, pnpm no longer expands ${ENV_VAR} placeholders that come from a repository-controlled config file, because a malicious repository could otherwise use them to leak your environment secrets (npm tokens, CI job tokens, etc.) to an attacker-controlled registry during install. This applies to:
This release also closes a bypass where a project .npmrc could set userconfig, globalconfig, or prefix to make pnpm load a repo-supplied file as trusted config (via @pnpm/npm-conf@3.0.3).
Environment variables are still expanded in trusted config: your user-level ~/.npmrc, the global config, CLI options, and environment config.
If your authentication broke after upgrading, move the token out of the committed .npmrc:
# Writes to your user/global config, not the repository:
pnpm config set"//registry.npmjs.org/:_authToken""$NPM_TOKEN"
Or keep the ${NPM_TOKEN} line but put it in your user-level ~/.npmrc instead of the repo. In GitHub Actions, actions/setup-node with registry-url already writes a user-level .npmrc, so NODE_AUTH_TOKEN keeps working. For other CI where editing each pipeline is hard, set NPM_CONFIG_USERCONFIG=.npmrc in the CI environment to declare the project .npmrc trusted.
Package-manager bootstrap traffic is now resolved through trusted registries and trusted network config. When pnpm downloads the pnpm version requested by a repository's packageManager field, the registry it fetches from (and the proxy/TLS settings used for that traffic) now come exclusively from trusted config sources — CLI options, env config, user and global .npmrc — defaulting to the public npm registry, instead of the repository's project/workspace settings.
pnpm now verifies the npm registry signature of a package-manager binary before spawning it. When the packageManager field (or pnpm self-update) makes pnpm download another pnpm version, the staged install is verified corepack-style: the integrity recorded in the staged lockfile must carry a valid npm registry signature for the exact name@version, validated against npm's public signing keys that ship embedded in the pnpm CLI. Verification fails closed — a tampered download, an unsigned package, or an unreachable registry refuses the version switch rather than running an unverified binary. It runs only when the wanted version is actually downloaded (a tools-directory cache miss), so repeated commands pay no extra network round trip.
Environment variable expansion is now trust-aware for registry/auth config and request destinations. Repository-controlled config files (the project and workspace .npmrc and pnpm-workspace.yaml) can no longer expand ${...} placeholders in registry/proxy request destinations, URL-scoped keys, or registry credential values, preventing repository-controlled configuration from exfiltrating environment secrets through request URLs. Trusted user/global/CLI/env config keeps full env expansion, so existing token and registry setup flows continue to work.
Reject reserved manifest bin names ("", ".", "..", and scoped forms such as @scope/..) when resolving a package's bins. These names previously passed the bin-name guard and, when joined to the global bin directory during global remove/update/add operations, could resolve to the global bin directory itself or its parent and have it recursively deleted.
Require trusted package identity before package-name onlyBuiltDependencies (and allowBuilds) entries can approve lifecycle scripts for git, git-hosted tarball, direct tarball, and local directory artifacts. To approve one of those artifacts explicitly, use its peer-suffix-free lockfile depPath as the key. Lockfile entries are now rejected when a registry-style dependency path (name@semver) is backed by a git, directory, or git-hosted tarball resolution (ERR_PNPM_RESOLUTION_SHAPE_MISMATCH), so the dependency path is a reliable artifact identity by the time scripts can run.
pnpm now verifies the detached OpenPGP signature of a Node.js release's SHASUMS256.txt against the Node.js release team's public keys (embedded in the pnpm CLI) before trusting its hashes. The Node.js download mirror is repository-configurable (node-mirror:<channel> in .npmrc), and the integrity check previously trusted a SHASUMS256.txt fetched from that same mirror — a circular check that a malicious mirror could satisfy with a tampered binary and matching hashes. A mirror that proxies the real signed SHASUMS keeps working unchanged. Only the release channel publishes signed SHASUMS files, so pre-release channels (rc, nightly, …) remain unverified.
Reject pnpm-lock.yaml entries whose remote tarball resolution: block is missing the integrity field. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that strips integrity:) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under --frozen-lockfile. pnpm now fails closed at lockfile-read time with ERR_PNPM_MISSING_TARBALL_INTEGRITY. Git-hosted tarballs (gitHosted: true or a URL on codeload.github.com / bitbucket.org / gitlab.com) and file: tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes.
Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously, pnpm install (non-frozen) would log ERR_PNPM_TARBALL_INTEGRITY, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile.
pnpm install now exits with ERR_PNPM_TARBALL_INTEGRITY and a hint pointing at the new opt-in flag.
The only opt-in is pnpm install --update-checksums — narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable.
--force and pnpm update deliberately do not bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. --frozen-lockfile behavior is unchanged. --fix-lockfile keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass.
Patch Changes
Pin unscoped per-registry settings (_authToken, _auth, username/_password, tokenHelper, inline cert/key) to the registry declared in the same config source at load time, so a later layer overriding registry= (workspace .npmrc, pnpm-workspace.yaml, CLI --registry) cannot redirect a credential or client certificate authored for a different host. A deprecation warning is emitted whenever an unscoped per-registry setting is encountered, naming the source and the URL it was pinned to. Reported by JUNYI LIU.
Fixed minimumReleaseAge handling when cached metadata is abbreviated. The npm registry returns abbreviated package metadata (without the per-version time field) by default, which made the maturity check throw ERR_PNPM_MISSING_TIME whenever cached abbreviated metadata was reused. pnpm now upgrades cached abbreviated metadata to the full document via a follow-up fetch when minimumReleaseAge is active, persists the upgrade to the on-disk cache so subsequent installs skip the extra fetch, and lets ERR_PNPM_MISSING_TIME from the cache fast-path fall through to the network fetch even under strict mode.
Reject git resolutions whose commit field is not a 40-character hexadecimal SHA before invoking git. A malicious lockfile could otherwise smuggle a value such as --upload-pack=<command> through git fetch / git checkout, which on SSH or local-file transports executes the supplied command.
Reject patch files whose diff --git headers reference paths outside the patched package directory. Previously a malicious .patch file added via a pull request could write, delete, or rename arbitrary files reachable by the user running pnpm install.
Fixed --prefix=<dir> not being honored when locating the workspace root. The --prefix → dir rename was applied after workspace detection, so workspace settings declared in <dir>/pnpm-workspace.yaml were not loaded when pnpm was invoked from outside <dir>#11535.
Reject dependency aliases that contain path-traversal segments (such as @x/../../../../../.git/hooks) when reading them from a package manifest or symlinking them into node_modules. A malicious registry package could otherwise use a transitive dependency key to make pnpm install create symlinks at attacker-chosen paths outside the intended node_modules directory.
Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.
A new gitHosted: true field is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.
Fix a regression where pnpm --recursive --filter '!<pkg>' run/exec/test/add would include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative --filter arguments are provided, matching the documented behavior. To include the root, pass --include-workspace-root#11341.
When self-updating from v10's @pnpm/exe to v11+ on Intel macOS (darwin-x64), pnpm self-update now transparently switches to the JS-only pnpm package on npm instead of installing @pnpm/exe@v11+ (which doesn't ship a working binary for Intel Macs because of an upstream Node.js SEA bug — see #11423 and nodejs/node#62893). Without this, the self-update would silently leave the user with no working pnpm binary. The new install requires Node.js to be available on PATH; a warning is printed when the swap happens. All other host/version combinations are unchanged.
pnpm self-update (with no version argument) no longer downgrades pnpm when the registry's latest dist-tag points to an older release than the currently active version. Run pnpm self-update latest to force a downgrade #11418.
Globally-installed bins no longer fail with ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND when pnpm was installed via the standalone @pnpm/exe binary (e.g. curl -fsSL https://get.pnpm.io/install.sh | sh -) on a system without a separate Node.js installation. Previously, when which('node') failed during pnpm add --global, pnpm fell back to process.execPath, which in @pnpm/exe is the pnpm binary itself — and that path was baked into the generated bin shim, causing the shim to invoke pnpm instead of Node #11291, #4645.
Fix an infinite fork-bomb that could happen when pnpm was installed with one version (e.g. npm install -g pnpm@A) and run inside a project whose package.json selected a different pnpm version via the packageManager field (e.g. pnpm@B), while a pnpm-workspace.yaml also existed at the project root.
The child's environment is now forced to manage-package-manager-versions=false (v10) and pm-on-fail=ignore (v11+), which disables the package-manager-version handling in whichever pnpm runs as the child.
When a project's packageManager field selects pnpm v11 or newer, commands that v10 would have passed through to npm (version, login, logout, publish, unpublish, deprecate, dist-tag, docs, ping, search, star, stars, unstar, whoami, etc.) are now handed over to the wanted pnpm, which implements them natively. Previously they silently shelled out to npm — making, for example, pnpm version --help print npm's help on a project with packageManager: pnpm@11.0.0-rc.3#11328.
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | devDependencies | major | [`^25.0.0` → `^26.0.0`](https://renovatebot.com/diffs/npm/@types%2fnode/25.5.0/26.1.0) |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme) ([source](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react)) | devDependencies | major | [`^5.0.0` → `^6.0.0`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-react/5.2.0/6.0.3) |
| [actions/checkout](https://github.com/actions/checkout) | action | major | `v6` → `v7` |
| docker.io/library/node | stage | major | `25` → `26` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | action | major | `v6` → `v7` |
| [docker/login-action](https://github.com/docker/login-action) | action | major | `v3` → `v4` |
| [docker/metadata-action](https://github.com/docker/metadata-action) | action | major | `v5` → `v6` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | action | major | `v3` → `v4` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | action | major | `v3` → `v4` |
| [jsdom](https://github.com/jsdom/jsdom) | devDependencies | major | [`^28.0.0` → `^29.0.0`](https://renovatebot.com/diffs/npm/jsdom/28.1.0/29.1.1) |
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm11/pnpm)) | packageManager | major | [`10.33.0` → `11.10.0`](https://renovatebot.com/diffs/npm/pnpm/10.33.0/11.10.0) |
| [typescript](https://www.typescriptlang.org/) ([source](https://github.com/microsoft/TypeScript)) | devDependencies | major | [`^5.7.3` → `^6.0.0`](https://renovatebot.com/diffs/npm/typescript/5.9.3/6.0.3) |
---
### Release Notes
<details>
<summary>vitejs/vite-plugin-react (@​vitejs/plugin-react)</summary>
### [`v6.0.3`](https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#603-2026-06-23)
[Compare Source](https://github.com/vitejs/vite-plugin-react/compare/6535b55e956b425e6650ffc2cc98fd23cca1d231...640fd358a0e82393acfce4e92e19a6ac6e1641a7)
### [`v6.0.2`](https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#602-2026-05-14)
[Compare Source](https://github.com/vitejs/vite-plugin-react/compare/1e94c06995c2afe2d1fee5aea2ef9720d35a7e02...6535b55e956b425e6650ffc2cc98fd23cca1d231)
##### Allow all options in reactCompilerPreset ([#​1189](https://github.com/vitejs/vite-plugin-react/pull/1189))
This is a type only change. Only `compilationMode` and `target` options were available for `reactCompilerPreset`.
### [`v6.0.1`](https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#601-2026-03-13)
[Compare Source](https://github.com/vitejs/vite-plugin-react/compare/dcc901236079ef7fa99139f7ba7beebac583f301...1e94c06995c2afe2d1fee5aea2ef9720d35a7e02)
##### Expand `@rolldown/plugin-babel` peer dep range ([#​1146](https://github.com/vitejs/vite-plugin-react/pull/1146))
Expanded `@rolldown/plugin-babel` peer dep range to include `^0.2.0`.
### [`v6.0.0`](https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md#600-2026-03-12)
[Compare Source](https://github.com/vitejs/vite-plugin-react/compare/fda3a86095556b49ae3c995eb57a30d4e0b8fa8d...dcc901236079ef7fa99139f7ba7beebac583f301)
</details>
<details>
<summary>actions/checkout (actions/checkout)</summary>
### [`v7.0.0`](https://github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v700)
[Compare Source](https://github.com/actions/checkout/compare/v7.0.0...v7.0.0)
- Block checking out fork PR for pull\_request\_target and workflow\_run by [@​aiqiaoy](https://github.com/aiqiaoy) in [#​2454](https://github.com/actions/checkout/pull/2454)
- Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2458](https://github.com/actions/checkout/pull/2458)
- Bump flatted from 3.3.1 to 3.4.2 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2460](https://github.com/actions/checkout/pull/2460)
- Bump js-yaml from 4.1.0 to 4.2.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2461](https://github.com/actions/checkout/pull/2461)
- Bump [@​actions/core](https://github.com/actions/core) and [@​actions/tool-cache](https://github.com/actions/tool-cache) and Remove uuid by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2459](https://github.com/actions/checkout/pull/2459)
- upgrade module to esm and update dependencies by [@​aiqiaoy](https://github.com/aiqiaoy) in [#​2463](https://github.com/actions/checkout/pull/2463)
- Bump the minor-npm-dependencies group across 1 directory with 3 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2462](https://github.com/actions/checkout/pull/2462)
### [`v7`](https://github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v700)
[Compare Source](https://github.com/actions/checkout/compare/v6.0.3...v7.0.0)
- Block checking out fork PR for pull\_request\_target and workflow\_run by [@​aiqiaoy](https://github.com/aiqiaoy) in [#​2454](https://github.com/actions/checkout/pull/2454)
- Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2458](https://github.com/actions/checkout/pull/2458)
- Bump flatted from 3.3.1 to 3.4.2 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2460](https://github.com/actions/checkout/pull/2460)
- Bump js-yaml from 4.1.0 to 4.2.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2461](https://github.com/actions/checkout/pull/2461)
- Bump [@​actions/core](https://github.com/actions/core) and [@​actions/tool-cache](https://github.com/actions/tool-cache) and Remove uuid by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2459](https://github.com/actions/checkout/pull/2459)
- upgrade module to esm and update dependencies by [@​aiqiaoy](https://github.com/aiqiaoy) in [#​2463](https://github.com/actions/checkout/pull/2463)
- Bump the minor-npm-dependencies group across 1 directory with 3 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​2462](https://github.com/actions/checkout/pull/2462)
</details>
<details>
<summary>docker/build-push-action (docker/build-push-action)</summary>
### [`v7.3.0`](https://github.com/docker/build-push-action/releases/tag/v7.3.0)
[Compare Source](https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0)
- Preserve names in esbuild bundle by [@​crazy-max](https://github.com/crazy-max) in [#​1567](https://github.com/docker/build-push-action/pull/1567)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.90.0 to 0.92.0 in [#​1545](https://github.com/docker/build-push-action/pull/1545) [#​1572](https://github.com/docker/build-push-action/pull/1572)
- Bump [@​sigstore/core](https://github.com/sigstore/core) from 3.1.0 to 3.2.1 in [#​1568](https://github.com/docker/build-push-action/pull/1568)
- Bump js-yaml from 4.1.1 to 4.3.0 in [#​1566](https://github.com/docker/build-push-action/pull/1566)
- Bump tmp from 0.2.5 to 0.2.7 in [#​1547](https://github.com/docker/build-push-action/pull/1547)
- Bump undici from 6.24.1 to 6.27.0 in [#​1564](https://github.com/docker/build-push-action/pull/1564)
- Bump vite from 7.3.2 to 7.3.6 in [#​1563](https://github.com/docker/build-push-action/pull/1563)
**Full Changelog**: <https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0>
### [`v7.2.0`](https://github.com/docker/build-push-action/releases/tag/v7.2.0)
[Compare Source](https://github.com/docker/build-push-action/compare/v7.1.0...v7.2.0)
- Bump [@​actions/core](https://github.com/actions/core) from 3.0.0 to 3.0.1 in [#​1525](https://github.com/docker/build-push-action/pull/1525)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.87.0 to 0.90.0 in [#​1517](https://github.com/docker/build-push-action/pull/1517)
- Bump brace-expansion from 2.0.2 to 5.0.6 in [#​1534](https://github.com/docker/build-push-action/pull/1534)
- Bump fast-xml-builder from 1.1.4 to 1.2.0 in [#​1529](https://github.com/docker/build-push-action/pull/1529)
- Bump fast-xml-parser from 5.5.7 to 5.8.0 in [#​1521](https://github.com/docker/build-push-action/pull/1521)
- Bump postcss from 8.5.6 to 8.5.10 in [#​1526](https://github.com/docker/build-push-action/pull/1526)
- Bump tar from 6.2.1 to 7.5.15 in [#​1533](https://github.com/docker/build-push-action/pull/1533)
**Full Changelog**: <https://github.com/docker/build-push-action/compare/v7.1.0...v7.2.0>
### [`v7.1.0`](https://github.com/docker/build-push-action/releases/tag/v7.1.0)
[Compare Source](https://github.com/docker/build-push-action/compare/v7...v7.1.0)
- Git context [query format](https://docs.docker.com/build/concepts/context/#url-queries) support by [@​crazy-max](https://github.com/crazy-max) in [#​1505](https://github.com/docker/build-push-action/pull/1505)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.79.0 to 0.87.0 by [@​crazy-max](https://github.com/crazy-max) in [#​1505](https://github.com/docker/build-push-action/pull/1505)
- Bump brace-expansion from 1.1.12 to 1.1.13 in [#​1500](https://github.com/docker/build-push-action/pull/1500)
- Bump fast-xml-parser from 5.4.2 to 5.5.7 in [#​1489](https://github.com/docker/build-push-action/pull/1489)
- Bump flatted from 3.3.3 to 3.4.2 in [#​1491](https://github.com/docker/build-push-action/pull/1491)
- Bump glob from 10.3.12 to 10.5.0 in [#​1490](https://github.com/docker/build-push-action/pull/1490)
- Bump handlebars from 4.7.8 to 4.7.9 in [#​1497](https://github.com/docker/build-push-action/pull/1497)
- Bump lodash from 4.17.23 to 4.18.1 in [#​1510](https://github.com/docker/build-push-action/pull/1510)
- Bump picomatch from 4.0.3 to 4.0.4 in [#​1496](https://github.com/docker/build-push-action/pull/1496)
- Bump undici from 6.23.0 to 6.24.1 in [#​1486](https://github.com/docker/build-push-action/pull/1486)
- Bump vite from 7.3.1 to 7.3.2 in [#​1509](https://github.com/docker/build-push-action/pull/1509)
**Full Changelog**: <https://github.com/docker/build-push-action/compare/v7.0.0...v7.1.0>
### [`v7.0.0`](https://github.com/docker/build-push-action/releases/tag/v7.0.0)
[Compare Source](https://github.com/docker/build-push-action/compare/v7...v7)
- Node 24 as default runtime (requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later) by [@​crazy-max](https://github.com/crazy-max) in [#​1470](https://github.com/docker/build-push-action/pull/1470)
- Remove deprecated `DOCKER_BUILD_NO_SUMMARY` and `DOCKER_BUILD_EXPORT_RETENTION_DAYS` envs by [@​crazy-max](https://github.com/crazy-max) in [#​1473](https://github.com/docker/build-push-action/pull/1473)
- Remove legacy export-build tool support for build summary by [@​crazy-max](https://github.com/crazy-max) in [#​1474](https://github.com/docker/build-push-action/pull/1474)
- Switch to ESM and update config/test wiring by [@​crazy-max](https://github.com/crazy-max) in [#​1466](https://github.com/docker/build-push-action/pull/1466)
- Bump [@​actions/core](https://github.com/actions/core) from 1.11.1 to 3.0.0 in [#​1454](https://github.com/docker/build-push-action/pull/1454)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.62.1 to 0.79.0 in [#​1453](https://github.com/docker/build-push-action/pull/1453) [#​1472](https://github.com/docker/build-push-action/pull/1472) [#​1479](https://github.com/docker/build-push-action/pull/1479)
- Bump minimatch from 3.1.2 to 3.1.5 in [#​1463](https://github.com/docker/build-push-action/pull/1463)
**Full Changelog**: <https://github.com/docker/build-push-action/compare/v6.19.2...v7.0.0>
### [`v7`](https://github.com/docker/build-push-action/compare/v6.19.2...v7)
[Compare Source](https://github.com/docker/build-push-action/compare/v6.19.2...v7)
</details>
<details>
<summary>docker/login-action (docker/login-action)</summary>
### [`v4.4.0`](https://github.com/docker/login-action/releases/tag/v4.4.0)
[Compare Source](https://github.com/docker/login-action/compare/v4.3.0...v4.4.0)
- Skip empty `registry-auth` secret mask by [@​crazy-max](https://github.com/crazy-max) in [#​1035](https://github.com/docker/login-action/pull/1035)
- Bump [@​aws-sdk/client-ecr](https://github.com/aws-sdk/client-ecr) and [@​aws-sdk/client-ecr-public](https://github.com/aws-sdk/client-ecr-public) to 3.1077.0 [#​1034](https://github.com/docker/login-action/pull/1034)
**Full Changelog**: <https://github.com/docker/login-action/compare/v4.3.0...v4.4.0>
### [`v4.3.0`](https://github.com/docker/login-action/releases/tag/v4.3.0)
[Compare Source](https://github.com/docker/login-action/compare/v4.2.0...v4.3.0)
- Preserve names in esbuild bundle by [@​crazy-max](https://github.com/crazy-max) in [#​1022](https://github.com/docker/login-action/pull/1022)
- Bump [@​aws-sdk/client-ecr](https://github.com/aws-sdk/client-ecr) and [@​aws-sdk/client-ecr-public](https://github.com/aws-sdk/client-ecr-public) to 3.1076.0 [#​999](https://github.com/docker/login-action/pull/999) [#​1030](https://github.com/docker/login-action/pull/1030)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.90.0 to 0.92.0 in [#​1004](https://github.com/docker/login-action/pull/1004) [#​1027](https://github.com/docker/login-action/pull/1027)
- Bump [@​sigstore/core](https://github.com/sigstore/core) from 3.1.0 to 3.2.1 in [#​1023](https://github.com/docker/login-action/pull/1023)
- Bump [@​sigstore/verify](https://github.com/sigstore/verify) from 3.1.0 to 3.1.1 in [#​1029](https://github.com/docker/login-action/pull/1029)
- Bump http-proxy-agent and https-proxy-agent to 9.1.0 in [#​1017](https://github.com/docker/login-action/pull/1017)
- Bump js-yaml from 4.1.1 to 5.2.0 in [#​1028](https://github.com/docker/login-action/pull/1028)
- Bump sigstore from 4.1.0 to 4.1.1 in [#​1031](https://github.com/docker/login-action/pull/1031)
- Bump tmp from 0.2.5 to 0.2.7 in [#​1002](https://github.com/docker/login-action/pull/1002)
- Bump undici from 6.24.1 to 6.27.0 in [#​1020](https://github.com/docker/login-action/pull/1020)
- Bump vite from 7.3.3 to 7.3.6 in [#​1019](https://github.com/docker/login-action/pull/1019)
**Full Changelog**: <https://github.com/docker/login-action/compare/v4.2.0...v4.3.0>
### [`v4.2.0`](https://github.com/docker/login-action/releases/tag/v4.2.0)
[Compare Source](https://github.com/docker/login-action/compare/v4.1.0...v4.2.0)
- Bump [@​actions/core](https://github.com/actions/core) from 3.0.0 to 3.0.1 in [#​976](https://github.com/docker/login-action/pull/976)
- Bump [@​aws-sdk/client-ecr](https://github.com/aws-sdk/client-ecr) and [@​aws-sdk/client-ecr-public](https://github.com/aws-sdk/client-ecr-public) to 3.1050.0 in [#​960](https://github.com/docker/login-action/pull/960)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.86.0 to 0.90.0 in [#​970](https://github.com/docker/login-action/pull/970)
- Bump brace-expansion from 2.0.1 to 5.0.6 in [#​993](https://github.com/docker/login-action/pull/993)
- Bump fast-xml-builder from 1.1.4 to 1.2.0 in [#​985](https://github.com/docker/login-action/pull/985)
- Bump fast-xml-parser from 5.3.6 to 5.8.0 in [#​963](https://github.com/docker/login-action/pull/963)
- Bump http-proxy-agent and https-proxy-agent to 9.0.0 in [#​961](https://github.com/docker/login-action/pull/961)
- Bump postcss from 8.5.6 to 8.5.10 in [#​979](https://github.com/docker/login-action/pull/979)
- Bump tar from 6.2.1 to 7.5.15 in [#​991](https://github.com/docker/login-action/pull/991)
- Bump vite from 7.3.1 to 7.3.3 in [#​986](https://github.com/docker/login-action/pull/986)
**Full Changelog**: <https://github.com/docker/login-action/compare/v4.1.0...v4.2.0>
### [`v4.1.0`](https://github.com/docker/login-action/releases/tag/v4.1.0)
[Compare Source](https://github.com/docker/login-action/compare/v4...v4.1.0)
- Fix scoped Docker Hub cleanup path when registry is omitted by [@​crazy-max](https://github.com/crazy-max) in [#​945](https://github.com/docker/login-action/pull/945)
- Bump [@​aws-sdk/client-ecr](https://github.com/aws-sdk/client-ecr) and [@​aws-sdk/client-ecr-public](https://github.com/aws-sdk/client-ecr-public) to 3.1020.0 in [#​930](https://github.com/docker/login-action/pull/930)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.77.0 to 0.86.0 in [#​932](https://github.com/docker/login-action/pull/932) [#​936](https://github.com/docker/login-action/pull/936)
- Bump brace-expansion from 1.1.12 to 1.1.13 in [#​952](https://github.com/docker/login-action/pull/952)
- Bump fast-xml-parser from 5.3.4 to 5.3.6 in [#​942](https://github.com/docker/login-action/pull/942)
- Bump flatted from 3.3.3 to 3.4.2 in [#​944](https://github.com/docker/login-action/pull/944)
- Bump glob from 10.3.12 to 10.5.0 in [#​940](https://github.com/docker/login-action/pull/940)
- Bump handlebars from 4.7.8 to 4.7.9 in [#​949](https://github.com/docker/login-action/pull/949)
- Bump http-proxy-agent and https-proxy-agent to 8.0.0 in [#​937](https://github.com/docker/login-action/pull/937)
- Bump lodash from 4.17.23 to 4.18.1 in [#​958](https://github.com/docker/login-action/pull/958)
- Bump minimatch from 3.1.2 to 3.1.5 in [#​941](https://github.com/docker/login-action/pull/941)
- Bump picomatch from 4.0.3 to 4.0.4 in [#​948](https://github.com/docker/login-action/pull/948)
- Bump undici from 6.23.0 to 6.24.1 in [#​938](https://github.com/docker/login-action/pull/938)
**Full Changelog**: <https://github.com/docker/login-action/compare/v4.0.0...v4.1.0>
### [`v4.0.0`](https://github.com/docker/login-action/releases/tag/v4.0.0)
[Compare Source](https://github.com/docker/login-action/compare/v4...v4)
- Node 24 as default runtime (requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later) by [@​crazy-max](https://github.com/crazy-max) in [#​929](https://github.com/docker/login-action/pull/929)
- Switch to ESM and update config/test wiring by [@​crazy-max](https://github.com/crazy-max) in [#​927](https://github.com/docker/login-action/pull/927)
- Bump [@​actions/core](https://github.com/actions/core) from 1.11.1 to 3.0.0 in [#​919](https://github.com/docker/login-action/pull/919)
- Bump [@​aws-sdk/client-ecr](https://github.com/aws-sdk/client-ecr) from 3.890.0 to 3.1000.0 in [#​909](https://github.com/docker/login-action/pull/909) [#​920](https://github.com/docker/login-action/pull/920)
- Bump [@​aws-sdk/client-ecr-public](https://github.com/aws-sdk/client-ecr-public) from 3.890.0 to 3.1000.0 in [#​909](https://github.com/docker/login-action/pull/909) [#​920](https://github.com/docker/login-action/pull/920)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.63.0 to 0.77.0 in [#​910](https://github.com/docker/login-action/pull/910) [#​928](https://github.com/docker/login-action/pull/928)
- Bump [@​isaacs/brace-expansion](https://github.com/isaacs/brace-expansion) from 5.0.0 to 5.0.1 in [#​921](https://github.com/docker/login-action/pull/921)
- Bump js-yaml from 4.1.0 to 4.1.1 in [#​901](https://github.com/docker/login-action/pull/901)
**Full Changelog**: <https://github.com/docker/login-action/compare/v3.7.0...v4.0.0>
### [`v4`](https://github.com/docker/login-action/compare/v3.7.0...v4)
[Compare Source](https://github.com/docker/login-action/compare/v3.7.0...v4)
</details>
<details>
<summary>docker/metadata-action (docker/metadata-action)</summary>
### [`v6.2.0`](https://github.com/docker/metadata-action/releases/tag/v6.2.0)
[Compare Source](https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0)
- Preserve names in esbuild bundle by [@​crazy-max](https://github.com/crazy-max) in [#​689](https://github.com/docker/metadata-action/pull/689)
- Bump [@​actions/core](https://github.com/actions/core) from 3.0.0 to 3.0.1 in [#​663](https://github.com/docker/metadata-action/pull/663)
- Bump [@​actions/github](https://github.com/actions/github) from 9.0.0 to 9.1.1 in [#​666](https://github.com/docker/metadata-action/pull/666)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.90.0 to 0.92.0 in [#​672](https://github.com/docker/metadata-action/pull/672) [#​696](https://github.com/docker/metadata-action/pull/696)
- Bump [@​sigstore/core](https://github.com/sigstore/core) from 3.1.0 to 3.2.1 in [#​690](https://github.com/docker/metadata-action/pull/690)
- Bump [@​sigstore/verify](https://github.com/sigstore/verify) from 3.1.0 to 3.1.1 in [#​693](https://github.com/docker/metadata-action/pull/693)
- Bump csv-parse from 6.2.1 to 7.0.0 in [#​683](https://github.com/docker/metadata-action/pull/683)
- Bump js-yaml from 4.1.1 to 4.3.0 in [#​688](https://github.com/docker/metadata-action/pull/688)
- Bump moment-timezone from 0.6.1 to 0.6.2 in [#​664](https://github.com/docker/metadata-action/pull/664)
- Bump semver from 7.7.4 to 7.8.5 in [#​665](https://github.com/docker/metadata-action/pull/665) [#​695](https://github.com/docker/metadata-action/pull/695)
- Bump sigstore from 4.1.0 to 4.1.1 in [#​694](https://github.com/docker/metadata-action/pull/694)
- Bump tmp from 0.2.5 to 0.2.7 in [#​673](https://github.com/docker/metadata-action/pull/673)
- Bump undici from 6.25.0 to 6.27.0 in [#​686](https://github.com/docker/metadata-action/pull/686)
- Bump vite from 7.3.2 to 7.3.6 in [#​685](https://github.com/docker/metadata-action/pull/685)
**Full Changelog**: <https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0>
### [`v6.1.0`](https://github.com/docker/metadata-action/releases/tag/v6.1.0)
[Compare Source](https://github.com/docker/metadata-action/compare/v6...v6.1.0)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.79.0 to 0.90.0 in [#​613](https://github.com/docker/metadata-action/pull/613)
- Bump brace-expansion from 1.1.12 to 5.0.6 in [#​658](https://github.com/docker/metadata-action/pull/658) [#​630](https://github.com/docker/metadata-action/pull/630)
- Bump csv-parse from 6.1.0 to 6.2.1 in [#​617](https://github.com/docker/metadata-action/pull/617)
- Bump fast-xml-parser from 5.4.2 to 5.8.0 in [#​620](https://github.com/docker/metadata-action/pull/620)
- Bump flatted from 3.3.3 to 3.4.2 in [#​623](https://github.com/docker/metadata-action/pull/623)
- Bump glob from 10.3.15 to 10.5.0 in [#​621](https://github.com/docker/metadata-action/pull/621)
- Bump handlebars from 4.7.8 to 4.7.9 in [#​629](https://github.com/docker/metadata-action/pull/629)
- Bump lodash from 4.17.23 to 4.18.1 in [#​639](https://github.com/docker/metadata-action/pull/639)
- Bump moment-timezone from 0.6.0 to 0.6.1 in [#​619](https://github.com/docker/metadata-action/pull/619)
- Bump picomatch from 4.0.3 to 4.0.4 in [#​626](https://github.com/docker/metadata-action/pull/626)
- Bump postcss from 8.5.6 to 8.5.10 in [#​649](https://github.com/docker/metadata-action/pull/649)
- Bump tar from 6.2.1 to 7.5.15 in [#​657](https://github.com/docker/metadata-action/pull/657)
- Bump undici from 6.23.0 to 6.25.0 in [#​614](https://github.com/docker/metadata-action/pull/614)
- Bump vite from 7.3.1 to 7.3.2 in [#​637](https://github.com/docker/metadata-action/pull/637)
**Full Changelog**: <https://github.com/docker/metadata-action/compare/v6.0.0...v6.1.0>
### [`v6.0.0`](https://github.com/docker/metadata-action/releases/tag/v6.0.0)
[Compare Source](https://github.com/docker/metadata-action/compare/v6...v6)
- Node 24 as default runtime (requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later) by [@​crazy-max](https://github.com/crazy-max) in [#​605](https://github.com/docker/metadata-action/pull/605)
- List inputs now preserve `#` inside values while still supporting full-line `#` comments by [@​crazy-max](https://github.com/crazy-max) in [#​607](https://github.com/docker/metadata-action/pull/607)
- Switch to ESM and update config/test wiring by [@​crazy-max](https://github.com/crazy-max) in [#​602](https://github.com/docker/metadata-action/pull/602)
- Bump lodash from 4.17.21 to 4.17.23 in [#​588](https://github.com/docker/metadata-action/pull/588)
- Bump [@​actions/core](https://github.com/actions/core) from 1.11.1 to 3.0.0 in [#​599](https://github.com/docker/metadata-action/pull/599)
- Bump [@​actions/github](https://github.com/actions/github) from 6.0.1 to 9.0.0 in [#​597](https://github.com/docker/metadata-action/pull/597)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.68.0 to 0.79.0 in [#​604](https://github.com/docker/metadata-action/pull/604)
- Bump [@​isaacs/brace-expansion](https://github.com/isaacs/brace-expansion) from 5.0.0 to 5.0.1 in [#​600](https://github.com/docker/metadata-action/pull/600)
- Bump semver from 7.7.3 to 7.7.4 in [#​603](https://github.com/docker/metadata-action/pull/603)
**Full Changelog**: <https://github.com/docker/metadata-action/compare/v5.10.0...v6.0.0>
### [`v6`](https://github.com/docker/metadata-action/compare/v5.10.0...v6)
[Compare Source](https://github.com/docker/metadata-action/compare/v5.10.0...v6)
</details>
<details>
<summary>docker/setup-buildx-action (docker/setup-buildx-action)</summary>
### [`v4.2.0`](https://github.com/docker/setup-buildx-action/releases/tag/v4.2.0)
[Compare Source](https://github.com/docker/setup-buildx-action/compare/v4.1.0...v4.2.0)
- Preserve names in esbuild bundle by [@​crazy-max](https://github.com/crazy-max) in [#​572](https://github.com/docker/setup-buildx-action/pull/572)
- Bump [@​actions/core](https://github.com/actions/core) from 3.0.0 to 3.0.1 in [#​551](https://github.com/docker/setup-buildx-action/pull/551)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.90.0 to 0.92.0 in [#​557](https://github.com/docker/setup-buildx-action/pull/557) [#​580](https://github.com/docker/setup-buildx-action/pull/580)
- Bump [@​sigstore/core](https://github.com/sigstore/core) from 3.1.0 to 3.2.1 in [#​573](https://github.com/docker/setup-buildx-action/pull/573)
- Bump [@​sigstore/verify](https://github.com/sigstore/verify) from 3.1.0 to 3.1.1 in [#​576](https://github.com/docker/setup-buildx-action/pull/576)
- Bump js-yaml from 4.1.1 to 5.2.0 in [#​562](https://github.com/docker/setup-buildx-action/pull/562)
- Bump sigstore from 4.1.0 to 4.1.1 in [#​577](https://github.com/docker/setup-buildx-action/pull/577)
- Bump tmp from 0.2.5 to 0.2.7 in [#​556](https://github.com/docker/setup-buildx-action/pull/556)
- Bump undici from 6.25.0 to 6.27.0 in [#​570](https://github.com/docker/setup-buildx-action/pull/570)
- Bump vite from 7.3.2 to 7.3.6 in [#​569](https://github.com/docker/setup-buildx-action/pull/569)
**Full Changelog**: <https://github.com/docker/setup-buildx-action/compare/v4.1.0...v4.2.0>
### [`v4.1.0`](https://github.com/docker/setup-buildx-action/releases/tag/v4.1.0)
[Compare Source](https://github.com/docker/setup-buildx-action/compare/v4...v4.1.0)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.79.0 to 0.90.0 in [#​489](https://github.com/docker/setup-buildx-action/pull/489)
- Bump brace-expansion from 1.1.12 to 5.0.6 in [#​547](https://github.com/docker/setup-buildx-action/pull/547) [#​508](https://github.com/docker/setup-buildx-action/pull/508)
- Bump fast-xml-builder from 1.0.0 to 1.2.0 in [#​540](https://github.com/docker/setup-buildx-action/pull/540)
- Bump fast-xml-parser from 5.4.2 to 5.8.0 in [#​496](https://github.com/docker/setup-buildx-action/pull/496)
- Bump flatted from 3.3.3 to 3.4.2 in [#​499](https://github.com/docker/setup-buildx-action/pull/499)
- Bump glob from 10.3.12 to 13.0.6 in [#​495](https://github.com/docker/setup-buildx-action/pull/495)
- Bump handlebars from 4.7.8 to 4.7.9 in [#​504](https://github.com/docker/setup-buildx-action/pull/504)
- Bump lodash from 4.17.23 to 4.18.1 in [#​523](https://github.com/docker/setup-buildx-action/pull/523)
- Bump picomatch from 4.0.3 to 4.0.4 in [#​503](https://github.com/docker/setup-buildx-action/pull/503)
- Bump postcss from 8.5.6 to 8.5.10 in [#​537](https://github.com/docker/setup-buildx-action/pull/537)
- Bump tar from 6.2.1 to 7.5.15 in [#​545](https://github.com/docker/setup-buildx-action/pull/545)
- Bump undici from 6.23.0 to 6.25.0 in [#​492](https://github.com/docker/setup-buildx-action/pull/492)
- Bump vite from 7.3.1 to 7.3.2 in [#​520](https://github.com/docker/setup-buildx-action/pull/520)
**Full Changelog**: <https://github.com/docker/setup-buildx-action/compare/v4.0.0...v4.1.0>
### [`v4.0.0`](https://github.com/docker/setup-buildx-action/releases/tag/v4.0.0)
[Compare Source](https://github.com/docker/setup-buildx-action/compare/v4...v4)
- Node 24 as default runtime (requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later) by [@​crazy-max](https://github.com/crazy-max) in [#​483](https://github.com/docker/setup-buildx-action/pull/483)
- Remove deprecated inputs/outputs by [@​crazy-max](https://github.com/crazy-max) in [#​464](https://github.com/docker/setup-buildx-action/pull/464)
- Switch to ESM and update config/test wiring by [@​crazy-max](https://github.com/crazy-max) in [#​481](https://github.com/docker/setup-buildx-action/pull/481)
- Bump [@​actions/core](https://github.com/actions/core) from 1.11.1 to 3.0.0 in [#​475](https://github.com/docker/setup-buildx-action/pull/475)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.63.0 to 0.79.0 in [#​482](https://github.com/docker/setup-buildx-action/pull/482) [#​485](https://github.com/docker/setup-buildx-action/pull/485)
- Bump js-yaml from 4.1.0 to 4.1.1 in [#​452](https://github.com/docker/setup-buildx-action/pull/452)
- Bump lodash from 4.17.21 to 4.17.23 in [#​472](https://github.com/docker/setup-buildx-action/pull/472)
- Bump minimatch from 3.1.2 to 3.1.5 in [#​480](https://github.com/docker/setup-buildx-action/pull/480)
**Full Changelog**: <https://github.com/docker/setup-buildx-action/compare/v3.12.0...v4.0.0>
### [`v4`](https://github.com/docker/setup-buildx-action/compare/v3.12.0...v4)
[Compare Source](https://github.com/docker/setup-buildx-action/compare/v3.12.0...v4)
</details>
<details>
<summary>docker/setup-qemu-action (docker/setup-qemu-action)</summary>
### [`v4.2.0`](https://github.com/docker/setup-qemu-action/releases/tag/v4.2.0)
[Compare Source](https://github.com/docker/setup-qemu-action/compare/v4.1.0...v4.2.0)
- Preserve names in esbuild bundle by [@​crazy-max](https://github.com/crazy-max) in [#​311](https://github.com/docker/setup-qemu-action/pull/311)
- Bump [@​actions/core](https://github.com/actions/core) from 3.0.0 to 3.0.1 in [#​295](https://github.com/docker/setup-qemu-action/pull/295)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.91.0 to 0.92.0 in [#​315](https://github.com/docker/setup-qemu-action/pull/315)
- Bump [@​sigstore/core](https://github.com/sigstore/core) from 3.1.0 to 3.2.1 in [#​312](https://github.com/docker/setup-qemu-action/pull/312)
- Bump js-yaml from 4.1.1 to 4.2.0 in [#​310](https://github.com/docker/setup-qemu-action/pull/310)
- Bump tmp from 0.2.6 to 0.2.7 in [#​304](https://github.com/docker/setup-qemu-action/pull/304)
- Bump undici from 6.26.0 to 6.27.0 in [#​308](https://github.com/docker/setup-qemu-action/pull/308)
- Bump vite from 7.3.2 to 7.3.6 in [#​307](https://github.com/docker/setup-qemu-action/pull/307)
**Full Changelog**: <https://github.com/docker/setup-qemu-action/compare/v4.1.0...v4.2.0>
### [`v4.1.0`](https://github.com/docker/setup-qemu-action/releases/tag/v4.1.0)
[Compare Source](https://github.com/docker/setup-qemu-action/compare/v4...v4.1.0)
- Add `reset` input to uninstall current emulators by [@​crazy-max](https://github.com/crazy-max) in [#​21](https://github.com/docker/setup-qemu-action/pull/21)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.77.0 to 0.91.0 in [#​250](https://github.com/docker/setup-qemu-action/pull/250) [#​247](https://github.com/docker/setup-qemu-action/pull/247)
- Bump brace-expansion from 1.1.12 to 1.1.15 in [#​265](https://github.com/docker/setup-qemu-action/pull/265)
- Bump fast-xml-builder from 1.0.0 to 1.2.0 in [#​286](https://github.com/docker/setup-qemu-action/pull/286)
- Bump fast-xml-parser from 5.4.2 to 5.8.0 in [#​255](https://github.com/docker/setup-qemu-action/pull/255)
- Bump flatted from 3.3.3 to 3.4.2 in [#​257](https://github.com/docker/setup-qemu-action/pull/257)
- Bump glob from 10.3.15 to 10.5.0 in [#​254](https://github.com/docker/setup-qemu-action/pull/254)
- Bump handlebars from 4.7.8 to 4.7.9 in [#​262](https://github.com/docker/setup-qemu-action/pull/262)
- Bump lodash from 4.17.23 to 4.18.1 in [#​273](https://github.com/docker/setup-qemu-action/pull/273)
- Bump postcss from 8.5.6 to 8.5.10 in [#​285](https://github.com/docker/setup-qemu-action/pull/285)
- Bump tar from 6.2.1 to 7.5.15 in [#​287](https://github.com/docker/setup-qemu-action/pull/287)
- Bump tmp from 0.2.5 to 0.2.6 in [#​291](https://github.com/docker/setup-qemu-action/pull/291)
- Bump undici from 6.23.0 to 6.26.0 in [#​251](https://github.com/docker/setup-qemu-action/pull/251)
- Bump vite from 7.3.1 to 7.3.2 in [#​271](https://github.com/docker/setup-qemu-action/pull/271)
**Full Changelog**: <https://github.com/docker/setup-qemu-action/compare/v4.0.0...v4.1.0>
### [`v4.0.0`](https://github.com/docker/setup-qemu-action/releases/tag/v4.0.0)
[Compare Source](https://github.com/docker/setup-qemu-action/compare/v4...v4)
- Node 24 as default runtime (requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later) by [@​crazy-max](https://github.com/crazy-max) in [#​245](https://github.com/docker/setup-qemu-action/pull/245)
- Switch to ESM and update config/test wiring by [@​crazy-max](https://github.com/crazy-max) in [#​241](https://github.com/docker/setup-qemu-action/pull/241)
- Bump [@​actions/core](https://github.com/actions/core) from 1.11.1 to 3.0.0 in [#​244](https://github.com/docker/setup-qemu-action/pull/244)
- Bump [@​docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.67.0 to 0.77.0 in [#​243](https://github.com/docker/setup-qemu-action/pull/243)
- Bump [@​isaacs/brace-expansion](https://github.com/isaacs/brace-expansion) from 5.0.0 to 5.0.1 in [#​240](https://github.com/docker/setup-qemu-action/pull/240)
- Bump js-yaml from 3.14.1 to 3.14.2 in [#​231](https://github.com/docker/setup-qemu-action/pull/231)
- Bump lodash from 4.17.21 to 4.17.23 in [#​238](https://github.com/docker/setup-qemu-action/pull/238)
**Full Changelog**: <https://github.com/docker/setup-qemu-action/compare/v3.7.0...v4.0.0>
### [`v4`](https://github.com/docker/setup-qemu-action/compare/v3.7.0...v4)
[Compare Source](https://github.com/docker/setup-qemu-action/compare/v3.7.0...v4)
</details>
<details>
<summary>jsdom/jsdom (jsdom)</summary>
### [`v29.1.1`](https://github.com/jsdom/jsdom/releases/tag/v29.1.1)
[Compare Source](https://github.com/jsdom/jsdom/compare/v29.1.0...v29.1.1)
- Fixed `'border-radius'` computed style serialization. ([@​asamuzaK](https://github.com/asamuzaK))
- Fixed computed style computation when using `'background-origin'` and `'background-clip'` CSS properties. ([@​asamuzaK](https://github.com/asamuzaK))
- Significantly optimized initial calls to `getComputedStyle()`, before the cache warms up. ([@​asamuzaK](https://github.com/asamuzaK))
### [`v29.1.0`](https://github.com/jsdom/jsdom/releases/tag/v29.1.0)
[Compare Source](https://github.com/jsdom/jsdom/compare/v29.0.2...v29.1.0)
- Added basic support for the ratio CSS type. ([@​asamuzaK](https://github.com/asamuzaK))
- Fixed `getComputedStyle()` sometimes returning outdated results after CSS was modified. ([@​asamuzaK](https://github.com/asamuzaK))
### [`v29.0.2`](https://github.com/jsdom/jsdom/releases/tag/v29.0.2)
[Compare Source](https://github.com/jsdom/jsdom/compare/v29.0.1...v29.0.2)
- Significantly improved and sped up `getComputedStyle()`. Computed value rules are now applied across a broader set of properties, and include fixes related to inheritance, defaulting keywords, custom properties, and color-related values such as `currentcolor` and system colors. ([@​asamuzaK](https://github.com/asamuzaK))
- Fixed CSS `'background`' and `'border'` shorthand parsing. ([@​asamuzaK](https://github.com/asamuzaK))
### [`v29.0.1`](https://github.com/jsdom/jsdom/releases/tag/v29.0.1)
[Compare Source](https://github.com/jsdom/jsdom/compare/v29.0.0...v29.0.1)
- Fixed CSS parsing of `'border'`, `'background'`, and their sub-shorthands containing keywords or `var()`. ([@​asamuzaK](https://github.com/asamuzaK))
- Fixed `getComputedStyle()` to return a more functional `CSSStyleDeclaration` object, including indexed access support, which regressed in v29.0.0.
### [`v29.0.0`](https://github.com/jsdom/jsdom/releases/tag/v29.0.0)
[Compare Source](https://github.com/jsdom/jsdom/compare/v28.1.0...v29.0.0)
Breaking changes:
- Node.js v22.13.0+ is now the minimum supported v22 version (was v22.12.0+).
Other changes:
- Overhauled the CSSOM implementation, replacing the [`@acemir/cssom`](https://www.npmjs.com/package/@​acemir/cssom) and [`cssstyle`](https://github.com/jsdom/cssstyle) dependencies with fresh internal implementations built on webidl2js wrappers and the [`css-tree`](https://www.npmjs.com/package/css-tree) parser. Serialization, parsing, and API behavior is improved in various ways, especially around edge cases.
- Added `CSSCounterStyleRule` and `CSSNamespaceRule` to jsdom `Window`s.
- Added `cssMediaRule.matches` and `cssSupportsRule.matches` getters.
- Added proper media query parsing in `MediaList`, using `css-tree` instead of naive comma-splitting. Invalid queries become `"not all"` per spec.
- Added `cssKeyframeRule.keyText` getter/setter validation.
- Added `cssStyleRule.selectorText` setter validation: invalid selectors are now rejected.
- Added `styleSheet.ownerNode`, `styleSheet.href`, and `styleSheet.title`.
- Added bad port blocking per the [fetch specification](https://fetch.spec.whatwg.org/#bad-port), preventing fetches to commonly-abused ports.
- Improved `Document` initialization performance by lazily initializing the CSS selector engine, avoiding \~0.5 ms of overhead per `Document`. ([@​thypon](https://github.com/thypon))
- Fixed a memory leak when stylesheets were removed from the document.
- Fixed `CSSStyleDeclaration` modifications to properly trigger custom element reactions.
- Fixed nested `@media` rule parsing.
- Fixed `CSSStyleSheet`'s "disallow modification" flag not being checked in all mutation methods.
- Fixed `XMLHttpRequest`'s `response` getter returning parsed JSON during the `LOADING` state instead of `null`.
- Fixed `getComputedStyle()` crashing in XHTML documents when stylesheets contained at-rules such as `@page` or `@font-face`.
- Fixed a potential hang in synchronous `XMLHttpRequest` caused by a race condition with the worker thread's idle timeout.
</details>
<details>
<summary>pnpm/pnpm (pnpm)</summary>
### [`v11.10.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#11100)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.9.0...v11.10.0)
##### Minor Changes
- [`e2e3c81`](https://github.com/pnpm/pnpm/commit/e2e3c81): Added the `issues` command as an alias of `bugs`, so `pnpm issues` opens the package's bug tracker URL in the browser.
- [`8491f8e`](https://github.com/pnpm/pnpm/commit/8491f8e): Added the `prefix` command which prints the current package prefix directory (or global prefix directory if `-g` / `--global` is used).
- [`3425e80`](https://github.com/pnpm/pnpm/commit/3425e80): Added an `_auth` setting for configuring registry authentication as a single structured (URL-keyed) value. It can be set in the **global** pnpm config (`config.yaml`) or, for CI, via the `pnpm_config__auth` environment variable. The env form sidesteps the GitHub Actions / bash / zsh limitation that broke the existing `pnpm_config_//host/:_authToken=…` form (env var names containing `/`, `:`, or `.` are silently dropped). Closes [#​12314](https://github.com/pnpm/pnpm/issues/12314).
The value is keyed by registry URL so each secret is explicitly bound to the host that may receive it. Registry URL keys must use `http` or `https` and must not include credentials, query strings, or fragments:
```sh
export pnpm_config__auth='{"https://registry.npmjs.org":{"@​":{"authToken":"npm-token"},"@​org":{"authToken":"org-token"}}}'
```
The equivalent in the global `config.yaml`:
```yaml
_auth:
https://registry.npmjs.org:
"@​":
authToken: npm-token
"@​org":
authToken: org-token
```
Within each registry URL, `@` means registry-wide/default credentials and package scopes like `@org` bind credentials to that scope on the same host. The only supported credential field is `authToken` (maps to `_authToken` / bearer auth); the deprecated `basicAuth` / `username` + `password` forms are intentionally not accepted here.
Each entry also infers a trusted registry route: `@` routes the default registry (and `pnpm add <pkg>` resolves there), and `@org` routes that scope. Because the credential and destination host arrive in one trusted value, repo-controlled `pnpm-workspace.yaml` or project `.npmrc` cannot redirect the token to a different host. `_auth` is honored **only** from the env var and the global config — it is ignored in a project `pnpm-workspace.yaml` / `.npmrc`, so repo-controlled config can never supply registry auth. Precedence: CLI flags (`--registry`, `--@​scope:registry`) > `pnpm_config__auth` > global `config.yaml` `_auth` > `pnpm-workspace.yaml`.
Both `pnpm_config__auth` (lowercase, documented form) and `PNPM_CONFIG__AUTH` (all-caps, the shell convention some CI runners apply) are honored. If both are set, lowercase wins unless it is empty, in which case uppercase is used. The env var wins over the global `config.yaml` `_auth` on a conflicting key. `tokenHelper` is not supported in `_auth`. Parsing is strict: a malformed value (bad JSON, wrong shape, invalid registry URL or scope, an unsupported credential field) fails fast with an error rather than being silently dropped.
**Pacquet parity note:** the pacquet (Rust) port supports the same single credential field as the TS CLI: `authToken`.
- [`a33eeec`](https://github.com/pnpm/pnpm/commit/a33eeec): `pnpm self-update` and `packageManager` version-switching can now install and link pnpm v12 (the Rust port), published with equal content under both the `pnpm` and `@pnpm/exe` names on the `next-12` dist-tag. Its native binaries ship as `@pnpm/exe.<platform>-<arch>` packages, which pnpm's built-in installer links directly — no Node.js launcher, so the command pays no Node startup cost. v12 is initialized exactly like `@pnpm/exe`, including per-platform global-virtual-store hashing. From v12 onward the install converges on the unscoped `pnpm` package (the Rust exe) — even when updating from the SEA `@pnpm/exe` build.
- [`1dd12bd`](https://github.com/pnpm/pnpm/commit/1dd12bd): When resolving through a pnpr install-accelerator server, pnpm no longer forwards its own upstream registry credentials in the resolve request. Only the `Authorization` header identifying the caller to pnpr is sent. The pnpr server now selects upstream credentials from its own route policy (operator-configured upstream credential aliases), so private dependencies resolve through a pnpr-managed alias the caller is authorized to use, rather than by sending the client's registry tokens to the server.
- [`1e81761`](https://github.com/pnpm/pnpm/commit/1e81761): Expose web authentication `authUrl` and `doneUrl` in JSON error output when OTP is required in a non-interactive terminal [#​12724](https://github.com/pnpm/pnpm/issues/12724).
##### Patch Changes
- [`2f389d6`](https://github.com/pnpm/pnpm/commit/2f389d6): Added the Node.js release team's new signing key (Stewart X Addison, `655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD`) to the embedded Node.js release keys, so runtimes whose `SHASUMS256.txt` is signed by the new releaser verify successfully.
- [`acbdb94`](https://github.com/pnpm/pnpm/commit/acbdb94): Fixed shell tab completion not suggesting workspaces after the `-F` alias for `--filter` option.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): Fixed `pnpm up -r <pkg>` bumping unrelated packages that have open semver ranges. Previously, any update mutation nullified the lockfile-derived `preferredVersions` globally, so packages with `^x.y.z` ranges could re-resolve to newer compatible versions even though the user only asked to update a specific package. The install layer now always seeds `preferredVersions` from the lockfile, and caller-supplied preferred versions (such as the vulnerability penalties of `pnpm audit --fix`) layer on top of the seed instead of replacing it. The targeted package still bumps: the per-resolve `updateRequested` flag makes the resolver ignore the target's own lockfile pins.
Closes [#​10662](https://github.com/pnpm/pnpm/issues/10662).
- [`d539172`](https://github.com/pnpm/pnpm/commit/d539172): Fixed pnpm pack and pnpm publish failing when prepack generates files that are included in the package and postpack cleans them up.
- [`be6505a`](https://github.com/pnpm/pnpm/commit/be6505a): Hardened global package management:
- On Windows, removing or updating a global package now also cleans up the `node.exe` flavor of a bin, so a stale `node.exe` no longer survives on `PATH` after uninstall, and a new global install no longer silently overwrites an existing `node.exe`.
- `pnpm add -g pnpm@<version>` (and `@pnpm/exe@<version>`) is now rejected like the bare `pnpm` form, pointing to `pnpm self-update`.
- Dependency aliases read from a global package's manifest are validated before being joined onto `node_modules` paths, preventing a tampered manifest from escaping the install directory.
- Each global install group is created in its own freshly-made directory (no longer reusing a colliding or pre-existing path).
- Removing or updating a global package no longer unlinks a bin that belongs to a different globally installed package.
- [`25c7388`](https://github.com/pnpm/pnpm/commit/25c7388): pnpm now rejects `jsr:` specifiers whose package name is not a valid npm package name — an empty scope or name (e.g. `jsr:@​scope/`), path separators inside the name, or any other shape `validate-npm-package-name` rejects — with `ERR_PNPM_INVALID_JSR_PACKAGE_NAME` instead of silently converting them into a malformed `@jsr/...` npm package name.
- [`25c7388`](https://github.com/pnpm/pnpm/commit/25c7388): pnpm now rejects named-registry specifiers (e.g. `gh:`) whose package name is not a valid npm package name — an empty scope (e.g. `gh:@​/bar`), path separators inside the name (e.g. `gh:@​scope/../name`), or any other shape `validate-npm-package-name` rejects — with `ERR_PNPM_INVALID_NAMED_REGISTRY_PACKAGE_NAME` instead of passing the name through to registry URLs and metadata cache file paths.
- [`96da7c5`](https://github.com/pnpm/pnpm/commit/96da7c5): node-gyp's `gyp_main.py` and `gyp` entrypoints are now packed with the executable bit in the `pnpm` and `@pnpm/exe` tarballs. Without it, building native addons from source could fail with a permission error.
- [`99982b9`](https://github.com/pnpm/pnpm/commit/99982b9): Sped up resolution and reduced memory use against registries that ignore npm's abbreviated metadata format and always return the full package document (for example, Azure DevOps Artifacts). pnpm now strips such documents down to the abbreviated field set before caching them. Resolution output is unchanged, and registries that honor the abbreviated format (such as the npm registry) pay no extra cost.
- [`11a7fdd`](https://github.com/pnpm/pnpm/commit/11a7fdd): Sped up offline and `--prefer-offline` resolution on large workspaces (e.g. `pnpm dedupe --offline`, `pnpm install --offline`). Package metadata loaded from the local cache is now kept in memory, so each package's metadata is parsed once per command instead of once per dependent that references it.
- [`2c7369d`](https://github.com/pnpm/pnpm/commit/2c7369d): `pnpm pack-app` now rejects `--entry` / `pnpm.app.entry` and `--output-dir` / `pnpm.app.outputDir` values that are absolute paths or escape the project directory via `..` (or a symlink that resolves outside it), and refuses to write the produced executable when its target path already exists as a symlink (or other non-regular file). This prevents a repository-controlled `package.json` from embedding host files (such as an SSH key) into the produced executable, writing build artifacts outside the project, or overwriting an arbitrary file through a committed symlink. The new error codes are `ERR_PNPM_PACK_APP_ENTRY_OUTSIDE_PROJECT`, `ERR_PNPM_PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT`, and `ERR_PNPM_PACK_APP_OUTPUT_FILE_NOT_REGULAR`.
When ad-hoc signing macOS targets, `pnpm pack-app` now runs the system `codesign` by absolute path and resolves `ldid` to a location outside the project, so a repository-controlled `node_modules/.bin` on `PATH` cannot hijack the signer.
- [`ce5d5a5`](https://github.com/pnpm/pnpm/commit/ce5d5a5): Relative paths in `patchedDependencies` are now resolved against the lockfile directory when computing patch file hashes, so running `pnpm install` from a subdirectory no longer fails with `ENOENT` looking for the patch file in the wrong location [#​12762](https://github.com/pnpm/pnpm/pull/12762).
- [`ebb4096`](https://github.com/pnpm/pnpm/commit/ebb4096): `pnpm peers` no longer reports a conflict for a missing peer dependency that is ignored via `pnpm.peerDependencyRules.ignoreMissing`.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): Fixed a prototype-pollution hazard when seeding preferred versions: a dependency named `__proto__` in a manifest or in `pnpm-lock.yaml` could write through `Object.prototype` (or crash the install) while the preferred-versions map was being built. The maps are now null-prototype objects, so crafted package names land as plain keys.
- [`f38e696`](https://github.com/pnpm/pnpm/commit/f38e696): Hardened `pnpm deploy --force` so it refuses unsafe deploy targets such as workspace roots, parent directories, out-of-workspace paths, and symlinked target parents.
- [`806c3ec`](https://github.com/pnpm/pnpm/commit/806c3ec): pnpm no longer warns about ignored project-level auth settings when `PNPM_CONFIG_NPMRC_AUTH_FILE` points at the project `.npmrc` — setting it to that file is an explicit opt-in to trusting it, so auth env variables in it are expanded [pnpm/pnpm#12480](https://github.com/pnpm/pnpm/issues/12480).
- [`991405e`](https://github.com/pnpm/pnpm/commit/991405e): Restore differential rendering (`ansi-diff`) to fix duplicated output lines introduced by [#​12351](https://github.com/pnpm/pnpm/issues/12351).
- [`c121235`](https://github.com/pnpm/pnpm/commit/c121235): Fixed the topological order of `--filter`ed commands (`pnpm run`, `pnpm exec`, `pnpm publish`, `pnpm pack`, `pnpm rebuild`) when the selected projects depend on each other only transitively through projects that were not selected. Previously such selected projects could run concurrently or in the wrong order; now a project always runs after the selected projects it transitively depends on, while projects without a real dependency relationship still run concurrently. This now also holds for prod-only filters (`--filter-prod`), which resolve order through the production dependency graph so transitive production dependencies are respected without pulling back the dev dependencies the filter drops, and for selections that mix `--filter` with `--filter-prod` [#​8335](https://github.com/pnpm/pnpm/issues/8335).
- [`d539172`](https://github.com/pnpm/pnpm/commit/d539172): `pnpm pack` and `pnpm publish` no longer follow a symlinked workspace `LICENSE` file when injecting it into a package that has no license of its own. Following the symlink could pack bytes from outside the workspace into the published tarball.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): Fixed `pnpm up <pkg>` producing a different result than a fresh install of the same manifests would. The resolver now distinguishes `updateRequested` (true only for packages that match the user's update target) from the broader `update` flag, and for the targeted package ignores only its own lockfile-derived preferred-version pins — so the target re-resolves exactly as if its lockfile entries were deleted and `pnpm install` ran. Preferred versions a fresh install applies (manifest pins, versions propagated down the dependency chain, and the vulnerability-avoidance penalties of `pnpm audit --fix`) stay in effect, so an update never installs duplicate versions that a reinstall from scratch would not reproduce. When a preferred version holds the update target below the newest version its range admits, pnpm now prints a warning explaining that reaching the newer version everywhere requires an override.
- [`dcabb78`](https://github.com/pnpm/pnpm/commit/dcabb78): `pnpm update <dep>@​<version>` now prints a warning when `<dep>` is only present as a transitive dependency: the requested version cannot be applied there (updates resolve the target the way a fresh install would), and the warning recommends adding the version to `pnpm.overrides` instead, which is the mechanism that does pin transitive dependencies. Closes [#​12744](https://github.com/pnpm/pnpm/issues/12744).
- [`a6c4d5f`](https://github.com/pnpm/pnpm/commit/a6c4d5f): When a dependency cannot be found in the registry (404) or the registry has no matching version, and a workspace project with the same name exists only at non-matching versions, the error now reports the available workspace versions (`ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE`) instead of the raw registry failure [pnpm/pnpm#1379](https://github.com/pnpm/pnpm/issues/1379). Other registry failures (authorization, network, server errors) still propagate unchanged. The pacquet (Rust) resolver applies the same behavior.
### [`v11.9.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1190)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.8.0...v11.9.0)
##### Minor Changes
- [`bae694f`](https://github.com/pnpm/pnpm/commit/bae694f): Some registries generate tarballs on-demand and cannot provide an integrity checksum in their package metadata. In that case pnpm now computes the integrity from the downloaded tarball and stores it in the lockfile, so the entry is verifiable on subsequent installs instead of being written without an integrity (which would fail the next install). This also applies to `--lockfile-only`: the tarball is downloaded so its integrity can be computed. A lockfile entry that is still missing its integrity is rejected as a `ERR_PNPM_MISSING_TARBALL_INTEGRITY` lockfile verification violation (the install fails closed) rather than being silently re-fetched.
- [`6c35a43`](https://github.com/pnpm/pnpm/commit/6c35a43): Added `--exclude-peers` to `pnpm sbom`. With `auto-install-peers` (the default), peer dependencies resolve into the lockfile and are otherwise indistinguishable from the package's own dependencies. The flag drops peer dependencies (and any transitive subtree reachable only through them) from the SBOM. CycloneDX 1.7 has no scope or relationship that expresses "consumer-provided peer", so omission is the only spec-clean handling. The flag name matches `pnpm list --exclude-peers`; note the SBOM flag prunes a peer's exclusive subtree, which is stricter than `pnpm list` (which only hides leaf peers).
##### Patch Changes
- [`25a829e`](https://github.com/pnpm/pnpm/commit/25a829e): `pnpm audit --fix` now writes a single combined `minimumReleaseAgeExclude` entry per package (e.g. `axios@0.18.1 || 0.21.1`) instead of one entry per version, matching the format documented for the setting. Existing per-version entries in `pnpm-workspace.yaml` are merged into the combined form rather than left as duplicates. Installs that auto-collect immature versions into `minimumReleaseAgeExclude` now report the same combined entries, so the "Added N entries" message matches what is written to the manifest [#​12534](https://github.com/pnpm/pnpm/issues/12534).
- [`1cbb5f2`](https://github.com/pnpm/pnpm/commit/1cbb5f2): Fixed non-deterministic peer resolution that could add or remove an optional transitive peer — for example `@babel/core`, reached through `styled-jsx` — from a package's peer-dependency suffix across otherwise identical installs, churning the lockfile and causing intermittent `pnpm dedupe --check` failures in CI. When a package's children are resolved by one occurrence (the "owner") and reused by a deeper consumer, whether that consumer inherited the owner's missing peers depended on whether the owner's resolution had finished yet — a race under concurrent resolution. The decision is now a function of the dependency graph's structure rather than resolution-completion order.
- [`d577eea`](https://github.com/pnpm/pnpm/commit/d577eea): Fixed a Windows flakiness in `pnpm dlx` where a failed install could surface a spurious `EBUSY: resource busy or locked` error. The cleanup of a partially-populated dlx cache is now best-effort with retries and no longer masks the original error.
- [`ec7cf70`](https://github.com/pnpm/pnpm/commit/ec7cf70): Shortened the `pnpm dlx` cache path so deep dependency trees no longer overflow Windows' `MAX_PATH`, which could make a dependency's lifecycle script fail with `spawn cmd.exe ENOENT`.
- [`05b95ab`](https://github.com/pnpm/pnpm/commit/05b95ab): Fixed `pnpm` hanging (and crashing with an unhandled promise rejection) when a non-retryable network error such as `SELF_SIGNED_CERT_IN_CHAIN` occurs while fetching from a registry. The error is now rejected through the returned promise instead of being thrown inside the detached retry callback.
- [`d3f68e2`](https://github.com/pnpm/pnpm/commit/d3f68e2): Fix a `pnpm audit` performance regression on lockfiles that contain dependency cycles. The reachable-vulnerability pruning added in pnpm 11.5.1 only memoized acyclic subtrees, so any node whose subtree touched a cycle — together with all of its ancestors — was recomputed on every query, making the path walk quadratic. Reachability is now computed once per node using Tarjan's strongly-connected-components algorithm, so cyclic graphs are handled in linear time [#​12212](https://github.com/pnpm/pnpm/issues/12212).
The audit path walk also no longer recurses, so a deeply nested dependency graph can no longer overflow the call stack, and the install path to each finding is tracked without per-node copying, keeping memory linear in the graph depth.
- [`322f88f`](https://github.com/pnpm/pnpm/commit/322f88f): Fix failed optional dependency updates so they don't rewrite unrelated dependency specs [#​11267](https://github.com/pnpm/pnpm/issues/11267).
- [`1488db1`](https://github.com/pnpm/pnpm/commit/1488db1): When `enableGlobalVirtualStore` is toggled on for a project that was previously installed without it, stale hoisted symlinks under `node_modules/.pnpm/node_modules` are now replaced instead of being left pointing at the old per-project virtual store location [#​9739](https://github.com/pnpm/pnpm/issues/9739).
- [`6545793`](https://github.com/pnpm/pnpm/commit/6545793): Fixed `pnpm install --ignore-workspace` overwriting the `allowBuilds` map in `pnpm-workspace.yaml`. The ignored builds of a package with a build script were auto-populated into `allowBuilds` even though `--ignore-workspace` was passed, clobbering committed `true`/`false` values with the `set this to true or false` placeholder [#​12469](https://github.com/pnpm/pnpm/issues/12469).
- [`fbdc0eb`](https://github.com/pnpm/pnpm/commit/fbdc0eb): Fixed `minimumReleaseAgeExclude` and `trustPolicyExclude` so multiple exact-version entries for the same package behave the same as a single `||` disjunction entry. Previously only the first matching rule's versions were honored, so a config like `[form-data@4.0.6, form-data@2.5.6]` could still flag `form-data@2.5.6` as violating `minimumReleaseAge`, while `[form-data@4.0.6 || 2.5.6]` worked as expected [#​12463](https://github.com/pnpm/pnpm/issues/12463).
- [`fa7004b`](https://github.com/pnpm/pnpm/commit/fa7004b): The in-memory package metadata cache is now populated on the exact-version disk fast path, so repeated resolutions of the same package within one install no longer re-read and re-parse the on-disk metadata. In large monorepos this brings the time for adding a new package down from minutes to seconds. The in-memory cache key now also includes the registry, so a package of the same name served by two different registries in a single install can no longer share a cache slot and resolve the wrong tarball.
- [`0a154b1`](https://github.com/pnpm/pnpm/commit/0a154b1): Fixed `pnpm patch` dropping the package name (and leaking internal option fields) when the patched dependency resolves to a single git-hosted version.
- [`4d3fe4b`](https://github.com/pnpm/pnpm/commit/4d3fe4b): The pnpr resolver endpoints moved under the reserved `/-/pnpr` namespace: `POST /v1/resolve` is now `POST /-/pnpr/v0/resolve` and `POST /v1/verify-lockfile` is now `POST /-/pnpr/v0/verify-lockfile`. The capability handshake at `GET /-/pnpr` advertises protocol version `0` to match. This keeps every pnpr-proprietary route in npm's reserved namespace, so it can never collide with a package path.
- [`0ec878d`](https://github.com/pnpm/pnpm/commit/0ec878d): Removing a runtime dependency now removes the matching `devEngines.runtime` or `engines.runtime` entry that was materialized from it. Blank runtime selectors are normalized to `latest`.
- [`17e7f2c`](https://github.com/pnpm/pnpm/commit/17e7f2c): `pnpm sbom` now emits a CycloneDX `issue-tracker` external reference for components (and the root) whose `package.json` declares a `bugs` URL. Email-only `bugs` entries are skipped, since the reference requires a URL.
- [`a84d2a1`](https://github.com/pnpm/pnpm/commit/a84d2a1): Add `@pnpm/resolving.tarball-url`, which builds and recognizes the canonical npm tarball URL of a package. It vendors `getNpmTarballUrl` (previously the external `get-npm-tarball-url` package) and adds `isCanonicalRegistryTarballUrl`, the predicate the lockfile writer uses to decide whether a tarball URL is derivable from name+version+registry (and can therefore be omitted from `pnpm-lock.yaml`).
Exposing `isCanonicalRegistryTarballUrl` lets a custom resolver (pnpmfile `resolvers`) fronting a proxy that serves tarballs on a non-canonical path (e.g. an ephemeral `localhost:<port>`) rewrite the resolved tarball to the canonical form, so nothing host-specific is persisted to the lockfile. Previously this logic was private to `@pnpm/lockfile.utils`.
Two correctness fixes are included while consolidating the logic: the scoped-package unescape now handles uppercase `%2F` as well as `%2f` (percent-encoding is case-insensitive), and protocol-insensitive comparison strips only a leading `http(s)://` scheme instead of splitting on the first `://` (which could truncate URLs containing a later `://`).
- [`852d537`](https://github.com/pnpm/pnpm/commit/852d537): Lockfile verification no longer reports a registry metadata fetch failure (for example a `403`/`401` on a private registry, or a network error) as `ERR_PNPM_TARBALL_URL_MISMATCH`. When the registry can't be reached to verify an entry, the install now aborts with the registry's own fetch error (such as `ERR_PNPM_FETCH_403`, which already explains the authentication situation) instead of mislabeling a transport failure as lockfile tampering. Registry fetch errors no longer leak basic-auth credentials embedded in the registry URL (`https://user:pass@host/`) into their message.
### [`v11.8.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1180)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.7.0...v11.8.0)
##### Minor Changes
- [`c112b61`](https://github.com/pnpm/pnpm/commit/c112b61): Added a `--dry-run` option to `pnpm install`. It runs a full dependency resolution and reports what an install would change, but writes nothing to disk (no lockfile, no `node_modules`) and always exits with code 0. This mirrors the preview semantics of `npm install --dry-run` [#​7340](https://github.com/pnpm/pnpm/issues/7340).
- [`179ebc4`](https://github.com/pnpm/pnpm/commit/179ebc4): `pnpm run --no-bail` now exits with a non-zero exit code when any of the executed scripts fail, while still running every matched script to completion. This makes the exit-code behavior of `--no-bail` consistent between recursive and non-recursive runs (recursive runs already failed at the end). Previously, a non-recursive `pnpm run --no-bail` always exited with code 0, even when a script failed [#​8013](https://github.com/pnpm/pnpm/issues/8013).
- [`0474a9c`](https://github.com/pnpm/pnpm/commit/0474a9c): Added support for generating Node.js package maps at `node_modules/.package-map.json` during isolated and hoisted installs. Added the `node-experimental-package-map` setting to inject the generated map into pnpm-managed Node.js script environments, and the `node-package-map-type` setting to choose between `standard` and `loose` package maps.
- [`dcededc`](https://github.com/pnpm/pnpm/commit/dcededc): `pnpm sbom` now marks components reachable only through `devDependencies` with CycloneDX `scope: "excluded"` and the `cdx:npm:package:development` property. The `excluded` scope documents "component usage for test and other non-runtime purposes", which matches the semantics of a devDependency; the property is the CycloneDX npm-taxonomy marker emitted by `@cyclonedx/cyclonedx-npm`, so both modern (scope) and existing (property) consumers are covered. Components reachable at runtime (including installed `optionalDependencies`) omit `scope` and default to `required`.
- [`1495cb0`](https://github.com/pnpm/pnpm/commit/1495cb0): Added per-package SBOM generation with `--out` and `--split` flags. Use `--out out/%s.cdx.json` to write one SBOM per workspace package to individual files, or `--split` for NDJSON output to stdout. When `--filter` selects a single package, the SBOM root component now uses that package's metadata. Workspace inter-dependencies (`workspace:` protocol) and their transitive dependencies are included. Author, repository, and license fall back to the root manifest when the package doesn't define them.
- [`293921a`](https://github.com/pnpm/pnpm/commit/293921a): feat(view): support searching project manifest upward when package name is omitted
When running `pnpm view` without a package name, the command now searches
upward for the nearest project manifest (`package.json`, `package.yaml`, or `package.json5`) and uses its `name` field.
If the manifest exists but lacks a `name` field, an error is thrown.
This change also replaces the `find-up` dependency with `empathic` for
improved performance and consistency across workspace tools.
##### Patch Changes
- [`29ab905`](https://github.com/pnpm/pnpm/commit/29ab905): Fixed `pnpm update` overriding the version range policy of a named catalog whose name parses as a version (e.g. `catalog:express4-21`). The `catalog:` reference carries no pinning of its own, so the prefix from the catalog entry (such as `~`) is now preserved instead of being widened to `^` [#​10321](https://github.com/pnpm/pnpm/issues/10321).
- [`bee4bf4`](https://github.com/pnpm/pnpm/commit/bee4bf4): Security: validate config dependency names and versions from the env lockfile (`pnpm-lock.yaml`) before using them to build filesystem paths. A committed lockfile with a traversal-shaped `configDependencies` name (such as `../../PWNED`) or version (such as `../../../PWNED`) could previously cause `pnpm install` to create symlinks or write package files outside `node_modules/.pnpm-config` and the store. Names must now be valid npm package names and versions must be exact semver versions; the same validation is applied to optional subdependencies of config dependencies, and to the legacy workspace-manifest format before any lockfile is written. See [GHSA-qrv3-253h-g69c](https://github.com/pnpm/pnpm/security/advisories/GHSA-qrv3-253h-g69c).
- [`96bdd57`](https://github.com/pnpm/pnpm/commit/96bdd57): Fix `link:` workspace protocol switching to `file:` after `pnpm rm` is run from inside a workspace package whose target workspace dependency has its own dependencies, when `injectWorkspacePackages: true` is set. Follow-up to [#​10575](https://github.com/pnpm/pnpm/pull/10575), which fixed the same symptom for workspace packages without dependencies.
- [`302a2f7`](https://github.com/pnpm/pnpm/commit/302a2f7): No longer warn about using both `packageManager` and `devEngines.packageManager` when the two fields pin the same package manager at the same version with the same integrity hash (e.g. both `pnpm@11.5.1+sha512.…`). Previously the hash was stripped from the legacy `packageManager` field but not from `devEngines.packageManager`, so even identical specifications looked like a mismatch [#​12028](https://github.com/pnpm/pnpm/issues/12028).
The warning still fires on any genuine divergence, and several cases now state the specific reason instead of a single generic message: a different package manager, a different version, or contradictory integrity hashes for the same version.
- [`3f0fb21`](https://github.com/pnpm/pnpm/commit/3f0fb21): Fixed the progress line showing leftover characters from external processes that write to the terminal between progress updates (e.g. an SSH passphrase prompt would leave a fragment like `added 0sa':`). The interactive reporter now redraws each frame in place, erasing to the end of the display before reprinting, so any such remnants are cleared [#​12350](https://github.com/pnpm/pnpm/issues/12350).
- [`564619f`](https://github.com/pnpm/pnpm/commit/564619f): Fixed `pnpm approve-builds` reporting "no packages awaiting approval" when a build-script dependency whose approval was revoked (e.g. after `git stash` drops the `allowBuilds` from `pnpm-workspace.yaml`) is re-added. The revoked packages are now correctly recorded in `.modules.yaml` so `approve-builds` can find them. [#​12221](https://github.com/pnpm/pnpm/issues/12221)
- [`3d1fd20`](https://github.com/pnpm/pnpm/commit/3d1fd20): Skip the redundant "target bin directory already contains an exe called node" warning on Windows when the existing `node.exe` already matches the target (same hard link or identical content) [pnpm/pnpm#12203](https://github.com/pnpm/pnpm/issues/12203).
- [`1b02b47`](https://github.com/pnpm/pnpm/commit/1b02b47): Fix macOS Gatekeeper blocking native binaries (`.node`, `.dylib`, `.so`) by removing the `com.apple.quarantine` extended attribute after importing them from the store.
When pnpm imports files from its content-addressable store into `node_modules`, macOS preserves extended attributes, including `com.apple.quarantine`. If this xattr is present on a store blob (e.g. it was first written under a Gatekeeper-enabled app such as a Git client), it propagates to `node_modules`, and Gatekeeper blocks the native binary from loading even though pnpm already verified the file's integrity against the lockfile.
After importing a package, pnpm now strips `com.apple.quarantine` from its native binaries, matching Homebrew's behaviour of dropping quarantine from verified downloads. The cleanup is macOS-only, runs in a single batched `xattr` call per package, is restricted to native binaries (other files are untouched), and is non-fatal (it logs a warning on unexpected errors).
Fixes [#​11056](https://github.com/pnpm/pnpm/issues/11056)
- [`61969fb`](https://github.com/pnpm/pnpm/commit/61969fb): Fix `pnpm install` with `optimisticRepeatInstall` incorrectly reporting `Already up to date` when `pnpm-lock.yaml` changed but project manifests did not. This affected workflows such as checking out or restoring only the lockfile [#​12100](https://github.com/pnpm/pnpm/issues/12100).
Also fixes `checkDepsStatus` to use the correct lockfile path when `useGitBranchLockfile` is enabled, so the optimistic fast-path and lockfile modification detection work with `pnpm-lock.<branch>.yaml` files instead of always stat'ing `pnpm-lock.yaml`. Merge-conflict detection now reads the resolved lockfile name as well, and with `mergeGitBranchLockfiles` enabled every `pnpm-lock.*.yaml` is scanned for modifications and conflicts. The git branch is now resolved by reading `.git/HEAD` directly (no process spawn) and uses the workspace directory rather than `process.cwd()`.
- [`5c12968`](https://github.com/pnpm/pnpm/commit/5c12968): Fix recursive updates of transitive dependencies when the update command mixes transitive dependency patterns with direct dependency selectors. For example, `pnpm up -r "@​babel/core" uuid` now updates matching transitive `@babel/core` dependencies even when `uuid` is a direct dependency selector [#​12103](https://github.com/pnpm/pnpm/issues/12103).
- [`9d79ba1`](https://github.com/pnpm/pnpm/commit/9d79ba1): Register the `pnpm update --no-save` flag in the CLI help and option parser.
- [`0474a9c`](https://github.com/pnpm/pnpm/commit/0474a9c): Fixed `pnpm import` for Yarn v2 lockfiles when `js-yaml` v4 is installed.
- [`9e0c375`](https://github.com/pnpm/pnpm/commit/9e0c375): Fixed `pnpm install` repeatedly prompting to remove and reinstall `node_modules` in a workspace package when `enableGlobalVirtualStore` is enabled. The post-install build step recorded a per-project `node_modules/.pnpm` virtual store directory in `node_modules/.modules.yaml`, overwriting the global `<storeDir>/links` value the install step had written. The next install then detected a virtual-store mismatch (`ERR_PNPM_UNEXPECTED_VIRTUAL_STORE`). The build step now derives the same global virtual store directory as the install step [#​12307](https://github.com/pnpm/pnpm/issues/12307).
- [`223d060`](https://github.com/pnpm/pnpm/commit/223d060): Document the `--cpu`, `--os` and `--libc` flags in the output of `pnpm install --help`. These flags were already supported but were only documented on the website [#​12359](https://github.com/pnpm/pnpm/issues/12359).
- [`e85aea2`](https://github.com/pnpm/pnpm/commit/e85aea2): Avoid reading `README.md` from disk when publishing if the publish manifest already provides a `readme` field. The README is now only read lazily, inside `createExportableManifest`, when it is actually needed.
- [`3188ae7`](https://github.com/pnpm/pnpm/commit/3188ae7): Fixed `pnpm peers check` to accept loose peer dependency ranges such as `>=3.16.0 || >=4.0.0-` when the installed peer version satisfies the range [#​12149](https://github.com/pnpm/pnpm/issues/12149).
- [`531f2a3`](https://github.com/pnpm/pnpm/commit/531f2a3): Fixed `pnpm update` rewriting a `workspace:` dependency that points at a local path (e.g. `workspace:../packages/foo/dist`) into a normalized `link:` or version-range specifier. Such specifiers are now preserved verbatim when the workspace protocol is preserved [#​3902](https://github.com/pnpm/pnpm/issues/3902).
- [`fe66535`](https://github.com/pnpm/pnpm/commit/fe66535): Fixed a lockfile non-convergence bug where an incremental install kept a duplicate transitive dependency that a fresh install would not produce. When a package is reused from the lockfile, its child edges are taken verbatim and bypass the preferred-versions walk, so a transitive dependency could stay pinned to an older version even after a direct dependency resolved to a higher version that satisfies the same range. The resolver now refreshes such a stale pin to the higher direct-dependency version during resolution — so the older version is never resolved or fetched, and the incremental result converges to the fresh one.
- [`6d35338`](https://github.com/pnpm/pnpm/commit/6d35338): `pnpm install` detects changes inside local file dependencies again. The optimistic repeat-install fast path only tracks manifest and lockfile modification times, so edits inside a local dependency's directory (or a repacked local tarball) were reported as "Already up to date". Projects with local file dependencies (`file:` and bare local path or tarball specifiers, declared directly or through `pnpm.overrides`) now always run a full install, which refetches those dependencies, matching pnpm v10 behavior [#​11795](https://github.com/pnpm/pnpm/issues/11795).
- [`4ca9247`](https://github.com/pnpm/pnpm/commit/4ca9247): Preserve the existing Node.js runtime version prefix when resolving `node@runtime:<range>` to a concrete version.
- [`30c7590`](https://github.com/pnpm/pnpm/commit/30c7590): Create shorter CAFS temporary package directories to leave room for lifecycle scripts that create IPC socket paths under TMPDIR.
- [`13815ad`](https://github.com/pnpm/pnpm/commit/13815ad): Reporter output (warnings, progress) for `pnpm store` and `pnpm config` subcommands now goes to stderr instead of stdout. This fixes scripts that capture their stdout (e.g. `PNPM_STORE=$(pnpm store path)`, `pnpm config list --json | jq`) from getting warnings mixed into the result.
- [`1c05876`](https://github.com/pnpm/pnpm/commit/1c05876): Avoid relinking unchanged child dependencies and remove stale child links during warm installs.
- [`817f99d`](https://github.com/pnpm/pnpm/commit/817f99d): Fixed lockfile churn where a package's `transitivePeerDependencies` could be dropped (and shift between packages) when the package participates in a dependency cycle. A cycle re-entry resolves against truncated children, so it must not be cached as "pure"; otherwise sibling occurrences of the same package short-circuit and lose transitive peers depending on traversal order [#​5108](https://github.com/pnpm/pnpm/issues/5108).
- [`eba03e0`](https://github.com/pnpm/pnpm/commit/eba03e0): Fix `pnpm install` reporting "Already up to date" after a catalog entry in `pnpm-workspace.yaml` was reverted to a previous version. After an update modified a catalog, the workspace state cache stored the pre-update catalog versions, so reverting the entry back to its original version was not detected as an outdated state [#​12418](https://github.com/pnpm/pnpm/issues/12418).
- [`3b54d79`](https://github.com/pnpm/pnpm/commit/3b54d79): `pnpm update` now keeps lockfile `overrides` that resolve through a catalog in sync with the catalog. Previously, when an override referenced a catalog (e.g. `overrides: { foo: 'catalog:' }`) and `pnpm update` bumped that catalog entry, the lockfile's `catalogs` advanced while the resolved `overrides` kept the old version. The resulting lockfile was internally inconsistent, so a later `pnpm install --frozen-lockfile` failed with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`.
- [`9d0a300`](https://github.com/pnpm/pnpm/commit/9d0a300): Fixed `pnpm version --recursive` so it honors the workspace selection. In recursive mode the version bump now applies to the packages resolved from the workspace filter (`selectedProjectsGraph`), matching the behavior of `pnpm publish --recursive`, instead of always bumping every workspace package [#​11348](https://github.com/pnpm/pnpm/issues/11348).
### [`v11.7.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1170)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.6.0...v11.7.0)
##### Minor Changes
- Added a new setting `frozenStore` (`--frozen-store`) that lets `pnpm install` run against a package store on a read-only filesystem (e.g. a Nix store, a read-only bind mount, an OCI layer). When enabled, pnpm opens the store's SQLite `index.db` through the `immutable=1` URI — bypassing the WAL/`-shm` sidecar creation that otherwise fails on a read-only directory — and suppresses every store-write path (the `index.db` writer and the project-registry write). Pair it with `--offline --frozen-lockfile` against a fully-populated store. Under the global virtual store, package directories live inside the store, so if the store is missing the build output of a package whose lifecycle scripts are approved (or that has a patch), pnpm fails up front with `ERR_PNPM_FROZEN_STORE_NEEDS_BUILD` rather than crashing mid-build on a read-only write — seed the store with those builds first. Incompatible with `--force` and with a configured pnpr server, since both write into the store; the side-effects cache is likewise not written under `frozenStore`. If the store is missing its content directory, the install fails fast with `ERR_PNPM_FROZEN_STORE_INCOMPLETE` rather than attempting to initialize it. The read-only `immutable=1` open requires Node.js >=22.15.0, >=23.11.0, or >=24.0.0; on older runtimes `--frozen-store` fails with a clear `ERR_PNPM_FROZEN_STORE_UNSUPPORTED_NODE` error. Bin-linking also tolerates a read-only store: under the global virtual store a package's bin source lives inside the store, so the `chmod` that makes it executable would be refused — with `EPERM`/`EACCES`, or with `EROFS` on a genuinely read-only filesystem. That `chmod` is redundant when the seed already ships its bins executable with a normalized shebang, so it is now skipped in that case, while a non-executable bin (or one still carrying a Windows CRLF shebang) on a read-only store still errors.
- When [`pacquet`](https://github.com/pnpm/pnpm/tree/main/pacquet) (the Rust port of pnpm) is declared in `configDependencies`, pnpm now delegates dependency **resolution** to it too — not just materialization — provided the installed pacquet is new enough to support full resolving installs (>= 0.11.7).
Previously pacquet only ran in frozen-install mode: pnpm always resolved the dependency graph itself (writing `pnpm-lock.yaml`) and handed pacquet a finished lockfile to fetch / import / link. With pacquet >= 0.11.7, a non-frozen `pnpm install` (default isolated `nodeLinker`, plain install) is delegated to pacquet end-to-end in a single pass — pacquet resolves the manifests, writes the lockfile, and materializes `node_modules`. pnpm detects the capability from the installed pacquet's version; older pacquet releases keep the resolve-then-materialize split, and `add` / `update` / `remove` still resolve in pnpm (it has to mutate the manifests first). This remains an opt-in preview of the Rust install engine [#​11723](https://github.com/pnpm/pnpm/issues/11723).
- Added a new opt-in `--batch` flag to `pnpm publish --recursive` that sends all selected packages to the registry in a single `PUT /-/pnpm/v1/publish` request instead of one request per package. The target registry has to implement the batch publish endpoint (pnpr does); registries that don't are reported with a clear `ERR_PNPM_BATCH_PUBLISH_UNSUPPORTED` error. The batch is processed all-or-nothing by pnpr: if any package in the batch fails validation, none of the packages are published.
##### Patch Changes
- Reject path-traversal and reserved dependency aliases (such as `../../../escape`, `.bin`, `.pnpm`, or `node_modules`) that come from a lockfile rather than a freshly resolved manifest. A crafted lockfile alias could otherwise be joined directly under a hoisted `node_modules` directory, letting package files be written outside the intended install root or overwrite pnpm-owned layout.
The fix adds two layers:
- The `nodeLinker: hoisted` graph builder now validates each alias at the directory sink (`safeJoinModulesDir`), matching the validation pnpm already performs when resolving aliases from manifests.
- The lockfile verification gate (`verifyLockfileResolutions`) now runs an always-on, policy-independent check that rejects any importer or snapshot dependency alias that is not a valid package name, failing the install early — before any fetch or filesystem work — for every node linker at once.
- Made shared package child resolution deterministic when the same package is reached through multiple contexts. pnpm now chooses the shallowest occurrence, then importer order, then parent path, instead of letting request timing decide the child context and missing-peer report [pnpm/pnpm#12358](https://github.com/pnpm/pnpm/issues/12358).
- Fix garbled summary line after submitting `pnpm update -i` and `pnpm audit --fix -i`. The interactive checkbox prompt previously printed every selected choice's full table row (label, current/target versions, workspace, URL) joined by commas, producing a wall of text after pressing Enter. The summary now lists only the selected package names (or vulnerability keys) by setting an explicit `short` per choice; the in-progress selection UI is unchanged.
- Prevent `pnpm patch-remove` from removing files outside the configured patches directory.
- Fixed `pnpm publish` ignoring `strictSsl: false` when publishing to registries with self-signed certificates. The `strictSSL` option is now forwarded to `libnpmpublish` / `npm-registry-fetch` so that `strict-ssl=false` in `.npmrc` or `strictSsl: false` in `pnpm-workspace.yaml` is respected during publish, the same way it is for `pnpm install` [pnpm/pnpm#12012](https://github.com/pnpm/pnpm/issues/12012).
- Fixed `Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined` regression introduced in 11.6.0 when running `pnpm add <pkg>` outside a workspace on Windows. `selectProjectByDir` was keying the resulting `ProjectsGraph` by `opts.dir` instead of `project.rootDir`, so downstream `manifestsByPath` lookups missed when the two paths normalized differently (typically drive-letter casing). [pnpm/pnpm#12379](https://github.com/pnpm/pnpm/issues/12379)
- Git dependencies that point to a subdirectory of a repository (`repo#commit&path:/sub/dir`) keep their `path` in the lockfile again. Since the integrity of git-hosted tarballs started being pinned in the lockfile, any install that actually downloaded the tarball rebuilt the lockfile resolution as `{ integrity, tarball, gitHosted }` and dropped the `path` field, while installs served from the store kept it — so the field disappeared seemingly at random. Without `path`, later installs from that lockfile silently unpacked the repository root instead of the subdirectory [#​12304](https://github.com/pnpm/pnpm/issues/12304).
- Fixed nondeterministic lockfile output that made `pnpm dedupe --check` fail intermittently in CI. When a locked peer provider was pinned for a dependency that has no child dependencies of its own, the pinned provider leaked into the shared parent scope, so siblings resolved after it could pick up an optional peer they should not see. Which siblings were affected depended on resolution order, which varies with network timing.
- Sped up `pnpm install` with a frozen lockfile by running lockfile verification (the policy revalidation gate added for `minimumReleaseAge`/`trustPolicy` and the tarball-URL anti-tamper check) concurrently with fetching and linking instead of blocking the whole install on it. Dependency lifecycle scripts are still held back until verification succeeds, so no script runs on an unverified lockfile: if verification fails the install aborts before any dependency build, and if linking finishes first the install waits for the verification verdict before completing.
- User-defined `npm_config_*` environment variables are now preserved during lifecycle script execution. Previously, all `npm_`-prefixed env vars were stripped, which caused user-set variables like `npm_config_platform_arch` to be lost [pnpm/pnpm#12399](https://github.com/pnpm/pnpm/issues/12399).
- pnpm can now use different auth tokens for different package scopes, even when those scopes use the same registry URL.
Previously, auth was selected only by registry URL. If `@org-a` and `@org-b` both used `https://npm.pkg.github.com/`, they had to share the same token. This caused problems for registries that issue tokens per organization or per scope.
Configure a scope-specific token by adding the package scope after the registry URL in the auth key:
```ini
@​org-a:registry=https://npm.pkg.github.com/
@​org-b:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:@​org-a:_authToken=${ORG_A_TOKEN}
//npm.pkg.github.com/:@​org-b:_authToken=${ORG_B_TOKEN}
//npm.pkg.github.com/:_authToken=${FALLBACK_TOKEN}
```
`pnpm login --registry=https://npm.pkg.github.com --scope=@​org-a` writes the token to the same scope-specific auth key.
When installing or publishing `@org-a/*`, pnpm uses `ORG_A_TOKEN`. For `@org-b/*`, pnpm uses `ORG_B_TOKEN`. Packages without a matching scope continue to use the registry-wide fallback token.
- `pnpm setup` no longer prompts to approve build scripts for `@pnpm/exe` when installing the standalone executable. pnpm links the platform-specific binary itself, so the package's install scripts are skipped during the global self-install [#​12377](https://github.com/pnpm/pnpm/issues/12377).
- Close lockfile reads deterministically before rewriting lockfiles and keep pacquet's virtual store directory length aligned with pnpm on Windows.
- A `304 Not Modified` answer from the registry now renews the cached metadata file's mtime, so the `minimumReleaseAge` freshness shortcut keeps serving resolutions from the cache. Previously, once a cached packument grew older than `minimumReleaseAge`, every subsequent install re-validated it against the registry forever, because a 304 never rewrites the file.
- Updated dependency ranges. Notably:
- `@pnpm/logger` peer dependency range moved to `^1100.0.0`.
- `msgpackr` 1.11.8 → 2.0.4 (store index files remain byte-compatible in both directions).
- `open` ^7.4.2 → ^11.0.0, `memoize` ^10 → ^11, `cli-truncate` ^5 → ^6, `pidtree` ^0.6 → ^1.
- `@yarnpkg/core` 4.5.0 → 4.8.0, `@rushstack/worker-pool` 0.7.7 → 0.7.18, `@cyclonedx/cyclonedx-library` 10.0.0 → 10.1.0, `@pnpm/config.nerf-dart` ^1 → ^2, `@pnpm/log.group` 3.0.2 → 4.0.1, `@pnpm/util.lex-comparator` ^3 → ^4.
- Updated `@zkochan/cmd-shim` to v9.0.6.
- Fixed a Windows-only hang where a failed command could take 20–46 seconds to exit. On error, pnpm enumerates descendant processes (via `pidtree`) to terminate them, which on Windows shells out to `wmic`/PowerShell `Get-CimInstance Win32_Process` — a lookup that is extremely slow on some machines. The lookup is now bounded by a short timeout so it can no longer stall the process exit.
### [`v11.6.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1160)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.5.3...v11.6.0)
##### Minor Changes
- `pnpm install` completes without re-resolving when `pnpm-lock.yaml` was deleted but `node_modules` is intact: the up-to-date check now treats the current lockfile (`node_modules/.pnpm/lock.yaml`) — the record of what the previous install materialized — as the wanted lockfile, verifies the manifests still match it, restores `pnpm-lock.yaml` from it, and reports "Already up to date". Previously this scenario triggered a full resolution and a re-verification of every locked package against the registry.
- [`615c669`](https://github.com/pnpm/pnpm/commit/615c669): Added support for configuring URL-scoped registry settings through `npm_config_//…` and `pnpm_config_//…` environment variables, for example:
```text
npm_config_//registry.npmjs.org/:_authToken=<token>
pnpm_config_//registry.npmjs.org/:_authToken=<token>
```
This provides a file-free way to supply registry authentication. Because the registry a value applies to is encoded in the (trusted) environment variable name, it is host-scoped by construction and cannot be redirected to another registry by repository-controlled config. The environment value is treated as trusted config: it takes precedence over a project/workspace `.npmrc` but is still overridden by command-line options. When the same key is provided through both prefixes, `pnpm_config_` wins.
- Raised the default network concurrency from `min(64, max(cpuCores * 3, 16))` to `min(96, max(cpuCores * 3, 64))`. Package downloads are I/O-bound, not CPU-bound, so deriving the floor from the core count left machines with few cores (for example 4-vCPU CI runners) downloading only 16 tarballs at a time and unable to saturate a low-latency registry. The `networkConcurrency` setting still overrides the default.
##### Patch Changes
- Improved the warning printed when a project `.npmrc` uses an environment variable in a registry/proxy URL or in registry credentials. The message now explains why the setting was ignored and how to migrate it to a trusted source — for example by moving the line to the user-level `~/.npmrc` or running `pnpm config set "<key>" <value>` — with a link to <https://pnpm.io/npmrc>. The `pnpm config set` example is only suggested when the key has no `${...}` placeholder, so the snippet is always safe to copy-paste.
- Print a "Lockfile passes supply-chain policies (verified 2h ago)" message when lockfile verification is skipped because a cached verdict for the same lockfile content and policy is reused. Previously the cached short-circuit was completely silent, which made it look like the policy gate never ran [#​12324](https://github.com/pnpm/pnpm/issues/12324).
- Platform-specific optional dependencies are now skipped even when their `os`/`cpu`/`libc` fields are missing from the registry metadata or the lockfile. Some registries strip these fields from the package metadata, which made pnpm download and install the binaries of every platform regardless of `supportedArchitectures`. The missing platform fields of an optional dependency are now inferred from its name (e.g. `@nx/nx-win32-arm64-msvc` → `os: win32`, `cpu: arm64`), so foreign-platform binaries are skipped without even downloading them [#​11702](https://github.com/pnpm/pnpm/issues/11702).
### [`v11.5.3`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1153)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.5.2...v11.5.3)
##### Patch Changes
- Stopped expanding environment variables in repository-controlled registry/proxy request destinations and registry credential values from `.npmrc`, and in workspace registry URLs from `pnpm-workspace.yaml`. Move dynamic registry URL and token configuration to trusted user, global, CLI, or environment config.
- Resolve package-manager bootstrap dependencies with trusted user or CLI registry and network config, and reject package-manager env-lockfile records that do not use registry package paths with integrity-only resolutions before auto-switch execution.
- Avoid writing `packageManagerDependencies` to `pnpm-lock.yaml` when package manager policy is set to `onFail: ignore` or `pmOnFail: ignore` [#​12228](https://github.com/pnpm/pnpm/issues/12228).
- Avoid running dependency-status auto-install when the dependency status is unavailable without a project manifest.
- Using the `$` version reference syntax in `overrides` (e.g. `"react": "$react"`) now prints a deprecation warning. The syntax still works, but [catalogs](https://pnpm.io/catalogs) are the recommended way to keep an overridden version in sync with the rest of the workspace. Reference a catalog entry with the `catalog:` protocol instead.
- Fixed `pnpm config get globalconfig` to return the global `config.yaml` path again [pnpm/pnpm#11962](https://github.com/pnpm/pnpm/issues/11962).
- Fixed bare `--color` so it does not consume the following CLI flag, allowing command shorthands like `--parallel` to expand correctly and forms like `pnpm --color with current <command>` to dispatch the inner command instead of failing with `MISSING_WITH_CURRENT_CMD`.
- Fix `pnpm install` ignoring `enableGlobalVirtualStore` toggle by including it in the workspace state settings check [#​12142](https://github.com/pnpm/pnpm/issues/12142).
- Security: pnpm now verifies the npm registry signature of a package-manager binary before spawning it, so a cloned repository cannot make pnpm download and execute an arbitrary native binary.
This covers two paths that select an executable from repository-controlled input:
- **pacquet install engine** — declaring `pacquet` (or `@pnpm/pacquet`) in `configDependencies` opts in to pnpm's Rust install engine. pnpm now verifies that the installed `pacquet` shim and the host's `@pacquet/<platform>-<arch>` binary carry a valid npm registry signature for their exact `name@version`, and refuses to run pacquet (failing the command) if the signature does not verify or cannot be checked. The only graceful fallback to pnpm's own engine is when pacquet has no binary for the current platform.
- **automatic version switch / `self-update`** — the `packageManager` / `devEngines.packageManager` field makes pnpm download and run a specific pnpm version. pnpm now verifies the registry signature of `pnpm`, `@pnpm/exe`, and the host platform binary before installing/spawning them, and refuses to run an engine whose signature does not match a published, signed release. The check runs only on an actual download (store cache miss), so it does not add a network round trip to every command.
In both cases the signature is verified over the *installed* integrity, against npm's public signing keys that ship embedded in the pnpm CLI (like corepack), so bytes substituted via a tampered lockfile or a repository-controlled registry fail verification — and a registry the user did not vouch for cannot supply its own signing keys. The signed packument is fetched from the configured registry, so an npm mirror works transparently. Verification fails closed: if it cannot be completed (for example, the registry is unreachable), the command fails rather than running an unverified binary. The embedded keys are kept current by a release-time check against npm's signing-keys endpoint.
- Made peer-dependent deduplication deterministic. When a peer-suffixed package variant was a subset of two or more mutually incompatible larger variants, the variant it collapsed into depended on the order importers were resolved in, which varies between machines. This could resolve the same workspace to different lockfiles on different platforms and make `pnpm dedupe --check` alternate between passing and failing.
- Reject invalid package names and versions from staged tarball manifests before deriving filenames for `pnpm stage download`.
- Clarified in CLI help that the pnpm store is trusted shared state and store integrity checks are corruption detection, not a tamper boundary for untrusted store writers.
- Reject reserved manifest `bin` names (`""`, `"."`, `".."`, and scoped forms such as `@scope/..`) when resolving a package's bins. These names previously passed the bin-name guard and, when joined to the global bin directory during global remove/update/add operations, could resolve to the global bin directory itself or its parent and have it recursively deleted.
- Require trusted package identity before package-name `allowBuilds` entries can approve lifecycle scripts for git, git-hosted tarball, direct tarball, and local directory artifacts. To approve one of those artifacts explicitly, use its peer-suffix-free lockfile depPath as the `allowBuilds` key. Lockfile verification now rejects lockfiles where a registry-style dependency path (`name@semver`) is backed by a git, directory, or git-hosted tarball resolution (`ERR_PNPM_RESOLUTION_SHAPE_MISMATCH`), so the dependency path is a reliable artifact identity by the time scripts can run.
- Security: pnpm now verifies the OpenPGP signature of a downloaded Node.js runtime's `SHASUMS256.txt` before trusting its integrity hashes.
When a repository requests a Node.js runtime (e.g. via `devEngines.runtime` / `useNodeVersion`), the download mirror is repository-configurable through `node-mirror:<channel>`. The integrity of the downloaded binary was only checked against `SHASUMS256.txt` fetched from that same mirror — a circular check that a malicious mirror could satisfy by serving a tampered binary together with a matching `SHASUMS256.txt`. pnpm then executes the binary (for example to run lifecycle scripts).
pnpm now fetches `SHASUMS256.txt.sig` and verifies the detached OpenPGP signature against the Node.js release team's public keys, which ship embedded in the pnpm CLI. A mirror that serves a tampered binary cannot also produce a valid signature, so the download fails to verify. The embedded keys are kept current by a release-time check against the canonical `nodejs/release-keys` list.
The musl variants from the hardcoded `unofficial-builds.nodejs.org` mirror are not repository-configurable and are signed by a different key, so they continue to be trusted over TLS.
### [`v11.5.2`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1152)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.5.1...v11.5.2)
##### Patch Changes
- Peer dependency resolution now reuses the peer contexts already recorded in the lockfile when those providers are still present in the dependency graph and still satisfy the peer ranges. This avoids unnecessary peer-context rewrites during lockfile regeneration. Current manifest choices remain authoritative: a newly added, explicitly updated, or aliased direct provider, a changed nested provider, or a locked version that no longer satisfies the range still takes precedence.
- The lockfile verifier now checks that a registry entry pinning an explicit `tarball` URL points at the artifact the registry's own metadata lists for that `name@version`. Previously a tampered lockfile could pair a trusted `name@version` with an attacker-chosen tarball URL (and a matching integrity for those bytes), so the install fetched the attacker's bytes. A mismatch — or any entry that can't be confirmed against the registry — is rejected with `ERR_PNPM_TARBALL_URL_MISMATCH`. Non-registry resolutions (`file:`, git-hosted, etc.) and registry entries without an explicit tarball URL (the URL is reconstructed from name+version+registry, so it is inherently bound) are unaffected; non-standard registry tarball URLs (npm Enterprise, GitHub Packages) still pass because they match the metadata.
- Fix `pnpm update --recursive --lockfile-only <pkg>@​<version>` crashing with `Invalid Version` when the catalog entry for `<pkg>` is a version range (e.g. `^21.2.10`) and `catalogMode` is `strict` or `prefer`. The catalog–version comparison now skips the equality check when either side is a range rather than passing a range to `semver.eq()`, so range specifiers fall through to the existing mismatch handling instead of throwing [#​11570](https://github.com/pnpm/pnpm/issues/11570).
- Avoided a Node.js crash when pnpm exits after network requests on Windows.
- Fixed packages being materialized into the virtual store without their root-level files (`package.json`, `LICENSE`, README, root entrypoints) when multiple `pnpm install` processes ran against the same store/workspace concurrently. The fast import path used to destructively empty the shared target directory, so a concurrent importer could wipe files another importer had already written; if the surviving files included the `package.json` completion marker, every later install treated the broken directory as complete and never repaired it. The fast path now imports directly only when it can create the target directory exclusively, and otherwise builds the package in a private temp directory and atomically renames it into place [#​12197](https://github.com/pnpm/pnpm/issues/12197).
- Fix dependency build scripts not running under the global virtual store (`enableGlobalVirtualStore`).
In a workspace install, dependency build scripts are deferred to a single `rebuild` pass (`buildProjects`). That pass resolved each package's location from the classic `node_modules/.pnpm/<depPathToFilename>` layout, which does not exist under the global virtual store — so native dependencies (e.g. packages using `node-gyp` / `prebuild-install`) were never built and failed to load at runtime (`Cannot find module .../build/Release/*.node`).
`buildProjects` now resolves the global-virtual-store projection directory (`<storeDir>/links/<hash>`, computed with the same graph hash the installer uses) when `enableGlobalVirtualStore` is set, and serializes concurrent builds of the same shared projection so parallel workspace projects don't race on the same directory.
- Don't promote a `runtime:` dependency (such as the Node.js version from `devEngines.runtime` or `pnpm runtime set`) into a catalog when `catalogMode` is `strict` or `prefer`. A `runtime:` dependency round-trips to `devEngines.runtime`, which only recognizes the `runtime:` protocol; cataloging it rewrote the manifest entry to `catalog:`, which broke that round-trip, stranded it in `devDependencies`, and left `devEngines.runtime` untouched.
- Skip lockfile `minimumReleaseAge`/`trustPolicy` verification for non-registry tarball protocols (for example `file:`), so local tarball dependencies are not incorrectly checked against npm registry metadata.
### [`v11.5.1`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1151)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.5.0...v11.5.1)
##### Patch Changes
- Improve `pnpm audit` performance by pruning non-vulnerable lockfile subtrees and stopping path enumeration once vulnerable findings reach the path cap.
- Avoid crashing when the workspace state cache is partially written or malformed.
- Set `npm_config_user_agent` for root lifecycle scripts during headless installs.
- Preserve the `integrity` field of a remote (non-registry) tarball dependency when its lockfile entry is rebuilt. Re-resolving such a dependency without re-fetching it (for example via `pnpm update`, or when another dependency changes) produced a resolution with no integrity — URL/tarball resolvers only learn the integrity after the tarball is downloaded — so the previously recorded integrity was dropped, making later installs fail with `ERR_PNPM_MISSING_TARBALL_INTEGRITY` [#​12067](https://github.com/pnpm/pnpm/issues/12067).
- Normalize a string `repository` field into the `{ type, url }` object form when creating the publish manifest, matching npm's behavior. Some registries (e.g. Gitea/Codeberg) reject a string `repository` with a 500 Internal Server Error during `pnpm publish` [#​12099](https://github.com/pnpm/pnpm/issues/12099).
- Preserve compatible optional peer versions already present in the lockfile when resolving dependencies.
- Fixed inconsistent resolution of a peer dependency that is shared through a diamond. When a package peer-depends on both another package and one of that package's own peer dependencies (for example `@typescript-eslint/eslint-plugin` peer-depends on both `@typescript-eslint/parser` and `typescript`, and `@typescript-eslint/parser` peer-depends on `typescript`), pnpm no longer reuses a hoisted instance of the shared peer that was resolved against a different version [#​12079](https://github.com/pnpm/pnpm/issues/12079).
### [`v11.5.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1150)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.4.0...v11.5.0)
##### Minor Changes
- Added a new `hoistingLimits` setting for `nodeLinker: hoisted` installs, mirroring yarn's `nmHoistingLimits`. It accepts `none` (the default — hoist as far as possible), `workspaces` (hoist only as far as each workspace package), or `dependencies` (hoist only up to each workspace package's direct dependencies). Originally proposed in [#​6468](https://github.com/pnpm/pnpm/pull/6468), closing [#​6457](https://github.com/pnpm/pnpm/issues/6457).
- Replaced `enquirer` with `@inquirer/prompts` for all interactive prompts. Fixes the `update -i` scrolling overflow bug where long choice lists were clipped in the terminal [#​6643](https://github.com/pnpm/pnpm/issues/6643).
**User-facing changes:**
- `pnpm update -i` / `pnpm update -i --latest`: Scrolling now works correctly when many packages are available; the new library uses visual-line-aware pagination via `usePagination`
- `pnpm audit --fix -i`: Same scrolling fix for vulnerability selection
- `pnpm approve-builds`: Interactive build approval prompts updated
- `pnpm patch`: Version selection and "apply to all" prompts updated
- `pnpm patch-remove`: Patch removal selection updated
- `pnpm publish`: Branch confirmation prompt updated
- `pnpm login`: Credential prompts updated
- `pnpm run` / `pnpm exec` (with `verifyDepsBeforeRun=prompt`): Confirmation prompt updated
Vim-style `j`/`k` keys still work for up/down navigation in all interactive prompts.
**Internal:** The `OtpEnquirer` and `LoginEnquirer` DI interfaces changed from `{ prompt }` to `{ input }` / `{ input, password }` respectively. Plugins or custom builds that inject their own enquirer mock will need to update.
- Staged publishes are now recognized in the trust scale. When a package version's registry metadata carries an `approver` field, it is treated as the strongest trust evidence (ranked above trusted publishers and provenance attestations), since staged publishes require 2FA publish approvals. This prevents false-positive trust downgrade errors when moving from a staged publish to a lower trust level [#​11887](https://github.com/pnpm/pnpm/issues/11887).
##### Patch Changes
- Fix pnpm hanging during peer resolution when an aliased install pulls in transitive packages with mutual peer cycles at different depths in the dependency tree (for example, `pnpm i nuxt@npm:nuxt-nightly@5x`). Cycles whose members hit the `findHit` cache instead of running their own `calculateDepPath` are now short-circuited by sibling resolutions at the level where the cycle is detected, so the cached path promises no longer deadlock. [#​11999](https://github.com/pnpm/pnpm/issues/11999).
- Fix `pnpm dist-tag add` and `pnpm dist-tag rm` against npmjs.org failing without `--otp` with `[ERR_PNPM_UNAUTHORIZED] You must be logged in to set dist-tag … "You must provide a one-time pass. Upgrade your client to npm@latest in order to use 2FA."`. pnpm now sends `npm-auth-type: web` on dist-tag writes and surfaces the resulting OTP challenge through the existing browser-based 2FA flow (the same `withOtpHandling` helper used by `pnpm publish`), so the browser opens, the user authenticates, and the dist-tag is set on retry. `--otp=<code>` continues to work via the classic flow.
- Fix `minimumReleaseAgeExclude` handling in npm resolution fast paths so excluded packages do not get pinned to stale versions. Excludes are honored consistently during `publishedBy` metadata selection and cache-mtime shortcuts.
- Fix the `integrity` field being dropped from the lockfile entry of a remote (non-registry) https-tarball dependency when an unrelated package is installed afterwards. URL/tarball resolvers do not return an integrity (it is only known after the tarball is downloaded), so when such a dependency was reused from the lockfile without being re-fetched, its integrity was lost. It is now carried over from the existing resolution. With pnpm's lockfile-integrity hardening, the missing integrity made subsequent `--frozen-lockfile` installs fail with `ERR_PNPM_MISSING_TARBALL_INTEGRITY`. [#​12001](https://github.com/pnpm/pnpm/issues/12001).
- Skip dependency re-resolution when `pnpm-lock.yaml` is missing but `node_modules/.pnpm/lock.yaml` exists and still satisfies the manifest. `pnpm install` now reuses the materialized snapshot to regenerate `pnpm-lock.yaml` instead of walking the registry to rebuild it from scratch, turning the cache+node\_modules variation into a near-no-op for users who deleted the lockfile but kept the install [#​11993](https://github.com/pnpm/pnpm/issues/11993).
`--frozen-lockfile` still refuses to proceed when `pnpm-lock.yaml` is absent — the regenerated lockfile must be committed, so failing loudly is the correct behavior for CI.
### [`v11.4.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1140)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.3.0...v11.4.0)
##### Minor Changes
- Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously, `pnpm install` (non-frozen) would log `ERR_PNPM_TARBALL_INTEGRITY`, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile.
`pnpm install` now exits with `ERR_PNPM_TARBALL_INTEGRITY` and a hint pointing at the new opt-in flag.
The only opt-in is **`pnpm install --update-checksums`** — narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable.
`--force` and `pnpm update` deliberately do **not** bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. `--frozen-lockfile` behavior is unchanged. `--fix-lockfile` keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass.
- `pnpm runtime set <name> <version>` now saves the runtime to `devEngines.runtime` by default instead of `engines.runtime`. Pass `--save-prod` (or `-P`) to save it to `engines.runtime` instead [#​11948](https://github.com/pnpm/pnpm/issues/11948).
##### Patch Changes
- Fix a credential disclosure issue where an unscoped `_authToken` (or `_auth`, or `username` + `_password`, or `tokenHelper`) defined in one source — `~/.npmrc`, `~/.config/pnpm/auth.ini`, a workspace `.npmrc`, CLI flags, etc. — would be sent as an `Authorization` header to whichever registry a different (potentially untrusted) source named. The same fix extends to client TLS credentials (`cert`, `key`) so they aren't presented to a registry their author didn't choose.
pnpm now rewrites each unscoped per-registry setting (`_authToken`, `_auth`, `username`, `_password`, `tokenHelper`, `cert`, `key`) to its URL-scoped form at load time, using the `registry=` value declared in the same source (or the npmjs default registry if the source declares none). A later layer overriding `registry=` therefore cannot pull an unscoped credential along, because it is already pinned to the URL its author intended. `ca`/`cafile` are intentionally not rescoped — they're trust anchors, not credentials, and corporate MITM-proxy setups rely on them applying globally.
Every rescope emits a deprecation warning telling the user where the setting was pinned and how to write it directly. npm has rejected unscoped credentials outright since `npm@9`, and pnpm intends to remove support in a future major release. To target a specific registry, write the setting URL-scoped (e.g. `//registry.example.com/:_authToken=...` or `//registry.example.com/:cert=...`).
`@pnpm/network.auth-header`: removed the `defaultRegistry` parameter from `createGetAuthHeaderByURI` and `getAuthHeadersFromCreds`. Now that credentials are URL-scoped at load time, the merged `configByUri` never contains the empty-string "default registry" placeholder slot, so re-keying it onto the merged default registry is no longer needed.
- Fix `pnpm deploy` crashing with `ENOENT: ... lstat '<deployDir>/node_modules'` when `configDependencies` declares pacquet (`pacquet` or `@pnpm/pacquet`). The deploy directory never installs config dependencies, so the install engine they designate isn't on disk to invoke; the nested install now skips them.
- Reject git resolutions whose `commit` field is not a 40-character hexadecimal SHA before invoking `git`. A malicious lockfile could otherwise smuggle a value such as `--upload-pack=<command>` through `git fetch` / `git checkout`, which on SSH or local-file transports executes the supplied command.
- Limit concurrent project manifest reads while listing large workspaces to avoid `EMFILE` errors.
- Reject patch files whose `diff --git` headers reference paths outside the patched package directory. Previously a malicious `.patch` file added via a pull request could write, delete, or rename arbitrary files reachable by the user running `pnpm install`.
- Improve the log message that pnpm prints after auto-adding entries to `minimumReleaseAgeExclude` when `minimumReleaseAge` is set without `minimumReleaseAgeStrict`. The message previously referred to the internal "loose mode" terminology, which wasn't searchable in the docs; it now tells the user to set `minimumReleaseAgeStrict` to `true` if they want these updates gated behind a prompt instead [#​11747](https://github.com/pnpm/pnpm/issues/11747).
- Reject dependency aliases that contain path-traversal segments (such as `@x/../../../../../.git/hooks`) when reading them from a package manifest or symlinking them into `node_modules`. A malicious registry package could otherwise use a transitive dependency key to make `pnpm install` create symlinks at attacker-chosen paths outside the intended `node_modules` directory.
- Reject `pnpm-lock.yaml` entries whose remote tarball `resolution:` block is missing the `integrity` field. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that strips `integrity:`) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under `--frozen-lockfile`. pnpm now fails closed at lockfile-read time with `ERR_PNPM_MISSING_TARBALL_INTEGRITY`. Git-hosted tarballs (`gitHosted: true` or a URL on codeload.github.com / bitbucket.org / gitlab.com) and `file:` tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes.
- Validate `devEngines.runtime` and `engines.runtime` version ranges for `node`, `deno`, and `bun` when `onFail` is set to `error` or `warn`. Previously these settings only had an effect with `onFail: 'download'` — the `error` and `warn` modes silently did nothing [#​11818](https://github.com/pnpm/pnpm/issues/11818). Violations now throw `ERR_PNPM_BAD_RUNTIME_VERSION`.
- Require provenance before treating trusted publisher metadata as the strongest trust evidence.
### [`v11.3.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1130)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.2.2...v11.3.0)
##### Minor Changes
- Added `pnpm stage` with `publish`, `list`, `view`, `approve`, `reject`, and `download` subcommands for npm staged publishing.
- Added a new setting `trustLockfile`. When `true`, `pnpm install` skips the supply-chain verification pass that re-applies `minimumReleaseAge` / `trustPolicy='no-downgrade'` to every entry in the loaded lockfile. The install treats the lockfile as already-trusted — useful for closed-source projects where every commit comes from a trusted author. Defaults to `false`; verification stays on by default. Set in `pnpm-workspace.yaml`.
Also cut the memory footprint of the verification pass itself: the per-(registry, name) trust-meta cache previously retained the full packument — dependency graphs, scripts, README, and per-version manifests — for the entire install. On large workspaces (`~4k` lockfile entries with `minimumReleaseAge` + `trustPolicy: no-downgrade` enabled) this could OOM CI runners with a 2GB heap cap. The cache now stores only the fields the trust check actually reads (`time`, per-version `_npmUser.trustedPublisher`, `dist.attestations.provenance`). The abbreviated-metadata cache is similarly projected to just the package-level `modified` field and the set of currently-listed version names. Fixes [#​11860](https://github.com/pnpm/pnpm/issues/11860).
- Implemented `pnpm pkg` command natively, following `npm pkg` standards.
- Implemented `pnpm repo` command natively, following `npm repo` standards.
- Implemented `pnpm set-script` (alias `ss`) natively. Adds or updates an entry in the `scripts` field of the project manifest, supporting `package.json`, `package.json5`, and `package.yaml` formats.
- Add a `skip-manifest-obfuscation` option for `pnpm pack` and `pnpm publish`. When enabled, the original `packageManager` field and publish lifecycle scripts are kept in the packed/published manifest instead of being stripped. The pnpm-specific `pnpm` field continues to be omitted.
##### Patch Changes
- Fixed `pnpm dlx` failing with `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` when the installed package's CAS slot is missing its `package.json`. Observed in the wild for `pnpm dlx node@runtime:<version>` when the GVS slot was populated without the synthesized manifest runtime archives need (they don't ship a `package.json` of their own, so the synthesized one is the only way it gets there; an existing slot from an earlier code path that skipped the synthesis stays incomplete). The bin link itself is wired up from the resolution and remains valid, so `dlx` now falls back to the scopeless package name when the slot's manifest is unreadable — for single-bin packages (the dlx common case, including every `runtime:` spec) this matches what `manifest.bin` would have named. Multi-bin packages already require `--package=<spec> <bin>` to disambiguate and don't enter this code path.
- Fixed non-determinism in `pnpm dedupe` and `pnpm install` when a dependency graph contains packages with transitive peer dependencies on each other (e.g. `@aws-sdk/client-sts` and `@aws-sdk/client-sso-oidc`) and `auto-install-peers` is enabled. The lockfile no longer flips between two equally-valid forms across consecutive runs. The root cause was that `resolveDependencies` pushed onto its `pkgAddresses` / `postponedResolutionsQueue` arrays from inside `Promise.all`-spawned callbacks, so completion-order timing leaked into the array order and downstream cyclic-peer suffix assignment. Fixes [#​8155](https://github.com/pnpm/pnpm/issues/8155).
- Fixed a regression introduced by [#​11711](https://github.com/pnpm/pnpm/pull/11711) where `pnpm add <github-shorthand>` (and any other wanted-dependency whose alias can't be parsed from the user-supplied spec, e.g. tarball URLs or `pnpm/test-git-fetch#sha`) was silently dropped from the manifest update and from `pendingBuilds`. The alias-keyed lookup added in that PR couldn't find a `wantedDependency` whose `alias` was `undefined` at parse time but resolved to a package name only after fetching, so the entry never made it into `specsToUpsert`. Restored the original index-based pairing between `directDependencies` and `wantedDependencies`; the catalog-protocol preservation that PR was originally fixing is unaffected because it's driven by `rdd.catalogLookup.userSpecifiedBareSpecifier`, not by the lookup. Fixes the three `rebuilds dependencies` / `rebuilds specific dependencies` / `rebuild with pending option` failures in `building/commands/test/build/index.ts`.
- Fixed `pnpm add --config` leaving orphan entries in `pnpm-lock.env.yaml` (the optional subdependencies of the previously resolved version of the updated config dependency).
### [`v11.2.2`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1122)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.2.1...v11.2.2)
##### Patch Changes
- When the install engine is delegated to pacquet via `configDependencies`, the user's CLI flags passed to `pnpm install` (e.g. `--no-runtime`, `--prod`, `--dev`, `--no-optional`, `--node-linker`, `--cpu`/`--os`/`--libc`, `--offline`, `--prefer-offline`) are now forwarded to pacquet's `install` subcommand verbatim. Previously pacquet was invoked with a fixed argument list, so flags like `--no-runtime` were silently dropped. Flag forwarding is gated on the command being `install`/`i`; `add`, `update`, and `dedupe` still don't forward (their flag surface doesn't line up with pacquet's `install`).
- Fixed `pnpm up` (and `pnpm add` / `pnpm remove`) failing with `pacquet_package_manager::outdated_lockfile` when pacquet is declared in `configDependencies`. pnpm now passes `--ignore-manifest-check` to pacquet so its `--frozen-lockfile` check doesn't fire against the (pre-mutation) `package.json` pnpm hasn't written yet [#​11797](https://github.com/pnpm/pnpm/issues/11797). Requires a pacquet release that supports the flag — bump `PACQUET_VERSION` in the e2e tests once it ships.
### [`v11.2.1`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1121)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.2.0...v11.2.1)
##### Patch Changes
- Mark optional subdependency snapshots of config dependencies with `optional: true` in the env lockfile, matching how optional dependencies are recorded elsewhere in `pnpm-lock.yaml`. Previously, snapshots for the platform-specific subdeps pulled in via a config dep's `optionalDependencies` were written as empty objects, which was inconsistent with the rest of the lockfile and made it look like those non-host platform variants were required.
- Fix `pickRegistryForPackage` returning the wrong registry for an unscoped `npm:` alias under a scoped local name. A manifest entry like `"@​private/foo": "npm:lodash@^1"` was routing the `lodash` fetch through `registries["@​private"]`, even though `lodash` is unscoped and doesn't live on that registry. The npm-alias branch now returns the alias target's own scope (or `null` for an unscoped target, falling through to `registries.default`) instead of leaking into the local key's scope.
- Don't print "Installing config dependencies..." when config dependencies are already installed and nothing needs to be fetched, re-linked, or removed.
### [`v11.2.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1120)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.1.3...v11.2.0)
##### Minor Changes
- **Experimental:** Adding [`@pnpm/pacquet`](https://npmx.dev/package/@​pnpm/pacquet) (the Rust port of pnpm) to `configDependencies` in `pnpm-workspace.yaml` now delegates the materialization phase of `pnpm install` to the pacquet binary. pnpm still owns dependency resolution; pacquet only fetches and imports from the freshly-written lockfile. This is an opt-in preview of the Rust install engine [#​11723](https://github.com/pnpm/pnpm/issues/11723).
To configure pacquet in a project, run:
```
pnpm add @​pnpm/pacquet --config
```
You'll see changes in `pnpm-workspace.yaml` and `pnpm-lock.yaml` that should be committed. If you experience any issues with pacquet, please let us know by mentioning this in the GitHub issue you create.
- `configDependencies` now resolve and install one level of `optionalDependencies` declared by the config dependency, with `os`/`cpu`/`libc` platform filtering applied at install time. This unlocks the esbuild/swc-style pattern where a package ships platform-specific binaries via `optionalDependencies` — a config dependency can now do the same and have the matching binary symlinked next to it in the global virtual store, so `require('pkg-platform-arch')` from inside the config dependency resolves correctly.
The env lockfile records all platform variants regardless of host platform, so it remains portable across machines. Each entry in a config dependency's `optionalDependencies` must declare an exact version — ranges and tags are rejected to keep installs reproducible.
- Implement the documented `pnpm login --scope <scope>` flag. The scope is normalized (a leading `@` is added if missing; blank values are ignored) and an `@<scope>:registry=<registry>` mapping is written to the pnpm auth file alongside the auth token. Subsequent installs of `@<scope>/*` packages then route to the chosen registry. Previously `pnpm login --scope foo` errored with `Unknown option: 'scope'` despite the flag being listed in the online documentation [#​11716](https://github.com/pnpm/pnpm/issues/11716).
- `pnpm outdated` and `pnpm update --interactive` now report Node.js, Deno, and Bun runtimes installed as project dependencies (`runtime:` specifiers). Previously these were silently skipped.
##### Patch Changes
- Fix `cafile=<relative-path>` in `.npmrc` being read from the wrong directory when pnpm is invoked from a different cwd (e.g. `pnpm --dir <project> install` from a CI wrapper or monorepo script). The path is now resolved against the directory of the `.npmrc` that declared it, not `process.cwd()`. Before this fix the CA file silently failed to load — the install proceeded without the configured CA and the user only saw TLS errors against a private registry, with no log line tying back to the wrongly resolved path [#​11624](https://github.com/pnpm/pnpm/issues/11624).
- Fix `config.registry` getting a trailing slash appended when `registry` is set in `.npmrc` and no `registries.default` is provided by `pnpm-workspace.yaml`. The sync from `registries.default` to `config.registry` introduced in [#​11744](https://github.com/pnpm/pnpm/issues/11744) now only fires when the workspace manifest actually contributes a different default.
- Fix global add/update to handle minimumReleaseAge policy violations instead of surfacing an internal resolver guardrail error.
- Fix two crashes with `injectWorkspacePackages: true` when the lockfile has been pruned (e.g. by `turbo prune --docker`):
- `Cannot use 'in' operator to search for 'directory' in undefined`: a peer-dependency-variant injected snapshot inherits its `resolution` from the base `packages:` entry; when a pruner drops that base entry the readers crash. `convertToLockfileObject` now reconstructs the directory resolution from the `file:` depPath at load time — a single normalization point, so every reader sees a fully-formed snapshot.
- `ERR_PNPM_ENOENT` on `node_modules/.bin/<tool>`: after `prepare`/`postinstall`, `runLifecycleHooksConcurrently` re-imported each injected workspace package; the `scanDir`-into-`filesMap` workaround fed target-internal paths to the importer, which the `makeEmptyDir` fast path ([#​11088](https://github.com/pnpm/pnpm/issues/11088)) then wiped. Drop the workaround and pass `keepModulesDir: true` so the importer preserves the target's existing `node_modules` (bin links + transitive deps) and source files keep their hardlinks.
- Fixed `pnpm login` and `pnpm logout` ignoring `registries.default` from `pnpm-workspace.yaml` [#​10099](https://github.com/pnpm/pnpm/issues/10099).
- Fix the `minimumReleaseAge` (publishedBy) maturity shortcut to be inclusive at the cutoff. Previously, abbreviated metadata whose `modified` field equalled the cutoff fell off the fast path and triggered a full-metadata re-fetch (or a `MISSING_TIME` error when full metadata wasn't permitted). Since `modified` is an upper bound on every version's publish time, `modified == publishedBy` already implies every version passes the per-version `<=` filter in `filterPkgMetadataByPublishDate`, so the shortcut now accepts the boundary case directly. Strictly `>` (was `>=`) at the rejection branch.
- Honor `publishConfig.access` when publishing packages.
### [`v11.1.3`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1113)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.1.2...v11.1.3)
##### Patch Changes
- `pnpm install` now re-validates `pnpm-lock.yaml` entries against the active `minimumReleaseAge` and `trustPolicy: 'no-downgrade'` policies before any tarball is fetched. Lockfiles resolved elsewhere (committed to the repo, restored from a CI cache, produced by an older pnpm) under a weaker or absent policy can no longer install a freshly-published or trust-downgraded version silently. Violating entries abort the install with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`, `ERR_PNPM_TRUST_DOWNGRADE`, or the generic `ERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION` when both policies trip in the same batch; `minimumReleaseAgeExclude` and `trustPolicyExclude` are honored. Verification results are cached so repeat installs against an unchanged lockfile take a fast path, and pnpm shows a transient progress line while the registry round-trip runs.
When fresh resolution picks an immature version, the behavior depends on `minimumReleaseAgeStrict`:
- **Loose mode** — the default, in effect whenever `minimumReleaseAge` keeps its built-in 24-hour value — auto-adds the immature picks to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` and lets the install proceed. A single info message lists what was persisted.
- **Strict mode** in an interactive terminal collects every immature direct AND transitive pick in one pass and prompts once with the full list. Approving adds them to `minimumReleaseAgeExclude` and the install continues; declining aborts before the lockfile, `package.json`, or `node_modules` is touched.
- **Strict mode** in CI (or any non-TTY context) aborts with `ERR_PNPM_NO_MATURE_MATCHING_VERSION` listing every offending entry, instead of failing on the first one the resolver hit.
`minimumReleaseAgeStrict` auto-enables whenever the user explicitly sets `minimumReleaseAge` (CLI flag, env var, global `config.yaml`, or `pnpm-workspace.yaml`); set `minimumReleaseAgeStrict: false` to keep loose-mode auto-collect even with an explicit `minimumReleaseAge` value. Closes [#​10438](https://github.com/pnpm/pnpm/issues/10438), [#​10488](https://github.com/pnpm/pnpm/issues/10488), [#​11687](https://github.com/pnpm/pnpm/issues/11687).
- Allow redundant trailing base64 padding in `.npmrc` auth values and report invalid auth base64 with a pnpm error.
- Make `pnpm self-update` respect `minimumReleaseAge` (and `minimumReleaseAgeExclude`) when resolving which pnpm version to install.
When the `latest` dist-tag points to a version newer than the configured age threshold, `self-update` now selects the newest mature version instead unless excluded by `minimumReleaseAgeExclude`.
Also makes `dlx` and `outdated` surface invalid `minimumReleaseAgeExclude` patterns under the same `ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE` error code already used by `install`, instead of leaking the internal `ERR_PNPM_INVALID_VERSION_UNION` / `ERR_PNPM_NAME_PATTERN_IN_VERSION_UNION` codes.
- Global installs respect global config build policy (e.g., `dangerouslyAllowAllBuilds` from config.yaml) when GVS is enabled [#​9249](https://github.com/pnpm/pnpm/issues/9249).
The global virtual-store (GVS) default `allowBuilds = {}` was applied before workspace manifest settings were read and before global config values (stripped by `extractAndRemoveDependencyBuildOptions`) were re-applied via `globalDepsBuildConfig`. This caused `hasDependencyBuildOptions` to return `true` (because `{}` is not null), blocking restoration of global config values like `dangerouslyAllowAllBuilds`. As a result, global installs skipped all build scripts even when the config explicitly allowed them.
This fix moves the GVS default to **after** workspace manifest reading and `globalDepsBuildConfig` re-application, so that:
1. Workspace manifest `allowBuilds` takes precedence (if present)
2. Global config `dangerouslyAllowAllBuilds` is properly restored (if set and no workspace policy exists)
3. Empty `{}` is only applied as a last resort when no policy is configured anywhere
- Honor `--silent` when `verifyDepsBeforeRun: install` auto-installs dependencies before `pnpm run` or `pnpm exec`, preventing install output from being written to stdout [#​11636](https://github.com/pnpm/pnpm/issues/11636).
- Fix lockfile parsing failures when `pnpm-lock.yaml` contains CRLF line endings and multiple YAML documents [#​11612](https://github.com/pnpm/pnpm/issues/11612).
- Anchor the side-effects-cache key and global-virtual-store hash to the project's script-runner Node — `engines.runtime` pin when present, shell `node` otherwise — instead of pnpm's own runtime.
`ENGINE_NAME` (the `<platform>;<arch>;node<major>` prefix used as the side-effects-cache key and the engine portion of the GVS hash) was computed from `process.version` — the Node that runs pnpm itself. That was wrong in two situations:
1. **`@pnpm/exe` SEA bundle.** The bundle has its own embedded Node, not the `node` on the user's `PATH` that actually spawns lifecycle scripts. Two pnpm installations on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store across two Node majors even though both installs would run scripts on the same shell `node`.
2. **`engines.runtime` / `devEngines.runtime` pin.** When a project pins a Node version via `devEngines.runtime` (pnpm v11+), pnpm downloads that Node into `node_modules/node/` and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node — so two installs of the same project with two different runner Nodes would still disagree on the GVS slot path even though scripts run on the same pinned Node.
Three changes:
- `@pnpm/engine.runtime.system-node-version` now exports `engineName(nodeVersion?)`. Resolves the version in this order: explicit override → `getSystemNodeVersion()` (which already prefers `node --version` over `process.version` in SEA contexts) → `process.version`.
- `@pnpm/deps.graph-hasher` now exports `findRuntimeNodeVersion(snapshotKeys)` — scans an iterable of lockfile snapshot keys for a `node@runtime:<version>` entry and returns its bare version string. `calcDepState` and `calcGraphNodeHash`/`iterateHashedGraphNodes` accept a `nodeVersion?` (in the options bag for the first, as a trailing parameter / ctx field for the others), forwarded to `engineName()`. The default (no override) preserves the pre-change behaviour. The legacy `ENGINE_NAME` constant in `@pnpm/constants` is unchanged so external consumers and existing tests keep working; in non-SEA, non-pinned contexts every value lines up.
- Every install-side caller of the graph-hasher (`@pnpm/installing.deps-resolver`, `@pnpm/installing.deps-restorer`, `@pnpm/installing.deps-installer`, `@pnpm/building.during-install`, `@pnpm/building.after-install`, `@pnpm/deps.graph-builder`) now derives the project's pinned runtime via `findRuntimeNodeVersion(Object.keys(graph))` once per invocation and threads it through.
On upgrade, two one-time GVS slot churns are possible:
- **SEA-pnpm users** without a runtime pin: slots that previously hashed under the embedded-Node major (e.g. `node26`) now hash under the shell-Node major (e.g. `node24`), matching what pacquet, the npm-published `pnpm` package, and any other pnpm-compatible tool already produce.
- **Projects with a `devEngines.runtime` pin**: slots that previously hashed under the runner's Node major now hash under the pinned Node major, matching what the lifecycle scripts will actually run on.
In both cases the old slots become prune-eligible.
- Resolve the GVS hash's engine portion per-snapshot when a dependency declares its own `engines.runtime`, instead of using an install-wide value.
Pnpm's resolver desugars a dep's `engines.runtime` into `dependencies.node: 'runtime:<version>'`, and the bin linker spawns that dep's lifecycle scripts through the pinned Node downloaded into `<pkgDir>/node_modules/node/`. The GVS hash and the side-effects-cache key prefix were still anchored to the install-wide runtime — so a pinning snapshot's slot encoded the wrong Node major, and a reinstall on the same host could read the cached side-effects under a key whose `<platform>;<arch>;node<major>` triple disagreed with the Node the build actually ran on.
Per-snapshot resolution now matches what `bins/linker` already does on a per-package basis:
- `@pnpm/deps.graph-hasher` adds `readSnapshotRuntimePin(children)` — reads the `node` entry from one snapshot's graph children and extracts the version from a `node@runtime:` value. Pairs with the existing `findRuntimeNodeVersion(snapshotKeys)` install-wide fallback (also now exported from `@pnpm/deps.graph-hasher` rather than `@pnpm/engine.runtime.system-node-version`, where it was a poor fit — `system-node-version` is about probing the host Node, not parsing lockfile-derived strings).
- `calcDepState` and `calcGraphNodeHash` consult `readSnapshotRuntimePin(graph[depPath].children)` first and only fall back to the install-wide `nodeVersion` parameter when the snapshot doesn't pin its own Node.
Pacquet mirrors the same precedence at the `calc_graph_node_hash` call site in `package-manager/src/virtual_store_layout.rs` — a new `find_own_runtime_node_major(snapshot)` helper reads each snapshot's `dependencies` for a `node` entry with `Prefix::Runtime` and overrides the install-wide engine when present.
On upgrade, snapshots of dependencies that declare their own `engines.runtime` re-hash under that dep's pinned Node instead of the install-wide value. The old slots become prune-eligible. Closes [#​11690](https://github.com/pnpm/pnpm/issues/11690).
- Fixed `pnpm publish` failing with a 404 when authentication relied on OIDC trusted publishing alongside an `.npmrc` written by `actions/setup-node` (`_authToken=${NODE_AUTH_TOKEN}`) without `NODE_AUTH_TOKEN` being set. Unresolved `${VAR}` placeholders in auth values are now treated as empty rather than passed through verbatim, so the literal placeholder no longer surfaces as a bearer token when OIDC fallback is the intended auth source [#​11513](https://github.com/pnpm/pnpm/issues/11513).
- Fix `devEngines.packageManager` (singular form, without `onFail`) defaulting to `onFail: "error"` instead of the documented `pmOnFail: "download"`. As a result, a project that pinned a different pnpm version via `devEngines.packageManager` and ran `pnpm install` from a mismatched pnpm version failed with a hard error, even though the migration table from `managePackageManagerVersions: true` to `pmOnFail: download (default)` promises the install would auto-download the wanted version [#​11676](https://github.com/pnpm/pnpm/issues/11676).
The array form of `devEngines.packageManager` keeps its existing per-element defaults (`error` for the last entry, `ignore` for the rest), since those reflect explicit prioritization by the user. Explicit `onFail` values continue to win.
- Fix `devEngines.packageManager` not writing `packageManagerDependencies` to `pnpm-lock.yaml` when the lockfile lacks an env-doc entry. Previously the lockfile sync skipped resolution unless an existing `packageManagerDependencies.pnpm` entry needed refreshing, so a fresh install without `onFail: "download"` left the resolved pnpm version unrecorded — contradicting the documented behavior that the resolved version is stored in `pnpm-lock.yaml` [#​11674](https://github.com/pnpm/pnpm/issues/11674).
- Warn when `package.json` contains a legacy `pnpm` field with settings pnpm no longer reads from `package.json` (e.g. `pnpm.overrides`, `pnpm.patchedDependencies`). Previously these were silently ignored after the upgrade from v10, leaving users unaware that their overrides/patched dependencies had stopped taking effect [#​11677](https://github.com/pnpm/pnpm/issues/11677).
### [`v11.1.2`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1112)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.1.1...v11.1.2)
##### Patch Changes
- `convertEnginesRuntimeToDependencies`: switch the runtime-dependency write to `Object.defineProperty` so the CodeQL `js/prototype-polluting-assignment` rule treats the assignment as safe regardless of the property name (follow-up to [#​11609](https://github.com/pnpm/pnpm/pull/11609)).
- Address CodeQL static-analysis findings: guard manifest dependency writes against prototype-polluting keys (`__proto__`, `constructor`, `prototype`), and replace a potentially super-linear semver-detection regex in registry 404 hints with an O(n) parser.
- Strip `sec-fetch-*` headers from outgoing HTTP requests. These headers are automatically added by undici's `fetch()` implementation per the Fetch spec but cause Azure DevOps Artifacts to return HTTP 400 for uncached upstream packages, as ADO interprets them as browser requests [#​11572](https://github.com/pnpm/pnpm/issues/11572).
- Fix `minimumReleaseAge` handling for cached abbreviated metadata.
The version-spec cache fast path no longer rethrows `ERR_PNPM_MISSING_TIME` under `strictPublishedByCheck`; it now falls through to the registry-fetch path, consistent with the adjacent mtime-gated cache block.
When the registry returns 304 Not Modified for a package whose cached metadata is abbreviated (no per-version `time`), pnpm now re-fetches with `fullMetadata: true` if `minimumReleaseAge` is active and the package was modified after the cutoff. The upgraded metadata is persisted to disk so subsequent installs don't repeat the fetch. Previously the abbreviated meta was used as-is and the maturity check fell back to its warn-and-skip path, silently bypassing the quarantine and emitting a misleading "metadata is missing the time field" warning.
Closes [#​11619](https://github.com/pnpm/pnpm/issues/11619).
- Fix `pnpm upgrade --interactive --latest -r` not respecting named catalog groups. Previously, upgrading a dependency using a named catalog (e.g. `"catalog:foo"`) would incorrectly rewrite `package.json` to `"catalog:"` and place the updated version in the default catalog instead of the named one [#​10115](https://github.com/pnpm/pnpm/issues/10115).
- Fixed `optimisticRepeatInstall` skipping `pnpm-lock.yaml` merge conflict resolution when the existing `node_modules` state appears up to date.
- Fix `minimumReleaseAge` / `resolutionMode: time-based` installs failing on lockfiles whose `time:` block is missing entries. The npm-resolver's peek-from-store fast path now surfaces `publishedAt` from the lockfile rather than discarding it, and falls through to a registry metadata fetch when the time-based cutoff can't be computed from the data on hand.
### [`v11.1.1`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1111)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.1.0...v11.1.1)
##### Patch Changes
- Skip installability validation when scanning workspace projects in `checkDepsStatus` (run by `verifyDepsBeforeRun`). Previously the status check called `findWorkspaceProjects`, which validates each project's `engines` and `os`/`cpu`/`libc` and warns about useless fields in non-root manifests — work that the install pipeline already performs. With no `nodeVersion` threaded through, the engine check also fell back to the system Node from `PATH` and emitted spurious "Unsupported engine" warnings before scripts ran. Status-only callers now use `findWorkspaceProjectsNoCheck`; install paths continue to validate.
- Fixed `pnpm add <alias>:@​scope/pkg` for [named registries](https://github.com/pnpm/pnpm/pull/11324). The local resolver was claiming any specifier containing `/` as a local directory, so `pnpm add bit:@​teambit/bit` (with `bit` configured under `namedRegistries`) installed a bogus link to `bit:@​teambit/bit/` instead of resolving from the configured registry. The local resolver now runs after the named-registry resolver in the resolution chain.
- Updated `@zkochan/cmd-shim` to 9.0.3. The sh shim it writes for `.cmd` / `.bat` targets now escapes the `/C` switch as `//C`, so it survives the path translation Git Bash applies when launching `cmd.exe`. Without this, a bare `/C` was rewritten to `C:\` before reaching cmd.exe — the switch was dropped, cmd started interactively, and the calling script saw the cmd banner instead of the wrapped command's output. Affects any cmd-shim-wrapped batch script invoked from Git Bash / MSYS / Cygwin on Windows. See [pnpm/cmd-shim#55](https://github.com/pnpm/cmd-shim/pull/55).
### [`v11.1.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1110)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.9...v11.1.0)
##### Minor Changes
- Added `pnpm audit signatures` to verify ECDSA registry signatures for installed packages against keys from `/-/npm/v1/keys` [#​7909](https://github.com/pnpm/pnpm/issues/7909). Scoped registries are respected, and registries without signing keys are skipped.
- Added support for installing packages from the [GitHub Packages npm registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry) via a built-in `gh:` prefix (e.g. `pnpm add gh:@​acme/private`), and, more broadly, for arbitrary named registries in the style of [vlt's named-registry aliases](https://docs.vlt.sh/cli/registries). Authentication is picked up from the existing per-URL `.npmrc` entries (e.g. `//npm.pkg.github.com/:_authToken=...`), so no separate auth mechanism is required.
Additional aliases — or an override for the built-in `gh` alias, for GitHub Enterprise Server — can be configured under `namedRegistries` in `pnpm-workspace.yaml`:
```yaml
namedRegistries:
gh: https://npm.pkg.github.example.com/
work: https://npm.work.example.com/
```
With this, `work:@​corp/lib@^2.0.0` resolves against `https://npm.work.example.com/`. [#​11324](https://github.com/pnpm/pnpm/issues/11324).
- Allow setting sbom spec version using `--sbom-spec-version` [#​11389](https://github.com/pnpm/pnpm/pull/11389).
- Add `--no-runtime` flag (config: `runtime=false`) to skip installing runtime entries (e.g. Node.js downloaded via `devEngines.runtime`) without modifying the lockfile. The lockfile keeps the runtime entry so frozen-lockfile validation still passes; only the runtime fetch and `.bin` linking are skipped. Useful in CI matrices where the runtime is provisioned externally (e.g. via `pnpm runtime -g set node <version>`) before `pnpm install` runs.
- Added the `pnpm bugs` command that opens a package's bug tracker URL in the browser. With no arguments, it reads the current project's `package.json`; with one or more package names, it fetches each package's metadata from the registry and opens its bug tracker. Falls back to `<repository>/issues` when the `bugs` field is missing [#​11279](https://github.com/pnpm/pnpm/pull/11279).
- Added `pnpm owner` command to manage package owners on the registry.
##### Patch Changes
- Added "published X ago by Y" information to the `pnpm view` command output, similar to `npm view`. This is useful when comparing against `minimumReleaseAge`.
For example, `pnpm view pnpm` now shows:
```
published 17 hours ago by GitHub Actions
```
- `pnpm publish` now honors the configured HTTP/HTTPS proxy (including `https_proxy`/`http_proxy`/`no_proxy` environment variables) when polling the registry's `doneUrl` during the web-based authentication flow. Previously the poll bypassed the proxy, causing the registry to respond `403` from a different source IP and the login to never complete [#​11561](https://github.com/pnpm/pnpm/issues/11561).
- `pnpm add -g` now installs each space-separated package into its own isolated directory by default. To bundle multiple packages into the same isolated install (so that they share dependencies and are removed together), pass them as a comma-separated list. For example:
- `pnpm add -g foo bar` installs `foo` and `bar` as two independent globals — removing one does not affect the other.
- `pnpm add -g foo,bar qar` bundles `foo` and `bar` into a single isolated install while `qar` is installed on its own.
Related: [#​11587](https://github.com/pnpm/pnpm/issues/11587).
- `pnpm runtime set <name> <version>` no longer fails in the root of a multi-package workspace with the `ADDING_TO_ROOT` error. Installing the workspace root is a valid target for a runtime, so the command now bypasses that safety check.
- Fix `pnpm --version` hanging for the lifetime of the worker pool after the version was printed. `main.ts`'s `--version` short-circuit returned before reaching the command-handler `finally` that calls `finishWorkers()`, so the worker pool that `switchCliVersion` had spawned during integrity resolution stayed alive and held the Node event loop open. The CLI entry now runs `finishWorkers()` from its own `finally`, so every exit path tears the pool down.
Repro: `pnpm --version` in a workspace whose `devEngines.packageManager` version already matches the running pnpm + `onFail: "download"`. `switchCliVersion` resolves the integrity (spawning workers), finds nothing to swap, returns. The version prints, then the process hangs.
### [`v11.0.9`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1109)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.8...v11.0.9)
##### Patch Changes
- Fixed installation of GitLab-hosted dependencies. pnpm now downloads the tarball from `https://gitlab.com/<user>/<project>/-/archive/<sha>/<project>-<sha>.tar.gz` instead of the GitLab API endpoint that contained an encoded slash (`%2F`) between user and project. The encoded slash both triggered `406 Not Acceptable` responses from GitLab and produced virtual store directory names that Node refused to import (`ERR_INVALID_MODULE_SPECIFIER`) [#​11533](https://github.com/pnpm/pnpm/issues/11533).
- Honor `NPM_CONFIG_USERCONFIG` (and its lowercase `npm_config_userconfig` form) as a low-priority fallback when locating the user-level `.npmrc`. This restores compatibility with environments that point npm at a custom auth file via that env var — most notably `actions/setup-node`, which writes registry credentials to `${runner.temp}/.npmrc` and exports `NPM_CONFIG_USERCONFIG` to reference it. Without this, GitHub Actions workflows using `actions/setup-node` to authenticate to private registries broke after upgrading to pnpm v11. PNPM-prefixed env vars and `npmrcAuthFile` from the global `config.yaml` continue to take precedence [#​11539](https://github.com/pnpm/pnpm/issues/11539).
- Fix `pnpm pack` not bundling dependencies listed in `bundleDependencies` (or `bundledDependencies`). The npm-packlist upgrade in pnpm 11 changed its API to require the caller to pre-populate the dependency tree, which the wrapper was not doing — `bundleDependencies` were silently dropped from the tarball [#​11519](https://github.com/pnpm/pnpm/issues/11519).
- Fixed the pnpm CLI crashing with a confusing `SyntaxError: Invalid regular expression flags` instead of printing a clear "requires Node.js v22.13" error when launched on an unsupported Node.js version. The Node.js version check in `bin/pnpm.mjs` was effectively dead code because the static `import` of the bundled `dist/pnpm.mjs` was hoisted by the ES module loader and parsed before the check could run [#​11546](https://github.com/pnpm/pnpm/issues/11546).
- Fixed `pnpm --prefix=<dir> install` overwriting the existing `pnpm-workspace.yaml` in `<dir>` with `set this to true or false` placeholders. The renamed `--prefix` option (which maps to `dir`) was not honored when locating the workspace root, so the workspace manifest's `allowBuilds` settings were not loaded into config and got clobbered when ignored builds were auto-populated [#​11535](https://github.com/pnpm/pnpm/issues/11535).
- Fixed `pnpm publish --provenance` failing with a 422 from the registry when the package version contained semver build metadata (e.g. `1.0.0-canary.0+abc1234`). The `+<build>` segment is now stripped before packing so that the version embedded in the tarball, the metadata sent to the registry, and the sigstore provenance subject all agree [#​11518](https://github.com/pnpm/pnpm/issues/11518).
### [`v11.0.8`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1108)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.7...v11.0.8)
##### Patch Changes
- Restored the heuristic that preserves tarball URLs in `pnpm-lock.yaml` when they cannot be derived from name+version+registry, even with the default `lockfileIncludeTarballUrl: false`. Without this, `pnpm install --frozen-lockfile` from an empty store fails with `ERR_PNPM_FETCH_404` for packages on registries that serve tarballs from a non-standard path — most notably GitHub Packages (`https://npm.pkg.github.com/download/<scope>/<name>/<version>/<hash>`) and JSR. `lockfileIncludeTarballUrl: true` continues to force the URL into the lockfile for every package [#​11276](https://github.com/pnpm/pnpm/issues/11276).
- Run `preversion`, `version`, and `postversion` lifecycle scripts for `pnpm version`.
- Fixed `ERR_PNPM_BAD_TARBALL_SIZE` when a registry serves tarballs with an end-to-end `Content-Encoding` (e.g. `gzip`). Tarballs are already compressed, so the fetcher now requests them with `Accept-Encoding: identity` (matching pnpm v10's effective behavior) and, as defense in depth against misbehaving servers, no longer enforces the strict `Content-Length` check when the response declares a `Content-Encoding` — `Content-Length` in that case refers to the encoded payload, not the decoded bytes the fetch implementation yields [#​11506](https://github.com/pnpm/pnpm/issues/11506).
### [`v11.0.7`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1107)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.6...v11.0.7)
##### Patch Changes
- Restore the execute bit on the `node-gyp` shims packed inside `@pnpm/exe` (`dist/node-gyp-bin/node-gyp`, `dist/node-gyp-bin/node-gyp.cmd`, and `dist/node_modules/node-gyp/bin/node-gyp.js`). Without this, `pnpm/action-setup`'s standalone path (used on runners with Node.js < 22.13) failed any install whose lifecycle script invoked `node-gyp rebuild` with `sh: 1: node-gyp: Permission denied` [#​11483](https://github.com/pnpm/pnpm/issues/11483).
- Fixed the `pn`, `pnpx`, and `pnx` aliases failing in Git Bash / MSYS2 on Windows when pnpm was installed via `@pnpm/exe` (or after `pnpm self-update`) [#​11486](https://github.com/pnpm/pnpm/issues/11486). Running `pnpx` (or `pnx`) printed the cmd.exe banner and dropped the user into an interactive command prompt instead of running `pnpm dlx`. The `bin` field rewrite on Windows was pointing those aliases at `.cmd` files; cmd-shim's Bash shim for a `.cmd` target wraps it in `exec cmd /C ...`, and MSYS2 mangles `/C` into a Windows path before cmd.exe sees it. The aliases are now `.exe` hardlinks of the SEA binary, which detects which name it was launched as via `process.execPath` and prepends `dlx` for `pnpx` / `pnx`.
- Fix `pnpm install` recreating `node_modules` after `pnpm fetch`. `pnpm fetch` records empty `hoistPattern` and `publicHoistPattern` in `.modules.yaml`; since v11 removed the explicit-config gate, the follow-up install treated those as a hoist-pattern change and purged the modules directory. The fetch step now flags the modules manifest with `virtualStoreOnly: true` so the next install skips the hoist-pattern comparison and completes the missing post-import linking in place [#​11488](https://github.com/pnpm/pnpm/issues/11488).
- Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.
A new `gitHosted: true` field is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.
- Allow user-level preferences in the global `config.yaml`. The following settings can now be set in `~/.config/pnpm/config.yaml` (or via `pnpm config set --location global`) instead of being restricted to `pnpm-workspace.yaml`: `agent`, `globalVirtualStoreDir`, `initPackageManager`, `initType`, `registrySupportsTimeField`, `scriptShell`, `shellEmulator`, `sideEffectsCache`, `sideEffectsCacheReadonly`, `stateDir`, `strictDepBuilds`, `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter`, `updateNotifier`, `useStderr`, `verifyDepsBeforeRun`, `verifyStoreIntegrity`, `virtualStoreDir`, `virtualStoreDirMaxLength` [#​11474](https://github.com/pnpm/pnpm/issues/11474).
- Make trusted publishing (OIDC) take precedence over a configured static `_authToken` in `pnpm publish`, mirroring the npm CLI's behavior. When OIDC succeeds, the OIDC-derived token overrides any pre-configured `_authToken`; when OIDC is not applicable (no CI environment, exchange fails, registry has no trusted publisher configured), the static token is used as a fallback. This applies on every package during recursive publish, so each workspace package independently attempts trusted publishing.
Additionally, the `NPM_ID_TOKEN` env var is now honored as a CI-agnostic injection point for an OIDC ID token. Previously OIDC was only attempted on GitHub Actions or GitLab; now any CI provider that exposes its own OIDC mechanism (e.g. CircleCI's `CIRCLE_OIDC_TOKEN_V2`, Buildkite, etc.) can forward its token via `NPM_ID_TOKEN` and trusted publishing will work without pnpm needing to recognize the provider explicitly.
- `--pm-on-fail=ignore` (and other universal options like `--loglevel`, `--reporter`) is now honored when combined with `--help` or `--version`. Previously the CLI argument parser short-circuited those flags before universal options were preserved, so `pnpm audit --pm-on-fail=ignore --help` and `pnpm --pm-on-fail=ignore --version` reported the strict packageManager mismatch instead of running the requested action [#​11487](https://github.com/pnpm/pnpm/issues/11487).
- Fix a regression where `pnpm --recursive --filter '!<pkg>' run/exec/test/add` would include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative `--filter` arguments are provided, matching the [documented behavior](https://pnpm.io/cli/recursive). To include the root, pass `--include-workspace-root` [#​11341](https://github.com/pnpm/pnpm/issues/11341).
- Restore npm-CLI-compatible `--json` stdout output for `pnpm publish` ([#​11476](https://github.com/pnpm/pnpm/issues/11476)). pnpm 11 reimplemented publish natively ([#​10591](https://github.com/pnpm/pnpm/pull/10591)) and inadvertently dropped the per-package JSON object that pnpm 10 emitted transitively via the npm CLI, silently breaking downstream tooling — most notably `nx release publish`, which parses stdout JSON to confirm success ([nrwl/nx#35575](https://github.com/nrwl/nx/issues/35575)). On success, the output is now:
- `pnpm publish --json` → single object `{ id, name, version, size, unpackedSize, shasum, integrity, filename, files, entryCount, bundled }`, mirroring `npm publish --json`.
- `pnpm publish -r --json` → array of those objects, mirroring `pnpm pack --json`'s shape choice.
- `pnpm publish -r --report-summary` → existing `pnpm-publish-summary.json` envelope `{ publishedPackages: [...] }` is preserved, but each entry is upgraded to the same per-package shape (additive — `name` and `version` are still present).
- `pnpm config get @​<scope>:registry` now reports the same URL that `pnpm publish` and the resolvers actually use. Previously, `config get` only consulted `.npmrc`, while `publish`/install used the merged map that includes `pnpm-workspace.yaml`'s `registries` block — so the two could diverge silently and a publish could go to the wrong registry [#​11492](https://github.com/pnpm/pnpm/issues/11492).
### [`v11.0.6`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1106)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.5...v11.0.6)
##### Patch Changes
- Fix `pnpm_config_npmrc_auth_file` and `pnpm_config_userconfig` env vars not actually loading the custom `.npmrc`. The env vars were parsed and assigned to the resolved config, but only after `loadNpmrcConfig` had already read the default `~/.npmrc` — so the custom file path was set but never read. The relevant env vars are now consulted before the user-level `.npmrc` is loaded [#​11465](https://github.com/pnpm/pnpm/issues/11465).
- Preserve the original key order in `pnpm-workspace.yaml` when updating it. Existing keys keep their position, and new keys are inserted in alphabetical position when the existing keys are already sorted (with a leading `packages` key allowed) or appended at the end otherwise.
- Fixed `pnpm self-update` on installations originally set up by pnpm v10. v10 added `PNPM_HOME` directly to PATH and wrote a `pnpm` bootstrap shim there. v11 setup writes shims under `PNPM_HOME/bin` instead, so when a v10 user upgrades to v11 the legacy shim at `PNPM_HOME` keeps pointing into the old `.tools/<version>` install — `pnpm --version` continues to report the pre-update version even though the new version was installed under `global/v11`. Self-update now detects this layout, refreshes the legacy shims so the upgrade actually takes effect, and prints a hint suggesting `pnpm setup` to migrate PATH to the v11 layout. [#​11464](https://github.com/pnpm/pnpm/issues/11464).
- Print a warning when settings that are not allowed in the global config file (e.g. `nodeLinker`, `hoistPattern`) are present in `config.yaml` and silently ignored. Previously these settings were dropped without any feedback, leaving users unsure why their global configuration had no effect. The warning suggests moving those settings to a project-level `pnpm-workspace.yaml`, or sharing them across projects via [config dependencies](https://pnpm.io/11.x/config-dependencies).
- Throw a pnpm error when `overrides` has an invalid shape or contains a non-string value.
- Validate all `readPackage` dependency map fields, including `devDependencies`, and reject falsy non-object invalid values instead of silently accepting them.
- Prevent crashes during `pnpm config`, `pnpm set`, and `pnpm get` by tolerating `configDependencies` install failures. For these commands, a failure to install `configDependencies` (for example because the registry auth token has not been written yet) is now logged at debug level and the command proceeds. All other commands still surface the install error [#​10684](https://github.com/pnpm/pnpm/issues/10684).
- Treat `allowBuilds` as an install-state input and clear previously ignored builds when they are explicitly disallowed.
- Fixes [#​10594](https://github.com/pnpm/pnpm/issues/10594), catalogs not being read from the workspace when using the `catalog:` protocol with the `pnpm dlx` / `pnpx` command, resulting in a catalog entry not found error.
- Accept `PNPM_CONFIG_*` (uppercase) environment variables in addition to `pnpm_config_*`. Previously, only the lowercase form was honored, so env vars renamed per the v11 migration guide (e.g. `PNPM_CONFIG_USERCONFIG`) silently had no effect on case-sensitive systems like macOS and Linux [#​11465](https://github.com/pnpm/pnpm/issues/11465).
### [`v11.0.5`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1105)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.4...v11.0.5)
##### Patch Changes
- Drop the `darwin-x64` artifact from `@pnpm/exe` and from the GitHub release page. The Node.js SEA mechanism `pnpm pack-app` uses produces a binary that segfaults at startup on Intel Macs because of an upstream Node.js bug ([nodejs/node#62893](https://github.com/nodejs/node/issues/62893), tracked alongside [#​59553](https://github.com/nodejs/node/issues/59553); the Node.js team has [opted not to fix it](https://github.com/nodejs/node/pull/60250) on the grounds that x64 macOS is being phased out). Re-signing with `codesign` or `ldid` doesn't help — the corruption is in LIEF's Mach-O surgery, before signing.
Intel Mac users should install pnpm via `npm install -g pnpm` (uses the system Node.js, no SEA), or stay on pnpm 10.x. `@pnpm/exe`'s preinstall on Intel Mac now exits with a clear error pointing at these alternatives.
Closes [#​11423](https://github.com/pnpm/pnpm/issues/11423).
- `pnpm dlx` (and `pnpx`/`pnx`/`pnpm create`) now runs the same interactive `approve-builds` prompt as `pnpm add -g` when the package being launched depends on transitive packages with install scripts. Previously, the v11 `strictDepBuilds` default made dlx fail with `ERR_PNPM_IGNORED_BUILDS` and required users to re-run with `--allow-build=<pkg>` for every offending dependency. dlx also now removes the partially-populated cache directory when the install fails, so a subsequent run starts clean instead of reusing a broken install whose builds were silently skipped [#​11444](https://github.com/pnpm/pnpm/issues/11444).
- [`72629fc`](https://github.com/pnpm/pnpm/commit/72629fc): Fix `pnpm -g ls --json` and `pnpm -g ls --parseable` so they emit valid JSON and parseable output respectively, matching pnpm 10 behavior. Since the isolated global packages refactor in pnpm 11, the global list command had a custom path that always printed plain text and ignored `--json`/`--parseable`, which broke tools like `npm-check-updates` that parse the JSON output [#​11440](https://github.com/pnpm/pnpm/issues/11440).
`pnpm -g ls --depth=<n>` (with n > 0) now errors when more than one isolated global install would be involved, since each install has its own lockfile and merging their transitive trees would be incoherent. When the request can be narrowed to a single install group, the regular `list` flow is used and the full dependency tree is shown.
- Fixed `pnpm publish` to honor `publishConfig.registry` from `package.json` when publishing a single package. The native publish flow introduced in v11 was reading the registry from `.npmrc` only, ignoring the per-package override [#​11419](https://github.com/pnpm/pnpm/issues/11419).
- When `strictPeerDependencies` is `true`, the `ERR_PNPM_PEER_DEP_ISSUES` error once again renders the peer dependency issues inline using the same format as `pnpm peers check`, so users (and CI tools like Renovate) can see what failed without running `pnpm peers check` separately [#​11439](https://github.com/pnpm/pnpm/issues/11439).
- The `WARN` and error code labels in pnpm's output now wrap in brackets (`[WARN]`, `[ERR_PNPM_FOO]`). Previously the labels relied entirely on a colored background to stand out, which meant they blended into the surrounding text in terminals without color (e.g. when `NO_COLOR` is set or output is piped). The brackets are painted in the same color as the badge background, so they appear as ordinary padding in color-capable terminals — only the no-color rendering changes.
### [`v11.0.4`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1104)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.3...v11.0.4)
##### Patch Changes
- Fixed `pnpm ci` not reinstalling workspace package `node_modules` directories after the clean step [#​11427](https://github.com/pnpm/pnpm/issues/11427).
- Remove pnpm's workspace state file when cleaning node\_modules so `pnpm ci` performs a fresh install after the clean step.
- Do not remove `pnpm-lock.yaml` during `pnpm clean` when `lockfile: true` is configured in `pnpm-workspace.yaml`. The lockfile is only removed when the `--lockfile` option is passed to `pnpm clean`.
- `pnpm self-update` (with no version argument) no longer downgrades pnpm when the registry's `latest` dist-tag points to an older release than the currently active version. Run `pnpm self-update latest` to force a downgrade [#​11418](https://github.com/pnpm/pnpm/issues/11418).
- `minimumReleaseAgeStrict` now defaults to `true` whenever the user explicitly sets `minimumReleaseAge` (via `pnpm-workspace.yaml`, the global `config.yaml`, the CLI, or `pnpm_config_*` env vars).
### [`v11.0.3`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1103)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.2...v11.0.3)
##### Patch Changes
- Fix too many open files error sometimes happening on Windows, when creating command shims in `node_modules/.bin` [#​11412](https://github.com/pnpm/pnpm/issues/11412).
- Fix `ERR_PNPM_FETCH_404` when installing a project whose lockfile depends on a `file:` tarball. The previous behavior dropped the `tarball` field from `file:` and git-hosted resolutions when `lockfile-include-tarball-url=false` (the default), even though those URLs cannot be reconstructed from the package name, version, and registry [#​11407](https://github.com/pnpm/pnpm/issues/11407).
### [`v11.0.2`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1102)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.1...v11.0.2)
##### Patch Changes
- Fix `ENOENT` symlink failure when `pnpm add -g` triggers the approve-builds prompt. The global add flow used to forward an absolute `modulesDir` (`<installDir>/node_modules`) into the install run by `approve-builds`. The install layer treated `modulesDir` as a path relative to `lockfileDir` and joined it again, producing a doubled path on Windows because `path.join` does not collapse an embedded absolute path. The hoist step then tried to `mkdir` and symlink under `<installDir>\<installDir>\node_modules\.pnpm\node_modules\...` and failed with `ENOENT` [#​11403](https://github.com/pnpm/pnpm/issues/11403).
- Fixed `packageManagerDependencies` going stale when pnpm is invoked through corepack. The lockfile sync (and the `devEngines.packageManager` version check) previously ran only when pnpm was invoked directly; under corepack the entire block was skipped, so a stale entry would persist even after the running pnpm version changed. The lockfile sync now runs regardless of how pnpm was invoked, while the pnpm-managed version switch (`onFail: 'download'`) remains skipped under corepack so it doesn't fight corepack's own version selection [#​11397](https://github.com/pnpm/pnpm/issues/11397).
- Fix recursive publish summaries to report the manifest from `publishConfig.directory` when packages publish from a generated directory [#​11239](https://github.com/pnpm/pnpm/issues/11239).
- Fix negated `os` / `cpu` entries (e.g. `["!win32"]`) being incorrectly rejected when `supportedArchitectures` expands to multiple platforms [#​11375](https://github.com/pnpm/pnpm/pull/11375).
### [`v11.0.1`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1101)
[Compare Source](https://github.com/pnpm/pnpm/compare/v11.0.0...v11.0.1)
##### Patch Changes
- Report unknown top-level options before falling back to implicit `pnpm run` scripts.
- Reject `null` named catalogs in workspace manifests with `InvalidWorkspaceManifestError` instead of crashing with a raw `TypeError`.
- Populate download location for git-sourced dependencies in SBOM output. Previously `pnpm sbom` emitted `NOASSERTION` (SPDX) and omitted the distribution reference (CycloneDX) for git dependencies. Now emits the git URL with commit hash, e.g. `git+https://github.com/user/repo.git#commit`.
- `pnpm self-update` now keeps `package.json`'s `packageManager` and `devEngines.packageManager` in sync. When the legacy `packageManager` field pins pnpm, both fields are rewritten to the new exact pnpm version on update — `packageManager` to `pnpm@<version>` (without an integrity hash), and `devEngines.packageManager.version` to the same exact `<version>` (dropping any range operator). When only `devEngines.packageManager` is declared, the existing range-preserving behavior is unchanged [#​11388](https://github.com/pnpm/pnpm/issues/11388).
- Sort the keys of the overrides object returned by `pnpm audit --fix` so that the log output order matches the order written to `pnpm-workspace.yaml`.
- Update the env lockfile's `packageManagerDependencies` entry when `devEngines.packageManager` declares a pnpm version that the lockfile no longer satisfies. Previously, the stale entry was kept even though the running pnpm matched the declared version, silently breaking the integrity record [#​11387](https://github.com/pnpm/pnpm/issues/11387).
### [`v11.0.0`](https://github.com/pnpm/pnpm/blob/HEAD/pnpm11/pnpm/CHANGELOG.md#1100)
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.34.4...v11.0.0)
##### Highlights
##### Major
- **Node.js 22+ required** — support for Node 18, 19, 20, and 21 is dropped, pnpm itself is now pure ESM, and the standalone exe requires glibc 2.27.
- **Supply-chain protection on by default** — `minimumReleaseAge` defaults to 1 day (newly published packages are not resolved for 24h) and `blockExoticSubdeps` defaults to `true`.
- **`allowBuilds` replaces the old build-dependency settings** — `onlyBuiltDependencies`, `onlyBuiltDependenciesFile`, `neverBuiltDependencies`, `ignoredBuiltDependencies`, and `ignoreDepScripts` have been removed.
- **Global installs are isolated and use the global virtual store by default** — each `pnpm add -g` gets its own directory with its own `package.json`, `node_modules`, and lockfile.
- **New SQLite-backed store index** (store v11) with bundled manifests and hex digests, reducing filesystem syscalls and speeding up installation.
- **Native publish flow** — [`pnpm publish`](https://pnpm.io/11.x/cli/publish), [`login`](https://pnpm.io/11.x/cli/login), [`logout`](https://pnpm.io/11.x/cli/logout), [`view`](https://pnpm.io/11.x/cli/view), [`deprecate`](https://pnpm.io/11.x/cli/deprecate), [`unpublish`](https://pnpm.io/11.x/cli/unpublish), [`dist-tag`](https://pnpm.io/11.x/cli/dist-tag), and [`version`](https://pnpm.io/11.x/cli/version) no longer delegate to the npm CLI, and the remaining npm passthrough commands now throw "not implemented".
- **[`pnpm audit`](https://pnpm.io/11.x/cli/audit) uses npm's bulk advisories endpoint** — the legacy `/security/audits` endpoints are gone. CVE-based filtering has been replaced with GHSA-based filtering: migrate `auditConfig.ignoreCves` entries to `auditConfig.ignoreGhsas`.
- **`.npmrc` is auth/registry only** — all other settings must live in `pnpm-workspace.yaml` or the new global `config.yaml`, and environment variables use the `pnpm_config_*` prefix.
- **Runtime installs are slimmer** — installing a Node.js runtime via `node@runtime:<version>` no longer extracts the bundled `npm`, `npx`, and `corepack`, roughly halving the files pnpm has to hash, write, and link.
##### Minor
- **New commands:** [`pnpm ci`](https://pnpm.io/11.x/cli/ci), [`pnpm sbom`](https://pnpm.io/11.x/cli/sbom), [`pnpm clean`](https://pnpm.io/11.x/cli/clean), [`pnpm peers check`](https://pnpm.io/11.x/cli/peers), [`pnpm runtime set`](https://pnpm.io/11.x/cli/runtime), [`pnpm docs`](https://pnpm.io/11.x/cli/docs)/`home`, [`pnpm ping`](https://pnpm.io/11.x/cli/ping), [`pnpm search`](https://pnpm.io/11.x/cli/search), [`pnpm star`](https://pnpm.io/11.x/cli/star)/`unstar`/`stars`, [`pnpm whoami`](https://pnpm.io/11.x/cli/whoami), [`pnpm with`](https://pnpm.io/11.x/cli/with), and [`pnpm pack-app`](https://pnpm.io/11.x/cli/pack-app), plus `pn`/[`pnx`](https://pnpm.io/11.x/cli/pnx) short aliases.
- **ESM pnpmfiles** via `.pnpmfile.mjs`, which takes priority over `.pnpmfile.cjs` when present.
- **[`pnpm audit --fix=update`](https://pnpm.io/11.x/cli/audit)** fixes vulnerabilities by updating packages in the lockfile instead of adding overrides, and `pnpm audit --fix --interactive` lets you select which advisories to fix.
- **[`pnpm pack-app`](https://pnpm.io/11.x/cli/pack-app)** packs a CommonJS entry into a standalone executable for one or more target platforms using Node.js Single Executable Applications.
- **Faster HTTP and I/O** — undici with Happy Eyeballs, direct-to-CAS writes, skipped staging directory, pre-allocated tarball downloads, and an NDJSON metadata cache.
##### Major Changes
##### Requirements
- pnpm is now distributed as pure ESM.
- Dropped support for Node.js v18, 19, 20, and 21.
- The standalone exe version of pnpm requires at least glibc 2.27.
##### Security & Build Defaults
- Changed default values: `optimisticRepeatInstall` is now `true`, `verifyDepsBeforeRun` is now `install`, `minimumReleaseAge` is now `1440` (1 day), and `minimumReleaseAgeStrict` is `false`. Newly published packages will not be resolved until they are at least 1 day old. This protects against supply chain attacks by giving the community time to detect and remove compromised versions. To opt out, set `minimumReleaseAge: 0` in `pnpm-workspace.yaml` [#​11158](https://github.com/pnpm/pnpm/pull/11158).
- `strictDepBuilds` is `true` by default.
- `blockExoticSubdeps` is `true` by default.
- Removed deprecated build dependency settings: `onlyBuiltDependencies`, `onlyBuiltDependenciesFile`, `neverBuiltDependencies`, `ignoredBuiltDependencies`, and `ignoreDepScripts` [#​11220](https://github.com/pnpm/pnpm/pull/11220).
Use the `allowBuilds` setting instead. It is a map where keys are package name patterns and values are booleans:
- `true` means the package is allowed to run build scripts
- `false` means the package is explicitly denied from running build scripts
Same as before, by default, none of the packages in the dependencies are allowed to run scripts. If a package has postinstall scripts and it isn't declared in `allowBuilds`, an error is printed.
Before:
```yaml
onlyBuiltDependencies:
- electron
onlyBuiltDependenciesFile: "allowed-builds.json"
neverBuiltDependencies:
- core-js
ignoredBuiltDependencies:
- esbuild
```
After:
```yaml
allowBuilds:
electron: true
core-js: false
esbuild: false
```
- Removed `allowNonAppliedPatches` in favor of `allowUnusedPatches`.
- Removed `ignorePatchFailures`; patch application failures now throw an error.
##### Store
- Runtime dependencies are always linked from the global virtual store [#​10233](https://github.com/pnpm/pnpm/pull/10233).
- Optimized index file format to store the hash algorithm once per file instead of repeating it for every file entry. Each file entry now stores only the hex digest instead of the full integrity string (`<algo>-<digest>`). Using hex format improves performance since file paths in the content-addressable store use hex representation, eliminating base64-to-hex conversion during path lookups.
- Store version bumped to v11.
- The bundled manifest (name, version, bin, engines, scripts, etc.) is now stored directly in the package index file, eliminating the need to read `package.json` from the content-addressable store during resolution and installation. This reduces I/O and speeds up repeat installs [#​10473](https://github.com/pnpm/pnpm/pull/10473).
- The package index in the content-addressable store is now backed by SQLite. Instead of individual JSON files under `$STORE/index/`, package metadata is stored in a single SQLite database at `$STORE/index.db` with MessagePack-encoded values. This reduces filesystem syscall overhead, improves space efficiency for small metadata entries, and enables concurrent access via SQLite's WAL mode. Packages missing from the new index are re-fetched on demand [#​10500](https://github.com/pnpm/pnpm/pull/10500) [#​10826](https://github.com/pnpm/pnpm/issues/10826).
##### Global Packages
- Global installs (`pnpm add -g pkg`) and `pnx` now use the global virtual store by default. Packages are stored at `{storeDir}/links` instead of per-project `.pnpm` directories. This can be disabled by setting `enableGlobalVirtualStore: false` [#​10694](https://github.com/pnpm/pnpm/pull/10694).
- Isolated global packages. Each globally installed package (or group of packages installed together) now gets its own isolated installation directory with its own `package.json`, `node_modules/`, and lockfile. This prevents global packages from interfering with each other through peer dependency conflicts, hoisting changes, or version resolution shifts.
Key changes:
- `pnpm add -g <pkg>` creates an isolated installation in `{pnpmHomeDir}/global/v11/{hash}/`
- `pnpm remove -g <pkg>` removes the entire installation group containing the package
- `pnpm update -g [pkg]` re-installs packages in new isolated directories
- `pnpm list -g` scans isolated directories to show all installed global packages
- `pnpm install -g` (no args) is no longer supported; use `pnpm add -g <pkg>` instead
- Globally installed binaries are now stored in a `bin` subdirectory of `PNPM_HOME` instead of directly in `PNPM_HOME`. This prevents internal directories like `global/` and `store/` from polluting shell autocompletion when `PNPM_HOME` is on PATH [#​10986](https://github.com/pnpm/pnpm/issues/10986). After upgrading, run `pnpm setup` to update your shell configuration.
- Breaking changes to `pnpm link`:
- `pnpm link <pkg-name>` no longer resolves packages from the global store. Only relative or absolute paths are accepted. For example, use `pnpm link ./foo` instead of `pnpm link foo`.
- `pnpm link --global` is removed. Use `pnpm add -g .` to register a local package's bins globally.
- `pnpm link` (no arguments) is removed. Use `pnpm link <dir>` with an explicit path instead.
##### Configuration
- pnpm no longer reads all settings from `.npmrc`. Only auth and registry settings are read from `.npmrc` files. All other settings (like `hoistPattern`, `nodeLinker`, `shamefullyHoist`, etc.) must be configured in `pnpm-workspace.yaml` or the global `~/.config/pnpm/config.yaml` [#​11189](https://github.com/pnpm/pnpm/pull/11189).
- Network settings (`httpProxy`, `httpsProxy`, `noProxy`, `localAddress`, `strictSsl`, `gitShallowHosts`) are now written to `config.yaml` (global) or `pnpm-workspace.yaml` (local) instead of `.npmrc`/`auth.ini`. They are still readable from `.npmrc` for easier migration from the npm CLI [#​11209](https://github.com/pnpm/pnpm/pull/11209).
pnpm no longer reads `npm_config_*` environment variables. Use `pnpm_config_*` environment variables instead (e.g., `pnpm_config_registry` instead of `npm_config_registry`).
pnpm no longer reads the npm global config at `$PREFIX/etc/npmrc`.
`pnpm login` writes auth tokens to `~/.config/pnpm/auth.ini`.
New `registries` setting in `pnpm-workspace.yaml`:
```yaml
registries:
default: https://registry.npmjs.org/
"@​my-org": https://private.example.com/
"@​internal": https://nexus.corp.com/
```
Auth tokens in `~/.npmrc` still work — pnpm continues to read `~/.npmrc` as a fallback for registry authentication. The new `npmrcAuthFile` setting can be used to point to a different file instead of `~/.npmrc`.
- Replace workspace project specific `.npmrc` with `packageConfigs` in `pnpm-workspace.yaml`.
A workspace manifest with `packageConfigs` looks something like this:
```yaml
# File: pnpm-workspace.yaml
packages:
- "packages/project-1"
- "packages/project-2"
packageConfigs:
"project-1":
saveExact: true
"project-2":
savePrefix: "~"
```
Or this:
```yaml
# File: pnpm-workspace.yaml
packages:
- "packages/project-1"
- "packages/project-2"
packageConfigs:
- match: ["project-1", "project-2"]
modulesDir: "node_modules"
saveExact: true
```
- pnpm no longer reads settings from the `pnpm` field of `package.json`. Settings should be defined in `pnpm-workspace.yaml` [#​10086](https://github.com/pnpm/pnpm/pull/10086).
- `pnpm config get` (without `--json`) no longer prints INI formatted text. Instead, it prints JSON for objects and arrays, and raw strings for strings, numbers, booleans, and nulls. `pnpm config get --json` still prints all types of values as JSON, as before.
- `pnpm config get <array>` now prints a JSON array.
- `pnpm config list` now prints a JSON object instead of INI formatted text.
- `pnpm config list` and `pnpm config get` (without argument) now hide auth-related settings.
- `pnpm config list` and `pnpm config get` (without argument) now show top-level keys as camelCase. Exception: keys that start with `@` or `//` are preserved (their cases don't change).
- `pnpm config get` and `pnpm config list` no longer load non-camelCase options from the workspace manifest (`pnpm-workspace.yaml`).
##### Removed Commands & npm Passthrough
- pnpm no longer falls back to the npm CLI. Commands that were previously passed through to npm (`access`, `bugs`, `docs`, `edit`, `find`, `home`, `issues`, `owner`, `ping`, `prefix`, `profile`, `pkg`, `repo`, `search`, `set-script`, `star`, `stars`, `team`, `token`, `unstar`, `whoami`, `xmas`) and their aliases (`s`, `se`) now throw a "not implemented" error, with a suggestion to use the npm CLI directly [#​10642](https://github.com/pnpm/pnpm/pull/10642). Other previously passed-through commands — [`view`](https://pnpm.io/11.x/cli/view) (`info`, `show`, `v`), [`login`](https://pnpm.io/11.x/cli/login) (`adduser`), [`logout`](https://pnpm.io/11.x/cli/logout), [`deprecate`](https://pnpm.io/11.x/cli/deprecate), [`unpublish`](https://pnpm.io/11.x/cli/unpublish), [`dist-tag`](https://pnpm.io/11.x/cli/dist-tag), and [`version`](https://pnpm.io/11.x/cli/version) — have been reimplemented natively in pnpm (see New Commands below).
- [`pnpm publish`](https://pnpm.io/11.x/cli/publish) now works without the `npm` CLI.
The One-time Password feature now reads from `PNPM_CONFIG_OTP` instead of `NPM_CONFIG_OTP`:
```sh
export PNPM_CONFIG_OTP='<your OTP here>'
pnpm publish --no-git-checks
```
If the registry requests OTP and the user has not provided it via the `PNPM_CONFIG_OTP` environment variable or the `--otp` flag, pnpm will prompt the user directly for an OTP code.
If the registry requests web-based authentication, pnpm will print a scannable QR code along with the URL.
Since the new `pnpm publish` no longer calls `npm publish`, some undocumented features may have been unknowingly dropped. If you rely on a feature that is now gone, please open an issue at <https://github.com/pnpm/pnpm/issues>. In the meantime, you can use `pnpm pack && npm publish *.tgz` as a workaround.
- Removed the `pnpm server` command [#​10463](https://github.com/pnpm/pnpm/pull/10463).
- Removed support for the `useNodeVersion` and `executionEnv.nodeVersion` fields. `devEngines.runtime` and `engines.runtime` should be used instead [#​10373](https://github.com/pnpm/pnpm/pull/10373).
- Removed support for `hooks.fetchers`. We now have a new API for custom fetchers and resolvers via the `fetchers` field of `pnpmfile`.
##### Lifecycle Scripts
- pnpm no longer populates `npm_config_*` environment variables from the pnpm config during lifecycle scripts. Only well-known `npm_*` env vars are now set, matching Yarn's behavior [#​11116](https://github.com/pnpm/pnpm/pull/11116).
##### CLI Output
- Cleaner output for script execution: pnpm now prints `$ command` instead of `> pkg@version stage path\n> command`, and shows project name and path only when running in a different directory. The `$ command` line is printed to stderr to keep stdout clean for piping [#​11132](https://github.com/pnpm/pnpm/pull/11132).
- During install, instead of rendering the full peer dependency issues tree, pnpm now suggests running [`pnpm peers check`](https://pnpm.io/11.x/cli/peers) to view the issues [#​11133](https://github.com/pnpm/pnpm/pull/11133).
##### Lockfile
- Simplified `patchedDependencies` lockfile format from `Record<string, { path: string, hash: string }>` to `Record<string, string>` (selector to hash). Existing lockfiles with the old format are automatically migrated [#​10911](https://github.com/pnpm/pnpm/pull/10911).
##### Other
- The default value of the `type` field in the `package.json` file of the project initialized by `pnpm init` command has been changed to `module`.
- Added support for lowercase options in `pnpm add`: `-d`, `-p`, `-o`, `-e` [#​9197](https://github.com/pnpm/pnpm/issues/9197).
When using the `pnpm add` command only:
- `-p` is now an alias for `--save-prod` instead of `--parseable`
- `-d` is now an alias for `--save-dev` instead of `--loglevel=info`
- The root workspace project is no longer excluded when it is explicitly selected via a filter [#​10465](https://github.com/pnpm/pnpm/pull/10465).
##### Audit
- [`pnpm audit`](https://pnpm.io/11.x/cli/audit) now calls npm's `/-/npm/v1/security/advisories/bulk` endpoint. The legacy `/-/npm/v1/security/audits{,/quick}` endpoints have been retired by the registry, so the legacy request/response contract is no longer supported.
The bulk endpoint does not return CVE identifiers. CVE-based filtering has been replaced with GitHub advisory ID (GHSA) filtering:
- `auditConfig.ignoreCves` → `auditConfig.ignoreGhsas` (the previous key is no longer recognized)
- `pnpm audit --ignore <id>` / `pnpm audit --ignore-unfixable` now read and write GHSAs instead of CVEs
- GHSAs are derived from each advisory's `url` (`https://github.com/advisories/GHSA-xxxx-xxxx-xxxx`)
To migrate: replace each `CVE-YYYY-NNNNN` entry in your `auditConfig.ignoreCves` with the corresponding `GHSA-xxxx-xxxx-xxxx` value (visible in the `More info` column of `pnpm audit` output) and move it under `auditConfig.ignoreGhsas`.
##### Package Manager Settings
- **Breaking:** removed the `managePackageManagerVersions`, `packageManagerStrict`, and `packageManagerStrictVersion` settings. They existed only to derive the `onFail` behavior for the legacy `packageManager` field, and the `pmOnFail` setting introduced alongside [`pnpm with`](https://pnpm.io/11.x/cli/with) subsumes all three — it directly sets the `onFail` behavior of both `packageManager` and `devEngines.packageManager`. The `COREPACK_ENABLE_STRICT` environment variable is no longer honored (it only gated `packageManagerStrict`); use `pmOnFail` instead.
Migration:
| Removed setting | Replace with |
| ------------------------------------- | ------------------------------ |
| `managePackageManagerVersions: true` | `pmOnFail: download` (default) |
| `managePackageManagerVersions: false` | `pmOnFail: ignore` |
| `packageManagerStrict: false` | `pmOnFail: warn` |
| `packageManagerStrictVersion: true` | `pmOnFail: error` |
| `COREPACK_ENABLE_STRICT=0` | `pmOnFail: warn` |
##### Runtime Installs
- Installing a Node.js runtime via `node@runtime:<version>` (including `pnpm env use` and `pnpm runtime set node`) no longer extracts the bundled `npm`, `npx`, and `corepack` from the Node.js archive. This cuts roughly half of the files pnpm has to hash, write to the CAS, and link during installation, making runtime installs noticeably faster. Users who still need `npm` can install it as a separate package.
##### Minor Changes
##### New Commands
- Added native [`pnpm view`](https://pnpm.io/11.x/cli/view) (`info`, `show`, `v`) command for viewing package metadata from the registry [#​11064](https://github.com/pnpm/pnpm/pull/11064).
- Added [`pnpm login`](https://pnpm.io/11.x/cli/login) (and `pnpm adduser` alias) command for authenticating with npm registries. Supports web-based login with QR code as well as classic username/password login [#​11094](https://github.com/pnpm/pnpm/pull/11094).
- Added [`pnpm logout`](https://pnpm.io/11.x/cli/logout) command for logging out of npm registries. Revokes the authentication token on the registry and removes it from the local auth config file [#​11213](https://github.com/pnpm/pnpm/pull/11213).
- Added native [`pnpm deprecate`](https://pnpm.io/11.x/cli/deprecate) and `pnpm undeprecate` commands for setting and removing deprecation messages on package versions without delegating to the npm CLI [#​11120](https://github.com/pnpm/pnpm/pull/11120).
- Added native [`pnpm unpublish`](https://pnpm.io/11.x/cli/unpublish) command. Supports unpublishing specific versions, version ranges via semver, and entire packages with `--force` [#​11128](https://github.com/pnpm/pnpm/pull/11128).
- Added native [`pnpm dist-tag`](https://pnpm.io/11.x/cli/dist-tag) command (`ls`, `add`, `rm` subcommands) [#​11218](https://github.com/pnpm/pnpm/pull/11218).
- Added [`pnpm sbom`](https://pnpm.io/11.x/cli/sbom) command for generating Software Bill of Materials in CycloneDX 1.7 and SPDX 2.3 JSON formats [#​9088](https://github.com/pnpm/pnpm/issues/9088).
- Added [`pnpm clean`](https://pnpm.io/11.x/cli/clean) command that safely removes `node_modules` directories from all workspace projects [#​10707](https://github.com/pnpm/pnpm/issues/10707). Use `--lockfile` to also remove `pnpm-lock.yaml` files.
- Added a new command [`pnpm runtime set <runtime name> <runtime version spec> [-g]`](https://pnpm.io/11.x/cli/runtime) for installing runtimes. Deprecated `pnpm env use` in favor of the new command.
- Added the ability to fix vulnerabilities by updating packages in the lockfile instead of adding overrides. Use [`pnpm audit --fix=update`](https://pnpm.io/11.x/cli/audit) [#​10341](https://github.com/pnpm/pnpm/pull/10341).
- Added [`pnpm ci`](https://pnpm.io/11.x/cli/ci) command for clean installs [#​6100](https://github.com/pnpm/pnpm/issues/6100). The command runs `pnpm clean` followed by `pnpm install --frozen-lockfile`. Designed for CI/CD environments where reproducible builds are critical. Aliases: `pnpm clean-install`, `pnpm ic`, `pnpm install-clean` [#​11003](https://github.com/pnpm/pnpm/pull/11003).
- Added [`pnpm peers check`](https://pnpm.io/11.x/cli/peers) command that checks for unmet and missing peer dependency issues by reading the lockfile [#​7087](https://github.com/pnpm/pnpm/issues/7087).
- Implemented the [`version`](https://pnpm.io/11.x/cli/version) command natively in pnpm to support workspaces and `workspace:` protocols correctly. The new command allows bumping package versions (major, minor, patch, etc.) with full workspace support and git integration [#​10879](https://github.com/pnpm/pnpm/pull/10879).
- [`pnpm audit --fix`](https://pnpm.io/11.x/cli/audit) now supports a new interactive mode via `--interactive`/`-i`.
- Added the [`pnpm docs`](https://pnpm.io/11.x/cli/docs) command and its alias `pnpm home`. This command opens the package documentation or homepage in the browser. When the package has no valid homepage, it falls back to `https://npmx.dev/package/<name>`.
- Added native [`pnpm ping`](https://pnpm.io/11.x/cli/ping) command to test registry connectivity. Provides a simple way to verify connectivity to the configured registry without requiring external tools.
- Implemented native [`search`](https://pnpm.io/11.x/cli/search) command and its aliases (`s`, `se`, `find`).
- Implemented native [`star`, `unstar`, `stars`](https://pnpm.io/11.x/cli/star), and [`whoami`](https://pnpm.io/11.x/cli/whoami) commands.
- Add [`pnpm with <version|current> <args...>`](https://pnpm.io/11.x/cli/with) command. Runs pnpm at a specific version (or the currently active one) for a single invocation, bypassing the project's `packageManager` and `devEngines.packageManager` pins.
- Added a new [`pnpm pack-app`](https://pnpm.io/11.x/cli/pack-app) command that packs a CommonJS entry file into a standalone executable for one or more target platforms, using the [Node.js Single Executable Applications](https://nodejs.org/api/single-executable-applications.html) API under the hood.
##### Configuration
- Added support for a global YAML config file named `config.yaml`.
Configuration is now split into two categories:
- Registry and auth settings, which can be stored in INI files such as the global `rc` file and local `.npmrc`.
- pnpm-specific settings, which can only be loaded from YAML files such as the global `config.yaml` and local `pnpm-workspace.yaml`.
- Added support for loading environment variables whose names start with `pnpm_config_` into config. These environment variables override settings from `pnpm-workspace.yaml` but not CLI arguments.
- Added support for reading `allowBuilds` from `pnpm-workspace.yaml` in the global package directory for global installs.
- Added support for `pnpm config get globalconfig` to retrieve the global config file path [#​9977](https://github.com/pnpm/pnpm/issues/9977).
- Added a new setting `virtualStoreOnly` that populates the virtual store without creating importer symlinks, hoisting, bin links, or running lifecycle scripts. This is useful for pre-populating a store (e.g., in Nix builds) without creating unnecessary project-level artifacts. `pnpm fetch` now uses this mode internally [#​10840](https://github.com/pnpm/pnpm/issues/10840).
- Added support for specifying the pnpm version via `devEngines.packageManager` in `package.json`. Unlike the `packageManager` field, this supports version ranges. The resolved version is stored in `pnpm-lock.yaml` and reused if it still satisfies the range [#​10932](https://github.com/pnpm/pnpm/pull/10932).
- Added a new `dedupePeers` setting that reduces peer dependency duplication. When enabled, peer dependency suffixes use version-only identifiers (`name@version`) instead of full dep paths, eliminating nested suffixes like `(foo@1.0.0(bar@2.0.0))`. This dramatically reduces the number of package instances in projects with many recursive peer dependencies [#​11070](https://github.com/pnpm/pnpm/issues/11070).
- Config dependencies are now installed into the global virtual store (`{storeDir}/links/`) and symlinked into `node_modules/.pnpm-config/`. This allows config dependencies to be shared across projects that use the same store, avoiding redundant fetches and imports [#​10910](https://github.com/pnpm/pnpm/pull/10910). Config dependency and package manager integrity info is now stored in `pnpm-lock.yaml` instead of inlined in `pnpm-workspace.yaml`: the workspace manifest contains only clean version specifiers for `configDependencies`, while the resolved versions, integrity hashes, and tarball URLs are recorded in the lockfile as a separate YAML document. The env lockfile section also stores `packageManagerDependencies` resolved during version switching and self-update. Projects using the old inline-hash format are automatically migrated on install [#​10912](https://github.com/pnpm/pnpm/pull/10912) [#​10964](https://github.com/pnpm/pnpm/pull/10964).
- Added `nodeDownloadMirrors` setting to configure custom Node.js download mirrors in `pnpm-workspace.yaml`. This replaces the `node-mirror:<channel>` `.npmrc` setting, which is no longer read [#​11194](https://github.com/pnpm/pnpm/pull/11194):
```yaml
nodeDownloadMirrors:
release: https://my-mirror.example.com/download/release/
```
- `pnpm dlx` and `pnpm create` now respect security and trust policy settings (`minimumReleaseAge`, `minimumReleaseAgeExclude`, `minimumReleaseAgeStrict`, `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter`) from project-level configuration [#​11183](https://github.com/pnpm/pnpm/issues/11183).
- `pnpm init` now writes a `devEngines.packageManager` field instead of the `packageManager` field when `init-package-manager` is enabled.
- Added a new setting `runtimeOnFail` that overrides the `onFail` field of `devEngines.runtime` (and `engines.runtime`) in the root project's `package.json`. Accepted values: `ignore`, `warn`, `error`, `download`. For example, setting `runtimeOnFail=download` makes pnpm download the declared runtime version even when the manifest does not set `onFail: "download"`.
- Added a new setting `minimumReleaseAgeIgnoreMissingTime`, which is `true` by default. When enabled, pnpm skips the `minimumReleaseAge` maturity check if the registry metadata does not include the `time` field. Set to `false` to fail resolution instead.
##### Store
- When the global virtual store is enabled, packages that are not allowed to build (and don't transitively depend on packages that are) now get hashes that don't include the engine name (platform, architecture, Node.js major version). This means \~95% of packages in the GVS survive Node.js upgrades and architecture changes without re-import [#​10837](https://github.com/pnpm/pnpm/issues/10837).
##### Hooks & Pnpmfiles
- Added support for pnpmfiles written in ESM, using the `.mjs` extension. When `.pnpmfile.mjs` exists, it takes priority over `.pnpmfile.cjs` and only one is loaded [#​9730](https://github.com/pnpm/pnpm/pull/9730).
##### CLI & Other
- The built-in `clean`, `setup`, `deploy`, and `rebuild` commands now prefer user scripts over built-in commands. When a project's `package.json` has a script with the same name, `pnpm` executes the script instead of the built-in command. Added `purge` as an alias for the built-in `clean` command, which always runs the built-in regardless of scripts [#​11118](https://github.com/pnpm/pnpm/pull/11118).
- Added `-F` as a short alias for the `--filter` option.
- Added support for hidden scripts. Scripts starting with `.` are hidden and cannot be run directly via `pnpm run`. They can only be called from other scripts. Hidden scripts are also omitted from the `pnpm run` listing [#​11041](https://github.com/pnpm/pnpm/pull/11041).
- `pnpm approve-builds` now accepts positional arguments for approving or denying packages without the interactive prompt. Prefix a package name with `!` to deny it. Only mentioned packages are affected; the rest are left untouched [#​11030](https://github.com/pnpm/pnpm/pull/11030).
- During install, packages with ignored builds that are not yet listed in `allowBuilds` are automatically added to `pnpm-workspace.yaml` with a placeholder value, so users can manually set them to `true` or `false` [#​11030](https://github.com/pnpm/pnpm/pull/11030).
- Added `pn` and `pnx` short aliases for `pnpm` and `pnpx` (`pnpm dlx`) [#​11052](https://github.com/pnpm/pnpm/pull/11052).
- `pnpm store prune` now displays the total size of removed files [#​11047](https://github.com/pnpm/pnpm/pull/11047).
- `pnpm audit --fix` now adds the minimum patched version for each advisory to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml`, so the security fix can be installed without waiting for `minimumReleaseAge` [#​11216](https://github.com/pnpm/pnpm/pull/11216).
- pnpm now warns when `optimisticRepeatInstall` skips `shouldRefreshResolution` hooks [#​10995](https://github.com/pnpm/pnpm/pull/10995).
##### Performance
- Replaced `node-fetch` with native `undici` for HTTP requests throughout pnpm [#​10537](https://github.com/pnpm/pnpm/pull/10537).
- Eliminated redundant internal linking during GVS warm reinstall when no packages were added [#​11073](https://github.com/pnpm/pnpm/pull/11073).
- Eliminated the staging directory when importing packages into `node_modules`, avoiding the overhead of creating a temp dir and renaming per package [#​11088](https://github.com/pnpm/pnpm/pull/11088).
- CAS files are now written directly to their final content-addressed path instead of to a temp file and renamed. This eliminates \~30k rename syscalls per cold install [#​11087](https://github.com/pnpm/pnpm/pull/11087).
- Optimized hot-path string operations in the content-addressable store and increased `gunzipSync` chunk size for fewer buffer allocations during tarball decompression [#​11086](https://github.com/pnpm/pnpm/pull/11086).
- Improved HTTP performance with Happy Eyeballs (dual-stack), better keep-alive settings, and an optimized global dispatcher. Tarball downloads with known size now pre-allocate memory to avoid double-copy overhead [#​11151](https://github.com/pnpm/pnpm/pull/11151).
- Adopted `If-Modified-Since` for conditional metadata fetches, avoiding re-downloading unchanged registry metadata [#​11161](https://github.com/pnpm/pnpm/pull/11161).
- Switched to abbreviated metadata when checking `minimumReleaseAge`, reducing the amount of data fetched from the registry [#​11160](https://github.com/pnpm/pnpm/pull/11160).
- Switched the metadata cache to NDJSON format, improving read/write performance [#​11188](https://github.com/pnpm/pnpm/pull/11188).
##### Patch Changes
- Switched to `process.stderr.write` instead of `console.error` for script logging [#​11140](https://github.com/pnpm/pnpm/pull/11140).
- Respected the `frozen-lockfile` flag when migrating config dependencies [#​11067](https://github.com/pnpm/pnpm/pull/11067).
- Removed the `--workspace` flag from the `version` command [#​11115](https://github.com/pnpm/pnpm/pull/11115).
- Handled `ENOTSUP` error in the clone import path during parallel I/O [#​11117](https://github.com/pnpm/pnpm/pull/11117).
- Fixed `pnpm audit` command.
- Updated dependencies to fix vulnerabilities.
- pnpm now checks whether a package is installable for non-npm-hosted packages (e.g., git or tarball dependencies) after the manifest has been fetched.
- pnpm now explicitly passes the path of the global `rc` config file to `npm`.
- Fixed YAML formatting preservation in `pnpm-workspace.yaml` when running commands like `pnpm update`. Previously, quotes and other formatting were lost even when catalog values didn't change.
Closes [#​10425](https://github.com/pnpm/pnpm/issues/10425)
- The parameter set by the `--allow-build` flag is now written to `allowBuilds`.
- Fixed a bug in which specifying `filter` in `pnpm-workspace.yaml` would cause pnpm to not detect any projects.
- Deferred patch errors until all patches in a group are applied, so that one failed patch does not prevent other patches from being attempted.
- pnpm now fails on incompatible lockfiles in CI when frozen lockfile mode is enabled [#​10978](https://github.com/pnpm/pnpm/pull/10978).
- Fixed `strictDepBuilds` and `allowBuilds` checks being bypassed when a package's build side-effects are cached in the store [#​11039](https://github.com/pnpm/pnpm/pull/11039).
- In GVS mode, `pnpm approve-builds` now runs a full install instead of rebuild, ensuring that GVS hash directories and symlinks are updated correctly after changing `allowBuilds` [#​11043](https://github.com/pnpm/pnpm/pull/11043).
- Fixed a crash in the lockfile merger when merging non-semver version strings (e.g. `link:`, `file:`, git URLs) [#​11102](https://github.com/pnpm/pnpm/pull/11102).
- Handled `ENOTSUP` error in `linkOrCopy` during parallel imports [#​11103](https://github.com/pnpm/pnpm/pull/11103).
- Skipped linking bins that already reference the correct target. This avoids redundant I/O during repeated installs and prevents permission errors when the store is read-only (e.g. Docker layer caching, CI prewarm, NFS) [#​11069](https://github.com/pnpm/pnpm/pull/11069).
- Fixed `_password` handling for the default registry to decode from base64 before use, consistent with scoped registry behavior [#​11089](https://github.com/pnpm/pnpm/pull/11089).
- Fixed a bug where the CAS locker cache was not updated when a file already existed with correct integrity [#​11085](https://github.com/pnpm/pnpm/pull/11085).
- Prevented catalog entries from being removed by `cleanupUnusedCatalogs` when they are referenced only from workspace `overrides` [#​11075](https://github.com/pnpm/pnpm/pull/11075).
- Resolved patch file paths during `pnpm fetch` [#​11054](https://github.com/pnpm/pnpm/pull/11054).
- Fixed invalid specifiers for peers on all non-exact version selectors [#​11049](https://github.com/pnpm/pnpm/pull/11049).
- Fixed false "Command not found" error on Windows when the command exists but exits with a non-zero exit code [#​11000](https://github.com/pnpm/pnpm/issues/11000).
- Prepended `Bearer` to the authorization token generated by `tokenHelper` if it is missing, aligning with npm's behavior [#​11097](https://github.com/pnpm/pnpm/pull/11097).
- Propagated error cause when throwing `PnpmError` in `@pnpm/npm-resolver` [#​10990](https://github.com/pnpm/pnpm/pull/10990).
- Fixed SQLite race condition during store initialization on Windows.
- Removed `rimrafSync` in `importIndexedDir` fast-path error handler [#​11168](https://github.com/pnpm/pnpm/pull/11168).
- Fixed `pnpm dedupe --check` unexpectedly failing due to non-deterministic resolution [#​11110](https://github.com/pnpm/pnpm/pull/11110).
- Fixed empty files not being rejected in `isEmptyDirOrNothing` [#​11182](https://github.com/pnpm/pnpm/pull/11182).
- Fixed `.bat`/`.cmd` token helpers not working on Windows due to missing `shell: true` option.
### [`v10.34.4`](https://github.com/pnpm/pnpm/releases/tag/v10.34.4): pnpm 10.34.4
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.34.3...v10.34.4)
#### Patch Changes
- [`352ae48`](https://github.com/pnpm/pnpm/commit/352ae48): Security: validate config dependency names and versions before using them to build filesystem paths. A `pnpm-workspace.yaml` with a traversal-shaped `configDependencies` name (such as `../../PWNED`) or version (such as `../../../PWNED`) could previously cause `pnpm install` to create symlinks or write package files outside `node_modules/.pnpm-config` and the store. Names must now be valid npm package names and versions must be exact semver versions. See [GHSA-qrv3-253h-g69c](https://github.com/pnpm/pnpm/security/advisories/GHSA-qrv3-253h-g69c).
- [`352ae48`](https://github.com/pnpm/pnpm/commit/352ae48): Reject path-traversal and reserved dependency aliases (such as `../../../escape`, `.bin`, `.pnpm`, or `node_modules`) that come from a lockfile rather than a freshly resolved manifest. A crafted lockfile alias could otherwise be joined directly under a hoisted `node_modules` directory, letting package files be written outside the intended install root or overwrite pnpm-owned layout.
The `nodeLinker: hoisted` graph builder now validates each alias at the directory sink (`safeJoinModulesDir`), matching the validation pnpm already performs when resolving aliases from manifests. See [GHSA-fr4h-3cph-29xv](https://github.com/pnpm/pnpm/security/advisories/GHSA-fr4h-3cph-29xv).
- [`352ae48`](https://github.com/pnpm/pnpm/commit/352ae48): Prevent `pnpm patch-remove` from removing files outside the configured patches directory.
- [`217fbe0`](https://github.com/pnpm/pnpm/commit/217fbe0): Hardened the warning printed when a project `.npmrc` uses environment variables in registry/auth settings: the suggested `pnpm config set` command is now only included for keys made up of shell-inert characters. Because the key comes from a repository-controlled `.npmrc` and a shell expands `$(...)`, backticks, and `$VAR` even inside double quotes, a crafted key could otherwise have turned the suggested copy-paste command into command execution.
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.34.3`](https://github.com/pnpm/pnpm/releases/tag/v10.34.3): pnpm 10.34.3
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.34.2...v10.34.3)
##### ⚠️ Security fix — environment variables in a project `.npmrc` (action may be required)
Following [GHSA-3qhv-2rgh-x77r](https://github.com/pnpm/pnpm/security/advisories/GHSA-3qhv-2rgh-x77r), pnpm no longer expands `${ENV_VAR}` placeholders that come from a **repository-controlled** config file, because a malicious repository could otherwise use them to leak your environment secrets (npm tokens, CI job tokens, etc.) to an attacker-controlled registry during install. This applies to:
- the project/workspace `.npmrc` — `registry`, `@scope:registry`, proxy URLs, URL-scoped keys (`//host/…`), and credential values (`_authToken`, `_auth`, `_password`, `username`, `tokenHelper`, `cert`, `key`);
- registry URLs in `pnpm-workspace.yaml`.
This release also closes a bypass where a project `.npmrc` could set `userconfig`, `globalconfig`, or `prefix` to make pnpm load a repo-supplied file as *trusted* config (via `@pnpm/npm-conf@3.0.3`).
Environment variables are **still** expanded in trusted config: your user-level `~/.npmrc`, the global config, CLI options, and environment config.
**If your authentication broke after upgrading**, move the token out of the committed `.npmrc`:
```sh
# Writes to your user/global config, not the repository:
pnpm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN"
```
Or keep the `${NPM_TOKEN}` line but put it in your user-level `~/.npmrc` instead of the repo. In **GitHub Actions**, `actions/setup-node` with `registry-url` already writes a user-level `.npmrc`, so `NODE_AUTH_TOKEN` keeps working. For other CI where editing each pipeline is hard, set `NPM_CONFIG_USERCONFIG=.npmrc` in the CI environment to declare the project `.npmrc` trusted.
See <https://pnpm.io/npmrc> for full migration details.
#### Patch Changes
- Improved the warning printed when a project `.npmrc` uses an environment variable in a registry/proxy URL or in registry credentials. The message now explains why the setting was ignored and how to migrate it to a trusted source — for example by running `pnpm config set "<key>" <value>` to store it in the global config, or by keeping the `${...}` line in the user-level `~/.npmrc` — with a link to <https://pnpm.io/npmrc>.
- A repository-controlled project or workspace `.npmrc` can no longer redirect which files pnpm loads as its trusted user and global configuration. Previously such a file could set `userconfig`, `globalconfig`, or `prefix` to point at an attacker-supplied file shipped in the repository, and pnpm would load it as a trusted config source — bypassing the protection that prevents repository config from expanding environment variables into registry request destinations and credentials, and allowing it to set `tokenHelper`. The user/global config file locations are now resolved only from trusted sources (CLI options, environment config, the npm builtin config, and defaults) before the project and workspace `.npmrc` files are read. Fixed by upgrading `@pnpm/npm-conf` to `3.0.3`.
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.34.2`](https://github.com/pnpm/pnpm/releases/tag/v10.34.2): pnpm 10.34.2
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.34.1...v10.34.2)
##### ⚠️ Security fix — environment variables in a project `.npmrc` (action may be required)
Following [GHSA-3qhv-2rgh-x77r](https://github.com/pnpm/pnpm/security/advisories/GHSA-3qhv-2rgh-x77r), pnpm no longer expands `${ENV_VAR}` placeholders that come from a **repository-controlled** config file, because a malicious repository could otherwise use them to leak your environment secrets (npm tokens, CI job tokens, etc.) to an attacker-controlled registry during install. This applies to:
- the project/workspace `.npmrc` — `registry`, `@scope:registry`, proxy URLs, URL-scoped keys (`//host/…`), and credential values (`_authToken`, `_auth`, `_password`, `username`, `tokenHelper`, `cert`, `key`);
- registry URLs in `pnpm-workspace.yaml`.
This release also closes a bypass where a project `.npmrc` could set `userconfig`, `globalconfig`, or `prefix` to make pnpm load a repo-supplied file as *trusted* config (via `@pnpm/npm-conf@3.0.3`).
Environment variables are **still** expanded in trusted config: your user-level `~/.npmrc`, the global config, CLI options, and environment config.
**If your authentication broke after upgrading**, move the token out of the committed `.npmrc`:
```sh
# Writes to your user/global config, not the repository:
pnpm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN"
```
Or keep the `${NPM_TOKEN}` line but put it in your user-level `~/.npmrc` instead of the repo. In **GitHub Actions**, `actions/setup-node` with `registry-url` already writes a user-level `.npmrc`, so `NODE_AUTH_TOKEN` keeps working. For other CI where editing each pipeline is hard, set `NPM_CONFIG_USERCONFIG=.npmrc` in the CI environment to declare the project `.npmrc` trusted.
See <https://pnpm.io/npmrc> for full migration details.
#### Patch Changes
- Package-manager bootstrap traffic is now resolved through trusted registries and trusted network config. When pnpm downloads the pnpm version requested by a repository's `packageManager` field, the registry it fetches from (and the proxy/TLS settings used for that traffic) now come exclusively from trusted config sources — CLI options, env config, user and global `.npmrc` — defaulting to the public npm registry, instead of the repository's project/workspace settings.
- pnpm now verifies the npm registry signature of a package-manager binary before spawning it. When the `packageManager` field (or `pnpm self-update`) makes pnpm download another pnpm version, the staged install is verified corepack-style: the integrity recorded in the staged lockfile must carry a valid npm registry signature for the exact `name@version`, validated against npm's public signing keys that ship embedded in the pnpm CLI. Verification fails closed — a tampered download, an unsigned package, or an unreachable registry refuses the version switch rather than running an unverified binary. It runs only when the wanted version is actually downloaded (a tools-directory cache miss), so repeated commands pay no extra network round trip.
- Environment variable expansion is now trust-aware for registry/auth config and request destinations. Repository-controlled config files (the project and workspace `.npmrc` and `pnpm-workspace.yaml`) can no longer expand `${...}` placeholders in registry/proxy request destinations, URL-scoped keys, or registry credential values, preventing repository-controlled configuration from exfiltrating environment secrets through request URLs. Trusted user/global/CLI/env config keeps full env expansion, so existing token and registry setup flows continue to work.
- Reject reserved manifest `bin` names (`""`, `"."`, `".."`, and scoped forms such as `@scope/..`) when resolving a package's bins. These names previously passed the bin-name guard and, when joined to the global bin directory during global remove/update/add operations, could resolve to the global bin directory itself or its parent and have it recursively deleted.
- Require trusted package identity before package-name `onlyBuiltDependencies` (and `allowBuilds`) entries can approve lifecycle scripts for git, git-hosted tarball, direct tarball, and local directory artifacts. To approve one of those artifacts explicitly, use its peer-suffix-free lockfile depPath as the key. Lockfile entries are now rejected when a registry-style dependency path (`name@semver`) is backed by a git, directory, or git-hosted tarball resolution (`ERR_PNPM_RESOLUTION_SHAPE_MISMATCH`), so the dependency path is a reliable artifact identity by the time scripts can run.
- pnpm now verifies the detached OpenPGP signature of a Node.js release's `SHASUMS256.txt` against the Node.js release team's public keys (embedded in the pnpm CLI) before trusting its hashes. The Node.js download mirror is repository-configurable (`node-mirror:<channel>` in `.npmrc`), and the integrity check previously trusted a `SHASUMS256.txt` fetched from that same mirror — a circular check that a malicious mirror could satisfy with a tampered binary and matching hashes. A mirror that proxies the real signed SHASUMS keeps working unchanged. Only the `release` channel publishes signed SHASUMS files, so pre-release channels (rc, nightly, …) remain unverified.
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.34.1`](https://github.com/pnpm/pnpm/releases/tag/v10.34.1): pnpm 10.34.1
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.34.0...v10.34.1)
#### Patch Changes
- Reject `pnpm-lock.yaml` entries whose remote tarball `resolution:` block is missing the `integrity` field. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that strips `integrity:`) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under `--frozen-lockfile`. pnpm now fails closed at lockfile-read time with `ERR_PNPM_MISSING_TARBALL_INTEGRITY`. Git-hosted tarballs (`gitHosted: true` or a URL on codeload.github.com / bitbucket.org / gitlab.com) and `file:` tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes.
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.34.0`](https://github.com/pnpm/pnpm/releases/tag/v10.34.0): pnpm 10.34
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.4...v10.34.0)
#### Minor Changes
- Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously, `pnpm install` (non-frozen) would log `ERR_PNPM_TARBALL_INTEGRITY`, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile.
`pnpm install` now exits with `ERR_PNPM_TARBALL_INTEGRITY` and a hint pointing at the new opt-in flag.
The only opt-in is **`pnpm install --update-checksums`** — narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable.
`--force` and `pnpm update` deliberately do **not** bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. `--frozen-lockfile` behavior is unchanged. `--fix-lockfile` keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass.
#### Patch Changes
- Pin unscoped per-registry settings (`_authToken`, `_auth`, `username`/`_password`, `tokenHelper`, inline `cert`/`key`) to the registry declared in the same config source at load time, so a later layer overriding `registry=` (workspace `.npmrc`, `pnpm-workspace.yaml`, CLI `--registry`) cannot redirect a credential or client certificate authored for a different host. A deprecation warning is emitted whenever an unscoped per-registry setting is encountered, naming the source and the URL it was pinned to. Reported by JUNYI LIU.
- Fixed `minimumReleaseAge` handling when cached metadata is abbreviated. The npm registry returns abbreviated package metadata (without the per-version `time` field) by default, which made the maturity check throw `ERR_PNPM_MISSING_TIME` whenever cached abbreviated metadata was reused. pnpm now upgrades cached abbreviated metadata to the full document via a follow-up fetch when `minimumReleaseAge` is active, persists the upgrade to the on-disk cache so subsequent installs skip the extra fetch, and lets `ERR_PNPM_MISSING_TIME` from the cache fast-path fall through to the network fetch even under strict mode.
- Reject git resolutions whose `commit` field is not a 40-character hexadecimal SHA before invoking `git`. A malicious lockfile could otherwise smuggle a value such as `--upload-pack=<command>` through `git fetch` / `git checkout`, which on SSH or local-file transports executes the supplied command.
- Reject patch files whose `diff --git` headers reference paths outside the patched package directory. Previously a malicious `.patch` file added via a pull request could write, delete, or rename arbitrary files reachable by the user running `pnpm install`.
- Fixed `--prefix=<dir>` not being honored when locating the workspace root. The `--prefix → dir` rename was applied after workspace detection, so workspace settings declared in `<dir>/pnpm-workspace.yaml` were not loaded when pnpm was invoked from outside `<dir>` [#​11535](https://github.com/pnpm/pnpm/issues/11535).
- Reject dependency aliases that contain path-traversal segments (such as `@x/../../../../../.git/hooks`) when reading them from a package manifest or symlinking them into `node_modules`. A malicious registry package could otherwise use a transitive dependency key to make `pnpm install` create symlinks at attacker-chosen paths outside the intended `node_modules` directory.
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.33.4`](https://github.com/pnpm/pnpm/releases/tag/v10.33.4): pnpm 10.33.4
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.3...v10.33.4)
#### Patch Changes
- Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.
A new `gitHosted: true` field is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.
- Fix a regression where `pnpm --recursive --filter '!<pkg>' run/exec/test/add` would include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative `--filter` arguments are provided, matching the [documented behavior](https://pnpm.io/cli/recursive). To include the root, pass `--include-workspace-root` [#​11341](https://github.com/pnpm/pnpm/issues/11341).
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.33.3`](https://github.com/pnpm/pnpm/releases/tag/v10.33.3): pnpm 10.33.3
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.2...v10.33.3)
#### Patch Changes
- When self-updating from v10's `@pnpm/exe` to v11+ on Intel macOS (darwin-x64), `pnpm self-update` now transparently switches to the JS-only `pnpm` package on npm instead of installing `@pnpm/exe@v11+` (which doesn't ship a working binary for Intel Macs because of an upstream Node.js SEA bug — see [#​11423](https://github.com/pnpm/pnpm/issues/11423) and [nodejs/node#62893](https://github.com/nodejs/node/issues/62893)). Without this, the self-update would silently leave the user with no working `pnpm` binary. The new install requires Node.js to be available on `PATH`; a warning is printed when the swap happens. All other host/version combinations are unchanged.
- `pnpm self-update` (with no version argument) no longer downgrades pnpm when the registry's `latest` dist-tag points to an older release than the currently active version. Run `pnpm self-update latest` to force a downgrade [#​11418](https://github.com/pnpm/pnpm/issues/11418).
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.33.2`](https://github.com/pnpm/pnpm/releases/tag/v10.33.2): pnpm 10.33.2
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.1...v10.33.2)
#### Patch Changes
- Globally-installed bins no longer fail with `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` when pnpm was installed via the standalone `@pnpm/exe` binary (e.g. `curl -fsSL https://get.pnpm.io/install.sh | sh -`) on a system without a separate Node.js installation. Previously, when `which('node')` failed during `pnpm add --global`, pnpm fell back to `process.execPath`, which in `@pnpm/exe` is the pnpm binary itself — and that path was baked into the generated bin shim, causing the shim to invoke pnpm instead of Node [#​11291](https://github.com/pnpm/pnpm/issues/11291), [#​4645](https://github.com/pnpm/pnpm/issues/4645).
- Fix an infinite fork-bomb that could happen when pnpm was installed with one version (e.g. `npm install -g pnpm@A`) and run inside a project whose `package.json` selected a different pnpm version via the `packageManager` field (e.g. `pnpm@B`), while a `pnpm-workspace.yaml` also existed at the project root.
The child's environment is now forced to `manage-package-manager-versions=false` (v10) and `pm-on-fail=ignore` (v11+), which disables the package-manager-version handling in whichever pnpm runs as the child.
Fixes [#​11337](https://github.com/pnpm/pnpm/issues/11337).
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
### [`v10.33.1`](https://github.com/pnpm/pnpm/releases/tag/v10.33.1): pnpm 10.33.1
[Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.0...v10.33.1)
#### Patch Changes
- When a project's `packageManager` field selects pnpm v11 or newer, commands that v10 would have passed through to npm (`version`, `login`, `logout`, `publish`, `unpublish`, `deprecate`, `dist-tag`, `docs`, `ping`, `search`, `star`, `stars`, `unstar`, `whoami`, etc.) are now handed over to the wanted pnpm, which implements them natively. Previously they silently shelled out to npm — making, for example, `pnpm version --help` print npm's help on a project with `packageManager: pnpm@11.0.0-rc.3` [#​11328](https://github.com/pnpm/pnpm/issues/11328).
<!-- sponsors -->
#### Platinum Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
</td>
</tr>
</tbody>
</table>
#### Gold Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
</picture>
</a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
</picture>
</a>
</td>
<td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
</picture>
</a>
</td>
</tr>
</tbody>
</table>
<!-- sponsors end -->
</details>
<details>
<summary>microsoft/TypeScript (typescript)</summary>
### [`v6.0.3`](https://github.com/microsoft/TypeScript/releases/tag/v6.0.3): TypeScript 6.0.3
[Compare Source](https://github.com/microsoft/TypeScript/compare/v6.0.2...v6.0.3)
For release notes, check out the [release announcement blog post](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/).
- [fixed issues query for TypeScript 6.0.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.0%22).
- [fixed issues query for TypeScript 6.0.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.1%22).
- [fixed issues query for TypeScript 6.0.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.2%22).
- [fixed issues query for TypeScript 6.0.3 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.3%22).
Downloads are available on:
- [npm](https://www.npmjs.com/package/typescript)
### [`v6.0.2`](https://github.com/microsoft/TypeScript/releases/tag/v6.0.2): TypeScript 6.0
[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.2)
For release notes, check out the [release announcement blog post](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/).
- [fixed issues query for TypeScript 6.0.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.0%22).
- [fixed issues query for TypeScript 6.0.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.1%22).
- [fixed issues query for TypeScript 6.0.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.2%22).
Downloads are available on:
- [npm](https://www.npmjs.com/package/typescript)
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS43IiwidXBkYXRlZEluVmVyIjoiNDMuMjUyLjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
This PR contains the following updates:
^25.0.0→^26.0.0^5.0.0→^6.0.0v6→v725→26v6→v7v3→v4v5→v6v3→v4v3→v4^28.0.0→^29.0.010.33.0→11.10.0^5.7.3→^6.0.0Release Notes
vitejs/vite-plugin-react (@vitejs/plugin-react)
v6.0.3Compare Source
v6.0.2Compare Source
Allow all options in reactCompilerPreset (#1189)
This is a type only change. Only
compilationModeandtargetoptions were available forreactCompilerPreset.v6.0.1Compare Source
Expand
@rolldown/plugin-babelpeer dep range (#1146)Expanded
@rolldown/plugin-babelpeer dep range to include^0.2.0.v6.0.0Compare Source
actions/checkout (actions/checkout)
v7.0.0Compare Source
v7Compare Source
docker/build-push-action (docker/build-push-action)
v7.3.0Compare Source
Full Changelog: https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0
v7.2.0Compare Source
Full Changelog: https://github.com/docker/build-push-action/compare/v7.1.0...v7.2.0
v7.1.0Compare Source
Full Changelog: https://github.com/docker/build-push-action/compare/v7.0.0...v7.1.0
v7.0.0Compare Source
DOCKER_BUILD_NO_SUMMARYandDOCKER_BUILD_EXPORT_RETENTION_DAYSenvs by @crazy-max in #1473Full Changelog: https://github.com/docker/build-push-action/compare/v6.19.2...v7.0.0
v7Compare Source
docker/login-action (docker/login-action)
v4.4.0Compare Source
registry-authsecret mask by @crazy-max in #1035Full Changelog: https://github.com/docker/login-action/compare/v4.3.0...v4.4.0
v4.3.0Compare Source
Full Changelog: https://github.com/docker/login-action/compare/v4.2.0...v4.3.0
v4.2.0Compare Source
Full Changelog: https://github.com/docker/login-action/compare/v4.1.0...v4.2.0
v4.1.0Compare Source
Full Changelog: https://github.com/docker/login-action/compare/v4.0.0...v4.1.0
v4.0.0Compare Source
Full Changelog: https://github.com/docker/login-action/compare/v3.7.0...v4.0.0
v4Compare Source
docker/metadata-action (docker/metadata-action)
v6.2.0Compare Source
Full Changelog: https://github.com/docker/metadata-action/compare/v6.1.0...v6.2.0
v6.1.0Compare Source
Full Changelog: https://github.com/docker/metadata-action/compare/v6.0.0...v6.1.0
v6.0.0Compare Source
#inside values while still supporting full-line#comments by @crazy-max in #607Full Changelog: https://github.com/docker/metadata-action/compare/v5.10.0...v6.0.0
v6Compare Source
docker/setup-buildx-action (docker/setup-buildx-action)
v4.2.0Compare Source
Full Changelog: https://github.com/docker/setup-buildx-action/compare/v4.1.0...v4.2.0
v4.1.0Compare Source
Full Changelog: https://github.com/docker/setup-buildx-action/compare/v4.0.0...v4.1.0
v4.0.0Compare Source
Full Changelog: https://github.com/docker/setup-buildx-action/compare/v3.12.0...v4.0.0
v4Compare Source
docker/setup-qemu-action (docker/setup-qemu-action)
v4.2.0Compare Source
Full Changelog: https://github.com/docker/setup-qemu-action/compare/v4.1.0...v4.2.0
v4.1.0Compare Source
resetinput to uninstall current emulators by @crazy-max in #21Full Changelog: https://github.com/docker/setup-qemu-action/compare/v4.0.0...v4.1.0
v4.0.0Compare Source
Full Changelog: https://github.com/docker/setup-qemu-action/compare/v3.7.0...v4.0.0
v4Compare Source
jsdom/jsdom (jsdom)
v29.1.1Compare Source
'border-radius'computed style serialization. (@asamuzaK)'background-origin'and'background-clip'CSS properties. (@asamuzaK)getComputedStyle(), before the cache warms up. (@asamuzaK)v29.1.0Compare Source
getComputedStyle()sometimes returning outdated results after CSS was modified. (@asamuzaK)v29.0.2Compare Source
getComputedStyle(). Computed value rules are now applied across a broader set of properties, and include fixes related to inheritance, defaulting keywords, custom properties, and color-related values such ascurrentcolorand system colors. (@asamuzaK)'background' and'border'shorthand parsing. (@asamuzaK)v29.0.1Compare Source
'border','background', and their sub-shorthands containing keywords orvar(). (@asamuzaK)getComputedStyle()to return a more functionalCSSStyleDeclarationobject, including indexed access support, which regressed in v29.0.0.v29.0.0Compare Source
Breaking changes:
Other changes:
@acemir/cssomandcssstyledependencies with fresh internal implementations built on webidl2js wrappers and thecss-treeparser. Serialization, parsing, and API behavior is improved in various ways, especially around edge cases.CSSCounterStyleRuleandCSSNamespaceRuleto jsdomWindows.cssMediaRule.matchesandcssSupportsRule.matchesgetters.MediaList, usingcss-treeinstead of naive comma-splitting. Invalid queries become"not all"per spec.cssKeyframeRule.keyTextgetter/setter validation.cssStyleRule.selectorTextsetter validation: invalid selectors are now rejected.styleSheet.ownerNode,styleSheet.href, andstyleSheet.title.Documentinitialization performance by lazily initializing the CSS selector engine, avoiding ~0.5 ms of overhead perDocument. (@thypon)CSSStyleDeclarationmodifications to properly trigger custom element reactions.@mediarule parsing.CSSStyleSheet's "disallow modification" flag not being checked in all mutation methods.XMLHttpRequest'sresponsegetter returning parsed JSON during theLOADINGstate instead ofnull.getComputedStyle()crashing in XHTML documents when stylesheets contained at-rules such as@pageor@font-face.XMLHttpRequestcaused by a race condition with the worker thread's idle timeout.pnpm/pnpm (pnpm)
v11.10.0Compare Source
Minor Changes
e2e3c81: Added theissuescommand as an alias ofbugs, sopnpm issuesopens the package's bug tracker URL in the browser.8491f8e: Added theprefixcommand which prints the current package prefix directory (or global prefix directory if-g/--globalis used).3425e80: Added an_authsetting for configuring registry authentication as a single structured (URL-keyed) value. It can be set in the global pnpm config (config.yaml) or, for CI, via thepnpm_config__authenvironment variable. The env form sidesteps the GitHub Actions / bash / zsh limitation that broke the existingpnpm_config_//host/:_authToken=…form (env var names containing/,:, or.are silently dropped). Closes #12314.The value is keyed by registry URL so each secret is explicitly bound to the host that may receive it. Registry URL keys must use
httporhttpsand must not include credentials, query strings, or fragments:The equivalent in the global
config.yaml:Within each registry URL,
@means registry-wide/default credentials and package scopes like@orgbind credentials to that scope on the same host. The only supported credential field isauthToken(maps to_authToken/ bearer auth); the deprecatedbasicAuth/username+passwordforms are intentionally not accepted here.Each entry also infers a trusted registry route:
@routes the default registry (andpnpm add <pkg>resolves there), and@orgroutes that scope. Because the credential and destination host arrive in one trusted value, repo-controlledpnpm-workspace.yamlor project.npmrccannot redirect the token to a different host._authis honored only from the env var and the global config — it is ignored in a projectpnpm-workspace.yaml/.npmrc, so repo-controlled config can never supply registry auth. Precedence: CLI flags (--registry,--@​scope:registry) >pnpm_config__auth> globalconfig.yaml_auth>pnpm-workspace.yaml.Both
pnpm_config__auth(lowercase, documented form) andPNPM_CONFIG__AUTH(all-caps, the shell convention some CI runners apply) are honored. If both are set, lowercase wins unless it is empty, in which case uppercase is used. The env var wins over the globalconfig.yaml_authon a conflicting key.tokenHelperis not supported in_auth. Parsing is strict: a malformed value (bad JSON, wrong shape, invalid registry URL or scope, an unsupported credential field) fails fast with an error rather than being silently dropped.Pacquet parity note: the pacquet (Rust) port supports the same single credential field as the TS CLI:
authToken.a33eeec:pnpm self-updateandpackageManagerversion-switching can now install and link pnpm v12 (the Rust port), published with equal content under both thepnpmand@pnpm/exenames on thenext-12dist-tag. Its native binaries ship as@pnpm/exe.<platform>-<arch>packages, which pnpm's built-in installer links directly — no Node.js launcher, so the command pays no Node startup cost. v12 is initialized exactly like@pnpm/exe, including per-platform global-virtual-store hashing. From v12 onward the install converges on the unscopedpnpmpackage (the Rust exe) — even when updating from the SEA@pnpm/exebuild.1dd12bd: When resolving through a pnpr install-accelerator server, pnpm no longer forwards its own upstream registry credentials in the resolve request. Only theAuthorizationheader identifying the caller to pnpr is sent. The pnpr server now selects upstream credentials from its own route policy (operator-configured upstream credential aliases), so private dependencies resolve through a pnpr-managed alias the caller is authorized to use, rather than by sending the client's registry tokens to the server.1e81761: Expose web authenticationauthUrlanddoneUrlin JSON error output when OTP is required in a non-interactive terminal #12724.Patch Changes
2f389d6: Added the Node.js release team's new signing key (Stewart X Addison,655F3B5C1FB3FA8D1A0CA6BDE4A7D232B936D2FD) to the embedded Node.js release keys, so runtimes whoseSHASUMS256.txtis signed by the new releaser verify successfully.acbdb94: Fixed shell tab completion not suggesting workspaces after the-Falias for--filteroption.dcabb78: Fixedpnpm up -r <pkg>bumping unrelated packages that have open semver ranges. Previously, any update mutation nullified the lockfile-derivedpreferredVersionsglobally, so packages with^x.y.zranges could re-resolve to newer compatible versions even though the user only asked to update a specific package. The install layer now always seedspreferredVersionsfrom the lockfile, and caller-supplied preferred versions (such as the vulnerability penalties ofpnpm audit --fix) layer on top of the seed instead of replacing it. The targeted package still bumps: the per-resolveupdateRequestedflag makes the resolver ignore the target's own lockfile pins.Closes #10662.
d539172: Fixed pnpm pack and pnpm publish failing when prepack generates files that are included in the package and postpack cleans them up.be6505a: Hardened global package management:node.exeflavor of a bin, so a stalenode.exeno longer survives onPATHafter uninstall, and a new global install no longer silently overwrites an existingnode.exe.pnpm add -g pnpm@<version>(and@pnpm/exe@<version>) is now rejected like the barepnpmform, pointing topnpm self-update.node_modulespaths, preventing a tampered manifest from escaping the install directory.25c7388: pnpm now rejectsjsr:specifiers whose package name is not a valid npm package name — an empty scope or name (e.g.jsr:@​scope/), path separators inside the name, or any other shapevalidate-npm-package-namerejects — withERR_PNPM_INVALID_JSR_PACKAGE_NAMEinstead of silently converting them into a malformed@jsr/...npm package name.25c7388: pnpm now rejects named-registry specifiers (e.g.gh:) whose package name is not a valid npm package name — an empty scope (e.g.gh:@​/bar), path separators inside the name (e.g.gh:@​scope/../name), or any other shapevalidate-npm-package-namerejects — withERR_PNPM_INVALID_NAMED_REGISTRY_PACKAGE_NAMEinstead of passing the name through to registry URLs and metadata cache file paths.96da7c5: node-gyp'sgyp_main.pyandgypentrypoints are now packed with the executable bit in thepnpmand@pnpm/exetarballs. Without it, building native addons from source could fail with a permission error.99982b9: Sped up resolution and reduced memory use against registries that ignore npm's abbreviated metadata format and always return the full package document (for example, Azure DevOps Artifacts). pnpm now strips such documents down to the abbreviated field set before caching them. Resolution output is unchanged, and registries that honor the abbreviated format (such as the npm registry) pay no extra cost.11a7fdd: Sped up offline and--prefer-offlineresolution on large workspaces (e.g.pnpm dedupe --offline,pnpm install --offline). Package metadata loaded from the local cache is now kept in memory, so each package's metadata is parsed once per command instead of once per dependent that references it.2c7369d:pnpm pack-appnow rejects--entry/pnpm.app.entryand--output-dir/pnpm.app.outputDirvalues that are absolute paths or escape the project directory via..(or a symlink that resolves outside it), and refuses to write the produced executable when its target path already exists as a symlink (or other non-regular file). This prevents a repository-controlledpackage.jsonfrom embedding host files (such as an SSH key) into the produced executable, writing build artifacts outside the project, or overwriting an arbitrary file through a committed symlink. The new error codes areERR_PNPM_PACK_APP_ENTRY_OUTSIDE_PROJECT,ERR_PNPM_PACK_APP_OUTPUT_DIR_OUTSIDE_PROJECT, andERR_PNPM_PACK_APP_OUTPUT_FILE_NOT_REGULAR.When ad-hoc signing macOS targets,
pnpm pack-appnow runs the systemcodesignby absolute path and resolvesldidto a location outside the project, so a repository-controllednode_modules/.binonPATHcannot hijack the signer.ce5d5a5: Relative paths inpatchedDependenciesare now resolved against the lockfile directory when computing patch file hashes, so runningpnpm installfrom a subdirectory no longer fails withENOENTlooking for the patch file in the wrong location #12762.ebb4096:pnpm peersno longer reports a conflict for a missing peer dependency that is ignored viapnpm.peerDependencyRules.ignoreMissing.dcabb78: Fixed a prototype-pollution hazard when seeding preferred versions: a dependency named__proto__in a manifest or inpnpm-lock.yamlcould write throughObject.prototype(or crash the install) while the preferred-versions map was being built. The maps are now null-prototype objects, so crafted package names land as plain keys.f38e696: Hardenedpnpm deploy --forceso it refuses unsafe deploy targets such as workspace roots, parent directories, out-of-workspace paths, and symlinked target parents.806c3ec: pnpm no longer warns about ignored project-level auth settings whenPNPM_CONFIG_NPMRC_AUTH_FILEpoints at the project.npmrc— setting it to that file is an explicit opt-in to trusting it, so auth env variables in it are expanded pnpm/pnpm#12480.991405e: Restore differential rendering (ansi-diff) to fix duplicated output lines introduced by #12351.c121235: Fixed the topological order of--filtered commands (pnpm run,pnpm exec,pnpm publish,pnpm pack,pnpm rebuild) when the selected projects depend on each other only transitively through projects that were not selected. Previously such selected projects could run concurrently or in the wrong order; now a project always runs after the selected projects it transitively depends on, while projects without a real dependency relationship still run concurrently. This now also holds for prod-only filters (--filter-prod), which resolve order through the production dependency graph so transitive production dependencies are respected without pulling back the dev dependencies the filter drops, and for selections that mix--filterwith--filter-prod#8335.d539172:pnpm packandpnpm publishno longer follow a symlinked workspaceLICENSEfile when injecting it into a package that has no license of its own. Following the symlink could pack bytes from outside the workspace into the published tarball.dcabb78: Fixedpnpm up <pkg>producing a different result than a fresh install of the same manifests would. The resolver now distinguishesupdateRequested(true only for packages that match the user's update target) from the broaderupdateflag, and for the targeted package ignores only its own lockfile-derived preferred-version pins — so the target re-resolves exactly as if its lockfile entries were deleted andpnpm installran. Preferred versions a fresh install applies (manifest pins, versions propagated down the dependency chain, and the vulnerability-avoidance penalties ofpnpm audit --fix) stay in effect, so an update never installs duplicate versions that a reinstall from scratch would not reproduce. When a preferred version holds the update target below the newest version its range admits, pnpm now prints a warning explaining that reaching the newer version everywhere requires an override.dcabb78:pnpm update <dep>@​<version>now prints a warning when<dep>is only present as a transitive dependency: the requested version cannot be applied there (updates resolve the target the way a fresh install would), and the warning recommends adding the version topnpm.overridesinstead, which is the mechanism that does pin transitive dependencies. Closes #12744.a6c4d5f: When a dependency cannot be found in the registry (404) or the registry has no matching version, and a workspace project with the same name exists only at non-matching versions, the error now reports the available workspace versions (ERR_PNPM_NO_MATCHING_VERSION_INSIDE_WORKSPACE) instead of the raw registry failure pnpm/pnpm#1379. Other registry failures (authorization, network, server errors) still propagate unchanged. The pacquet (Rust) resolver applies the same behavior.v11.9.0Compare Source
Minor Changes
bae694f: Some registries generate tarballs on-demand and cannot provide an integrity checksum in their package metadata. In that case pnpm now computes the integrity from the downloaded tarball and stores it in the lockfile, so the entry is verifiable on subsequent installs instead of being written without an integrity (which would fail the next install). This also applies to--lockfile-only: the tarball is downloaded so its integrity can be computed. A lockfile entry that is still missing its integrity is rejected as aERR_PNPM_MISSING_TARBALL_INTEGRITYlockfile verification violation (the install fails closed) rather than being silently re-fetched.6c35a43: Added--exclude-peerstopnpm sbom. Withauto-install-peers(the default), peer dependencies resolve into the lockfile and are otherwise indistinguishable from the package's own dependencies. The flag drops peer dependencies (and any transitive subtree reachable only through them) from the SBOM. CycloneDX 1.7 has no scope or relationship that expresses "consumer-provided peer", so omission is the only spec-clean handling. The flag name matchespnpm list --exclude-peers; note the SBOM flag prunes a peer's exclusive subtree, which is stricter thanpnpm list(which only hides leaf peers).Patch Changes
25a829e:pnpm audit --fixnow writes a single combinedminimumReleaseAgeExcludeentry per package (e.g.axios@0.18.1 || 0.21.1) instead of one entry per version, matching the format documented for the setting. Existing per-version entries inpnpm-workspace.yamlare merged into the combined form rather than left as duplicates. Installs that auto-collect immature versions intominimumReleaseAgeExcludenow report the same combined entries, so the "Added N entries" message matches what is written to the manifest #12534.1cbb5f2: Fixed non-deterministic peer resolution that could add or remove an optional transitive peer — for example@babel/core, reached throughstyled-jsx— from a package's peer-dependency suffix across otherwise identical installs, churning the lockfile and causing intermittentpnpm dedupe --checkfailures in CI. When a package's children are resolved by one occurrence (the "owner") and reused by a deeper consumer, whether that consumer inherited the owner's missing peers depended on whether the owner's resolution had finished yet — a race under concurrent resolution. The decision is now a function of the dependency graph's structure rather than resolution-completion order.d577eea: Fixed a Windows flakiness inpnpm dlxwhere a failed install could surface a spuriousEBUSY: resource busy or lockederror. The cleanup of a partially-populated dlx cache is now best-effort with retries and no longer masks the original error.ec7cf70: Shortened thepnpm dlxcache path so deep dependency trees no longer overflow Windows'MAX_PATH, which could make a dependency's lifecycle script fail withspawn cmd.exe ENOENT.05b95ab: Fixedpnpmhanging (and crashing with an unhandled promise rejection) when a non-retryable network error such asSELF_SIGNED_CERT_IN_CHAINoccurs while fetching from a registry. The error is now rejected through the returned promise instead of being thrown inside the detached retry callback.d3f68e2: Fix apnpm auditperformance regression on lockfiles that contain dependency cycles. The reachable-vulnerability pruning added in pnpm 11.5.1 only memoized acyclic subtrees, so any node whose subtree touched a cycle — together with all of its ancestors — was recomputed on every query, making the path walk quadratic. Reachability is now computed once per node using Tarjan's strongly-connected-components algorithm, so cyclic graphs are handled in linear time #12212.The audit path walk also no longer recurses, so a deeply nested dependency graph can no longer overflow the call stack, and the install path to each finding is tracked without per-node copying, keeping memory linear in the graph depth.
322f88f: Fix failed optional dependency updates so they don't rewrite unrelated dependency specs #11267.1488db1: WhenenableGlobalVirtualStoreis toggled on for a project that was previously installed without it, stale hoisted symlinks undernode_modules/.pnpm/node_modulesare now replaced instead of being left pointing at the old per-project virtual store location #9739.6545793: Fixedpnpm install --ignore-workspaceoverwriting theallowBuildsmap inpnpm-workspace.yaml. The ignored builds of a package with a build script were auto-populated intoallowBuildseven though--ignore-workspacewas passed, clobbering committedtrue/falsevalues with theset this to true or falseplaceholder #12469.fbdc0eb: FixedminimumReleaseAgeExcludeandtrustPolicyExcludeso multiple exact-version entries for the same package behave the same as a single||disjunction entry. Previously only the first matching rule's versions were honored, so a config like[form-data@4.0.6, form-data@2.5.6]could still flagform-data@2.5.6as violatingminimumReleaseAge, while[form-data@4.0.6 || 2.5.6]worked as expected #12463.fa7004b: The in-memory package metadata cache is now populated on the exact-version disk fast path, so repeated resolutions of the same package within one install no longer re-read and re-parse the on-disk metadata. In large monorepos this brings the time for adding a new package down from minutes to seconds. The in-memory cache key now also includes the registry, so a package of the same name served by two different registries in a single install can no longer share a cache slot and resolve the wrong tarball.0a154b1: Fixedpnpm patchdropping the package name (and leaking internal option fields) when the patched dependency resolves to a single git-hosted version.4d3fe4b: The pnpr resolver endpoints moved under the reserved/-/pnprnamespace:POST /v1/resolveis nowPOST /-/pnpr/v0/resolveandPOST /v1/verify-lockfileis nowPOST /-/pnpr/v0/verify-lockfile. The capability handshake atGET /-/pnpradvertises protocol version0to match. This keeps every pnpr-proprietary route in npm's reserved namespace, so it can never collide with a package path.0ec878d: Removing a runtime dependency now removes the matchingdevEngines.runtimeorengines.runtimeentry that was materialized from it. Blank runtime selectors are normalized tolatest.17e7f2c:pnpm sbomnow emits a CycloneDXissue-trackerexternal reference for components (and the root) whosepackage.jsondeclares abugsURL. Email-onlybugsentries are skipped, since the reference requires a URL.a84d2a1: Add@pnpm/resolving.tarball-url, which builds and recognizes the canonical npm tarball URL of a package. It vendorsgetNpmTarballUrl(previously the externalget-npm-tarball-urlpackage) and addsisCanonicalRegistryTarballUrl, the predicate the lockfile writer uses to decide whether a tarball URL is derivable from name+version+registry (and can therefore be omitted frompnpm-lock.yaml).Exposing
isCanonicalRegistryTarballUrllets a custom resolver (pnpmfileresolvers) fronting a proxy that serves tarballs on a non-canonical path (e.g. an ephemerallocalhost:<port>) rewrite the resolved tarball to the canonical form, so nothing host-specific is persisted to the lockfile. Previously this logic was private to@pnpm/lockfile.utils.Two correctness fixes are included while consolidating the logic: the scoped-package unescape now handles uppercase
%2Fas well as%2f(percent-encoding is case-insensitive), and protocol-insensitive comparison strips only a leadinghttp(s)://scheme instead of splitting on the first://(which could truncate URLs containing a later://).852d537: Lockfile verification no longer reports a registry metadata fetch failure (for example a403/401on a private registry, or a network error) asERR_PNPM_TARBALL_URL_MISMATCH. When the registry can't be reached to verify an entry, the install now aborts with the registry's own fetch error (such asERR_PNPM_FETCH_403, which already explains the authentication situation) instead of mislabeling a transport failure as lockfile tampering. Registry fetch errors no longer leak basic-auth credentials embedded in the registry URL (https://user:pass@host/) into their message.v11.8.0Compare Source
Minor Changes
c112b61: Added a--dry-runoption topnpm install. It runs a full dependency resolution and reports what an install would change, but writes nothing to disk (no lockfile, nonode_modules) and always exits with code 0. This mirrors the preview semantics ofnpm install --dry-run#7340.179ebc4:pnpm run --no-bailnow exits with a non-zero exit code when any of the executed scripts fail, while still running every matched script to completion. This makes the exit-code behavior of--no-bailconsistent between recursive and non-recursive runs (recursive runs already failed at the end). Previously, a non-recursivepnpm run --no-bailalways exited with code 0, even when a script failed #8013.0474a9c: Added support for generating Node.js package maps atnode_modules/.package-map.jsonduring isolated and hoisted installs. Added thenode-experimental-package-mapsetting to inject the generated map into pnpm-managed Node.js script environments, and thenode-package-map-typesetting to choose betweenstandardandloosepackage maps.dcededc:pnpm sbomnow marks components reachable only throughdevDependencieswith CycloneDXscope: "excluded"and thecdx:npm:package:developmentproperty. Theexcludedscope documents "component usage for test and other non-runtime purposes", which matches the semantics of a devDependency; the property is the CycloneDX npm-taxonomy marker emitted by@cyclonedx/cyclonedx-npm, so both modern (scope) and existing (property) consumers are covered. Components reachable at runtime (including installedoptionalDependencies) omitscopeand default torequired.1495cb0: Added per-package SBOM generation with--outand--splitflags. Use--out out/%s.cdx.jsonto write one SBOM per workspace package to individual files, or--splitfor NDJSON output to stdout. When--filterselects a single package, the SBOM root component now uses that package's metadata. Workspace inter-dependencies (workspace:protocol) and their transitive dependencies are included. Author, repository, and license fall back to the root manifest when the package doesn't define them.293921a: feat(view): support searching project manifest upward when package name is omittedWhen running
pnpm viewwithout a package name, the command now searchesupward for the nearest project manifest (
package.json,package.yaml, orpackage.json5) and uses itsnamefield.If the manifest exists but lacks a
namefield, an error is thrown.This change also replaces the
find-updependency withempathicforimproved performance and consistency across workspace tools.
Patch Changes
29ab905: Fixedpnpm updateoverriding the version range policy of a named catalog whose name parses as a version (e.g.catalog:express4-21). Thecatalog:reference carries no pinning of its own, so the prefix from the catalog entry (such as~) is now preserved instead of being widened to^#10321.bee4bf4: Security: validate config dependency names and versions from the env lockfile (pnpm-lock.yaml) before using them to build filesystem paths. A committed lockfile with a traversal-shapedconfigDependenciesname (such as../../PWNED) or version (such as../../../PWNED) could previously causepnpm installto create symlinks or write package files outsidenode_modules/.pnpm-configand the store. Names must now be valid npm package names and versions must be exact semver versions; the same validation is applied to optional subdependencies of config dependencies, and to the legacy workspace-manifest format before any lockfile is written. See GHSA-qrv3-253h-g69c.96bdd57: Fixlink:workspace protocol switching tofile:afterpnpm rmis run from inside a workspace package whose target workspace dependency has its own dependencies, wheninjectWorkspacePackages: trueis set. Follow-up to #10575, which fixed the same symptom for workspace packages without dependencies.302a2f7: No longer warn about using bothpackageManageranddevEngines.packageManagerwhen the two fields pin the same package manager at the same version with the same integrity hash (e.g. bothpnpm@11.5.1+sha512.…). Previously the hash was stripped from the legacypackageManagerfield but not fromdevEngines.packageManager, so even identical specifications looked like a mismatch #12028.The warning still fires on any genuine divergence, and several cases now state the specific reason instead of a single generic message: a different package manager, a different version, or contradictory integrity hashes for the same version.
3f0fb21: Fixed the progress line showing leftover characters from external processes that write to the terminal between progress updates (e.g. an SSH passphrase prompt would leave a fragment likeadded 0sa':). The interactive reporter now redraws each frame in place, erasing to the end of the display before reprinting, so any such remnants are cleared #12350.564619f: Fixedpnpm approve-buildsreporting "no packages awaiting approval" when a build-script dependency whose approval was revoked (e.g. aftergit stashdrops theallowBuildsfrompnpm-workspace.yaml) is re-added. The revoked packages are now correctly recorded in.modules.yamlsoapprove-buildscan find them. #122213d1fd20: Skip the redundant "target bin directory already contains an exe called node" warning on Windows when the existingnode.exealready matches the target (same hard link or identical content) pnpm/pnpm#12203.1b02b47: Fix macOS Gatekeeper blocking native binaries (.node,.dylib,.so) by removing thecom.apple.quarantineextended attribute after importing them from the store.When pnpm imports files from its content-addressable store into
node_modules, macOS preserves extended attributes, includingcom.apple.quarantine. If this xattr is present on a store blob (e.g. it was first written under a Gatekeeper-enabled app such as a Git client), it propagates tonode_modules, and Gatekeeper blocks the native binary from loading even though pnpm already verified the file's integrity against the lockfile.After importing a package, pnpm now strips
com.apple.quarantinefrom its native binaries, matching Homebrew's behaviour of dropping quarantine from verified downloads. The cleanup is macOS-only, runs in a single batchedxattrcall per package, is restricted to native binaries (other files are untouched), and is non-fatal (it logs a warning on unexpected errors).Fixes #11056
61969fb: Fixpnpm installwithoptimisticRepeatInstallincorrectly reportingAlready up to datewhenpnpm-lock.yamlchanged but project manifests did not. This affected workflows such as checking out or restoring only the lockfile #12100.Also fixes
checkDepsStatusto use the correct lockfile path whenuseGitBranchLockfileis enabled, so the optimistic fast-path and lockfile modification detection work withpnpm-lock.<branch>.yamlfiles instead of always stat'ingpnpm-lock.yaml. Merge-conflict detection now reads the resolved lockfile name as well, and withmergeGitBranchLockfilesenabled everypnpm-lock.*.yamlis scanned for modifications and conflicts. The git branch is now resolved by reading.git/HEADdirectly (no process spawn) and uses the workspace directory rather thanprocess.cwd().5c12968: Fix recursive updates of transitive dependencies when the update command mixes transitive dependency patterns with direct dependency selectors. For example,pnpm up -r "@​babel/core" uuidnow updates matching transitive@babel/coredependencies even whenuuidis a direct dependency selector #12103.9d79ba1: Register thepnpm update --no-saveflag in the CLI help and option parser.0474a9c: Fixedpnpm importfor Yarn v2 lockfiles whenjs-yamlv4 is installed.9e0c375: Fixedpnpm installrepeatedly prompting to remove and reinstallnode_modulesin a workspace package whenenableGlobalVirtualStoreis enabled. The post-install build step recorded a per-projectnode_modules/.pnpmvirtual store directory innode_modules/.modules.yaml, overwriting the global<storeDir>/linksvalue the install step had written. The next install then detected a virtual-store mismatch (ERR_PNPM_UNEXPECTED_VIRTUAL_STORE). The build step now derives the same global virtual store directory as the install step #12307.223d060: Document the--cpu,--osand--libcflags in the output ofpnpm install --help. These flags were already supported but were only documented on the website #12359.e85aea2: Avoid readingREADME.mdfrom disk when publishing if the publish manifest already provides areadmefield. The README is now only read lazily, insidecreateExportableManifest, when it is actually needed.3188ae7: Fixedpnpm peers checkto accept loose peer dependency ranges such as>=3.16.0 || >=4.0.0-when the installed peer version satisfies the range #12149.531f2a3: Fixedpnpm updaterewriting aworkspace:dependency that points at a local path (e.g.workspace:../packages/foo/dist) into a normalizedlink:or version-range specifier. Such specifiers are now preserved verbatim when the workspace protocol is preserved #3902.fe66535: Fixed a lockfile non-convergence bug where an incremental install kept a duplicate transitive dependency that a fresh install would not produce. When a package is reused from the lockfile, its child edges are taken verbatim and bypass the preferred-versions walk, so a transitive dependency could stay pinned to an older version even after a direct dependency resolved to a higher version that satisfies the same range. The resolver now refreshes such a stale pin to the higher direct-dependency version during resolution — so the older version is never resolved or fetched, and the incremental result converges to the fresh one.6d35338:pnpm installdetects changes inside local file dependencies again. The optimistic repeat-install fast path only tracks manifest and lockfile modification times, so edits inside a local dependency's directory (or a repacked local tarball) were reported as "Already up to date". Projects with local file dependencies (file:and bare local path or tarball specifiers, declared directly or throughpnpm.overrides) now always run a full install, which refetches those dependencies, matching pnpm v10 behavior #11795.4ca9247: Preserve the existing Node.js runtime version prefix when resolvingnode@runtime:<range>to a concrete version.30c7590: Create shorter CAFS temporary package directories to leave room for lifecycle scripts that create IPC socket paths under TMPDIR.13815ad: Reporter output (warnings, progress) forpnpm storeandpnpm configsubcommands now goes to stderr instead of stdout. This fixes scripts that capture their stdout (e.g.PNPM_STORE=$(pnpm store path),pnpm config list --json | jq) from getting warnings mixed into the result.1c05876: Avoid relinking unchanged child dependencies and remove stale child links during warm installs.817f99d: Fixed lockfile churn where a package'stransitivePeerDependenciescould be dropped (and shift between packages) when the package participates in a dependency cycle. A cycle re-entry resolves against truncated children, so it must not be cached as "pure"; otherwise sibling occurrences of the same package short-circuit and lose transitive peers depending on traversal order #5108.eba03e0: Fixpnpm installreporting "Already up to date" after a catalog entry inpnpm-workspace.yamlwas reverted to a previous version. After an update modified a catalog, the workspace state cache stored the pre-update catalog versions, so reverting the entry back to its original version was not detected as an outdated state #12418.3b54d79:pnpm updatenow keeps lockfileoverridesthat resolve through a catalog in sync with the catalog. Previously, when an override referenced a catalog (e.g.overrides: { foo: 'catalog:' }) andpnpm updatebumped that catalog entry, the lockfile'scatalogsadvanced while the resolvedoverrideskept the old version. The resulting lockfile was internally inconsistent, so a laterpnpm install --frozen-lockfilefailed withERR_PNPM_LOCKFILE_CONFIG_MISMATCH.9d0a300: Fixedpnpm version --recursiveso it honors the workspace selection. In recursive mode the version bump now applies to the packages resolved from the workspace filter (selectedProjectsGraph), matching the behavior ofpnpm publish --recursive, instead of always bumping every workspace package #11348.v11.7.0Compare Source
Minor Changes
Added a new setting
frozenStore(--frozen-store) that letspnpm installrun against a package store on a read-only filesystem (e.g. a Nix store, a read-only bind mount, an OCI layer). When enabled, pnpm opens the store's SQLiteindex.dbthrough theimmutable=1URI — bypassing the WAL/-shmsidecar creation that otherwise fails on a read-only directory — and suppresses every store-write path (theindex.dbwriter and the project-registry write). Pair it with--offline --frozen-lockfileagainst a fully-populated store. Under the global virtual store, package directories live inside the store, so if the store is missing the build output of a package whose lifecycle scripts are approved (or that has a patch), pnpm fails up front withERR_PNPM_FROZEN_STORE_NEEDS_BUILDrather than crashing mid-build on a read-only write — seed the store with those builds first. Incompatible with--forceand with a configured pnpr server, since both write into the store; the side-effects cache is likewise not written underfrozenStore. If the store is missing its content directory, the install fails fast withERR_PNPM_FROZEN_STORE_INCOMPLETErather than attempting to initialize it. The read-onlyimmutable=1open requires Node.js >=22.15.0, >=23.11.0, or >=24.0.0; on older runtimes--frozen-storefails with a clearERR_PNPM_FROZEN_STORE_UNSUPPORTED_NODEerror. Bin-linking also tolerates a read-only store: under the global virtual store a package's bin source lives inside the store, so thechmodthat makes it executable would be refused — withEPERM/EACCES, or withEROFSon a genuinely read-only filesystem. Thatchmodis redundant when the seed already ships its bins executable with a normalized shebang, so it is now skipped in that case, while a non-executable bin (or one still carrying a Windows CRLF shebang) on a read-only store still errors.When
pacquet(the Rust port of pnpm) is declared inconfigDependencies, pnpm now delegates dependency resolution to it too — not just materialization — provided the installed pacquet is new enough to support full resolving installs (>= 0.11.7).Previously pacquet only ran in frozen-install mode: pnpm always resolved the dependency graph itself (writing
pnpm-lock.yaml) and handed pacquet a finished lockfile to fetch / import / link. With pacquet >= 0.11.7, a non-frozenpnpm install(default isolatednodeLinker, plain install) is delegated to pacquet end-to-end in a single pass — pacquet resolves the manifests, writes the lockfile, and materializesnode_modules. pnpm detects the capability from the installed pacquet's version; older pacquet releases keep the resolve-then-materialize split, andadd/update/removestill resolve in pnpm (it has to mutate the manifests first). This remains an opt-in preview of the Rust install engine #11723.Added a new opt-in
--batchflag topnpm publish --recursivethat sends all selected packages to the registry in a singlePUT /-/pnpm/v1/publishrequest instead of one request per package. The target registry has to implement the batch publish endpoint (pnpr does); registries that don't are reported with a clearERR_PNPM_BATCH_PUBLISH_UNSUPPORTEDerror. The batch is processed all-or-nothing by pnpr: if any package in the batch fails validation, none of the packages are published.Patch Changes
Reject path-traversal and reserved dependency aliases (such as
../../../escape,.bin,.pnpm, ornode_modules) that come from a lockfile rather than a freshly resolved manifest. A crafted lockfile alias could otherwise be joined directly under a hoistednode_modulesdirectory, letting package files be written outside the intended install root or overwrite pnpm-owned layout.The fix adds two layers:
nodeLinker: hoistedgraph builder now validates each alias at the directory sink (safeJoinModulesDir), matching the validation pnpm already performs when resolving aliases from manifests.verifyLockfileResolutions) now runs an always-on, policy-independent check that rejects any importer or snapshot dependency alias that is not a valid package name, failing the install early — before any fetch or filesystem work — for every node linker at once.Made shared package child resolution deterministic when the same package is reached through multiple contexts. pnpm now chooses the shallowest occurrence, then importer order, then parent path, instead of letting request timing decide the child context and missing-peer report pnpm/pnpm#12358.
Fix garbled summary line after submitting
pnpm update -iandpnpm audit --fix -i. The interactive checkbox prompt previously printed every selected choice's full table row (label, current/target versions, workspace, URL) joined by commas, producing a wall of text after pressing Enter. The summary now lists only the selected package names (or vulnerability keys) by setting an explicitshortper choice; the in-progress selection UI is unchanged.Prevent
pnpm patch-removefrom removing files outside the configured patches directory.Fixed
pnpm publishignoringstrictSsl: falsewhen publishing to registries with self-signed certificates. ThestrictSSLoption is now forwarded tolibnpmpublish/npm-registry-fetchso thatstrict-ssl=falsein.npmrcorstrictSsl: falseinpnpm-workspace.yamlis respected during publish, the same way it is forpnpm installpnpm/pnpm#12012.Fixed
Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefinedregression introduced in 11.6.0 when runningpnpm add <pkg>outside a workspace on Windows.selectProjectByDirwas keying the resultingProjectsGraphbyopts.dirinstead ofproject.rootDir, so downstreammanifestsByPathlookups missed when the two paths normalized differently (typically drive-letter casing). pnpm/pnpm#12379Git dependencies that point to a subdirectory of a repository (
repo#commit&path:/sub/dir) keep theirpathin the lockfile again. Since the integrity of git-hosted tarballs started being pinned in the lockfile, any install that actually downloaded the tarball rebuilt the lockfile resolution as{ integrity, tarball, gitHosted }and dropped thepathfield, while installs served from the store kept it — so the field disappeared seemingly at random. Withoutpath, later installs from that lockfile silently unpacked the repository root instead of the subdirectory #12304.Fixed nondeterministic lockfile output that made
pnpm dedupe --checkfail intermittently in CI. When a locked peer provider was pinned for a dependency that has no child dependencies of its own, the pinned provider leaked into the shared parent scope, so siblings resolved after it could pick up an optional peer they should not see. Which siblings were affected depended on resolution order, which varies with network timing.Sped up
pnpm installwith a frozen lockfile by running lockfile verification (the policy revalidation gate added forminimumReleaseAge/trustPolicyand the tarball-URL anti-tamper check) concurrently with fetching and linking instead of blocking the whole install on it. Dependency lifecycle scripts are still held back until verification succeeds, so no script runs on an unverified lockfile: if verification fails the install aborts before any dependency build, and if linking finishes first the install waits for the verification verdict before completing.User-defined
npm_config_*environment variables are now preserved during lifecycle script execution. Previously, allnpm_-prefixed env vars were stripped, which caused user-set variables likenpm_config_platform_archto be lost pnpm/pnpm#12399.pnpm can now use different auth tokens for different package scopes, even when those scopes use the same registry URL.
Previously, auth was selected only by registry URL. If
@org-aand@org-bboth usedhttps://npm.pkg.github.com/, they had to share the same token. This caused problems for registries that issue tokens per organization or per scope.Configure a scope-specific token by adding the package scope after the registry URL in the auth key:
pnpm login --registry=https://npm.pkg.github.com --scope=@​org-awrites the token to the same scope-specific auth key.When installing or publishing
@org-a/*, pnpm usesORG_A_TOKEN. For@org-b/*, pnpm usesORG_B_TOKEN. Packages without a matching scope continue to use the registry-wide fallback token.pnpm setupno longer prompts to approve build scripts for@pnpm/exewhen installing the standalone executable. pnpm links the platform-specific binary itself, so the package's install scripts are skipped during the global self-install #12377.Close lockfile reads deterministically before rewriting lockfiles and keep pacquet's virtual store directory length aligned with pnpm on Windows.
A
304 Not Modifiedanswer from the registry now renews the cached metadata file's mtime, so theminimumReleaseAgefreshness shortcut keeps serving resolutions from the cache. Previously, once a cached packument grew older thanminimumReleaseAge, every subsequent install re-validated it against the registry forever, because a 304 never rewrites the file.Updated dependency ranges. Notably:
@pnpm/loggerpeer dependency range moved to^1100.0.0.msgpackr1.11.8 → 2.0.4 (store index files remain byte-compatible in both directions).open^7.4.2 → ^11.0.0,memoize^10 → ^11,cli-truncate^5 → ^6,pidtree^0.6 → ^1.@yarnpkg/core4.5.0 → 4.8.0,@rushstack/worker-pool0.7.7 → 0.7.18,@cyclonedx/cyclonedx-library10.0.0 → 10.1.0,@pnpm/config.nerf-dart^1 → ^2,@pnpm/log.group3.0.2 → 4.0.1,@pnpm/util.lex-comparator^3 → ^4.Updated
@zkochan/cmd-shimto v9.0.6.Fixed a Windows-only hang where a failed command could take 20–46 seconds to exit. On error, pnpm enumerates descendant processes (via
pidtree) to terminate them, which on Windows shells out towmic/PowerShellGet-CimInstance Win32_Process— a lookup that is extremely slow on some machines. The lookup is now bounded by a short timeout so it can no longer stall the process exit.v11.6.0Compare Source
Minor Changes
pnpm installcompletes without re-resolving whenpnpm-lock.yamlwas deleted butnode_modulesis intact: the up-to-date check now treats the current lockfile (node_modules/.pnpm/lock.yaml) — the record of what the previous install materialized — as the wanted lockfile, verifies the manifests still match it, restorespnpm-lock.yamlfrom it, and reports "Already up to date". Previously this scenario triggered a full resolution and a re-verification of every locked package against the registry.615c669: Added support for configuring URL-scoped registry settings throughnpm_config_//…andpnpm_config_//…environment variables, for example:This provides a file-free way to supply registry authentication. Because the registry a value applies to is encoded in the (trusted) environment variable name, it is host-scoped by construction and cannot be redirected to another registry by repository-controlled config. The environment value is treated as trusted config: it takes precedence over a project/workspace
.npmrcbut is still overridden by command-line options. When the same key is provided through both prefixes,pnpm_config_wins.Raised the default network concurrency from
min(64, max(cpuCores * 3, 16))tomin(96, max(cpuCores * 3, 64)). Package downloads are I/O-bound, not CPU-bound, so deriving the floor from the core count left machines with few cores (for example 4-vCPU CI runners) downloading only 16 tarballs at a time and unable to saturate a low-latency registry. ThenetworkConcurrencysetting still overrides the default.Patch Changes
.npmrcuses an environment variable in a registry/proxy URL or in registry credentials. The message now explains why the setting was ignored and how to migrate it to a trusted source — for example by moving the line to the user-level~/.npmrcor runningpnpm config set "<key>" <value>— with a link to https://pnpm.io/npmrc. Thepnpm config setexample is only suggested when the key has no${...}placeholder, so the snippet is always safe to copy-paste.os/cpu/libcfields are missing from the registry metadata or the lockfile. Some registries strip these fields from the package metadata, which made pnpm download and install the binaries of every platform regardless ofsupportedArchitectures. The missing platform fields of an optional dependency are now inferred from its name (e.g.@nx/nx-win32-arm64-msvc→os: win32,cpu: arm64), so foreign-platform binaries are skipped without even downloading them #11702.v11.5.3Compare Source
Patch Changes
Stopped expanding environment variables in repository-controlled registry/proxy request destinations and registry credential values from
.npmrc, and in workspace registry URLs frompnpm-workspace.yaml. Move dynamic registry URL and token configuration to trusted user, global, CLI, or environment config.Resolve package-manager bootstrap dependencies with trusted user or CLI registry and network config, and reject package-manager env-lockfile records that do not use registry package paths with integrity-only resolutions before auto-switch execution.
Avoid writing
packageManagerDependenciestopnpm-lock.yamlwhen package manager policy is set toonFail: ignoreorpmOnFail: ignore#12228.Avoid running dependency-status auto-install when the dependency status is unavailable without a project manifest.
Using the
$version reference syntax inoverrides(e.g."react": "$react") now prints a deprecation warning. The syntax still works, but catalogs are the recommended way to keep an overridden version in sync with the rest of the workspace. Reference a catalog entry with thecatalog:protocol instead.Fixed
pnpm config get globalconfigto return the globalconfig.yamlpath again pnpm/pnpm#11962.Fixed bare
--colorso it does not consume the following CLI flag, allowing command shorthands like--parallelto expand correctly and forms likepnpm --color with current <command>to dispatch the inner command instead of failing withMISSING_WITH_CURRENT_CMD.Fix
pnpm installignoringenableGlobalVirtualStoretoggle by including it in the workspace state settings check #12142.Security: pnpm now verifies the npm registry signature of a package-manager binary before spawning it, so a cloned repository cannot make pnpm download and execute an arbitrary native binary.
This covers two paths that select an executable from repository-controlled input:
pacquet(or@pnpm/pacquet) inconfigDependenciesopts in to pnpm's Rust install engine. pnpm now verifies that the installedpacquetshim and the host's@pacquet/<platform>-<arch>binary carry a valid npm registry signature for their exactname@version, and refuses to run pacquet (failing the command) if the signature does not verify or cannot be checked. The only graceful fallback to pnpm's own engine is when pacquet has no binary for the current platform.self-update— thepackageManager/devEngines.packageManagerfield makes pnpm download and run a specific pnpm version. pnpm now verifies the registry signature ofpnpm,@pnpm/exe, and the host platform binary before installing/spawning them, and refuses to run an engine whose signature does not match a published, signed release. The check runs only on an actual download (store cache miss), so it does not add a network round trip to every command.In both cases the signature is verified over the installed integrity, against npm's public signing keys that ship embedded in the pnpm CLI (like corepack), so bytes substituted via a tampered lockfile or a repository-controlled registry fail verification — and a registry the user did not vouch for cannot supply its own signing keys. The signed packument is fetched from the configured registry, so an npm mirror works transparently. Verification fails closed: if it cannot be completed (for example, the registry is unreachable), the command fails rather than running an unverified binary. The embedded keys are kept current by a release-time check against npm's signing-keys endpoint.
Made peer-dependent deduplication deterministic. When a peer-suffixed package variant was a subset of two or more mutually incompatible larger variants, the variant it collapsed into depended on the order importers were resolved in, which varies between machines. This could resolve the same workspace to different lockfiles on different platforms and make
pnpm dedupe --checkalternate between passing and failing.Reject invalid package names and versions from staged tarball manifests before deriving filenames for
pnpm stage download.Clarified in CLI help that the pnpm store is trusted shared state and store integrity checks are corruption detection, not a tamper boundary for untrusted store writers.
Reject reserved manifest
binnames ("",".","..", and scoped forms such as@scope/..) when resolving a package's bins. These names previously passed the bin-name guard and, when joined to the global bin directory during global remove/update/add operations, could resolve to the global bin directory itself or its parent and have it recursively deleted.Require trusted package identity before package-name
allowBuildsentries can approve lifecycle scripts for git, git-hosted tarball, direct tarball, and local directory artifacts. To approve one of those artifacts explicitly, use its peer-suffix-free lockfile depPath as theallowBuildskey. Lockfile verification now rejects lockfiles where a registry-style dependency path (name@semver) is backed by a git, directory, or git-hosted tarball resolution (ERR_PNPM_RESOLUTION_SHAPE_MISMATCH), so the dependency path is a reliable artifact identity by the time scripts can run.Security: pnpm now verifies the OpenPGP signature of a downloaded Node.js runtime's
SHASUMS256.txtbefore trusting its integrity hashes.When a repository requests a Node.js runtime (e.g. via
devEngines.runtime/useNodeVersion), the download mirror is repository-configurable throughnode-mirror:<channel>. The integrity of the downloaded binary was only checked againstSHASUMS256.txtfetched from that same mirror — a circular check that a malicious mirror could satisfy by serving a tampered binary together with a matchingSHASUMS256.txt. pnpm then executes the binary (for example to run lifecycle scripts).pnpm now fetches
SHASUMS256.txt.sigand verifies the detached OpenPGP signature against the Node.js release team's public keys, which ship embedded in the pnpm CLI. A mirror that serves a tampered binary cannot also produce a valid signature, so the download fails to verify. The embedded keys are kept current by a release-time check against the canonicalnodejs/release-keyslist.The musl variants from the hardcoded
unofficial-builds.nodejs.orgmirror are not repository-configurable and are signed by a different key, so they continue to be trusted over TLS.v11.5.2Compare Source
Patch Changes
Peer dependency resolution now reuses the peer contexts already recorded in the lockfile when those providers are still present in the dependency graph and still satisfy the peer ranges. This avoids unnecessary peer-context rewrites during lockfile regeneration. Current manifest choices remain authoritative: a newly added, explicitly updated, or aliased direct provider, a changed nested provider, or a locked version that no longer satisfies the range still takes precedence.
The lockfile verifier now checks that a registry entry pinning an explicit
tarballURL points at the artifact the registry's own metadata lists for thatname@version. Previously a tampered lockfile could pair a trustedname@versionwith an attacker-chosen tarball URL (and a matching integrity for those bytes), so the install fetched the attacker's bytes. A mismatch — or any entry that can't be confirmed against the registry — is rejected withERR_PNPM_TARBALL_URL_MISMATCH. Non-registry resolutions (file:, git-hosted, etc.) and registry entries without an explicit tarball URL (the URL is reconstructed from name+version+registry, so it is inherently bound) are unaffected; non-standard registry tarball URLs (npm Enterprise, GitHub Packages) still pass because they match the metadata.Fix
pnpm update --recursive --lockfile-only <pkg>@​<version>crashing withInvalid Versionwhen the catalog entry for<pkg>is a version range (e.g.^21.2.10) andcatalogModeisstrictorprefer. The catalog–version comparison now skips the equality check when either side is a range rather than passing a range tosemver.eq(), so range specifiers fall through to the existing mismatch handling instead of throwing #11570.Avoided a Node.js crash when pnpm exits after network requests on Windows.
Fixed packages being materialized into the virtual store without their root-level files (
package.json,LICENSE, README, root entrypoints) when multiplepnpm installprocesses ran against the same store/workspace concurrently. The fast import path used to destructively empty the shared target directory, so a concurrent importer could wipe files another importer had already written; if the surviving files included thepackage.jsoncompletion marker, every later install treated the broken directory as complete and never repaired it. The fast path now imports directly only when it can create the target directory exclusively, and otherwise builds the package in a private temp directory and atomically renames it into place #12197.Fix dependency build scripts not running under the global virtual store (
enableGlobalVirtualStore).In a workspace install, dependency build scripts are deferred to a single
rebuildpass (buildProjects). That pass resolved each package's location from the classicnode_modules/.pnpm/<depPathToFilename>layout, which does not exist under the global virtual store — so native dependencies (e.g. packages usingnode-gyp/prebuild-install) were never built and failed to load at runtime (Cannot find module .../build/Release/*.node).buildProjectsnow resolves the global-virtual-store projection directory (<storeDir>/links/<hash>, computed with the same graph hash the installer uses) whenenableGlobalVirtualStoreis set, and serializes concurrent builds of the same shared projection so parallel workspace projects don't race on the same directory.Don't promote a
runtime:dependency (such as the Node.js version fromdevEngines.runtimeorpnpm runtime set) into a catalog whencatalogModeisstrictorprefer. Aruntime:dependency round-trips todevEngines.runtime, which only recognizes theruntime:protocol; cataloging it rewrote the manifest entry tocatalog:, which broke that round-trip, stranded it indevDependencies, and leftdevEngines.runtimeuntouched.Skip lockfile
minimumReleaseAge/trustPolicyverification for non-registry tarball protocols (for examplefile:), so local tarball dependencies are not incorrectly checked against npm registry metadata.v11.5.1Compare Source
Patch Changes
pnpm auditperformance by pruning non-vulnerable lockfile subtrees and stopping path enumeration once vulnerable findings reach the path cap.npm_config_user_agentfor root lifecycle scripts during headless installs.integrityfield of a remote (non-registry) tarball dependency when its lockfile entry is rebuilt. Re-resolving such a dependency without re-fetching it (for example viapnpm update, or when another dependency changes) produced a resolution with no integrity — URL/tarball resolvers only learn the integrity after the tarball is downloaded — so the previously recorded integrity was dropped, making later installs fail withERR_PNPM_MISSING_TARBALL_INTEGRITY#12067.repositoryfield into the{ type, url }object form when creating the publish manifest, matching npm's behavior. Some registries (e.g. Gitea/Codeberg) reject a stringrepositorywith a 500 Internal Server Error duringpnpm publish#12099.@typescript-eslint/eslint-pluginpeer-depends on both@typescript-eslint/parserandtypescript, and@typescript-eslint/parserpeer-depends ontypescript), pnpm no longer reuses a hoisted instance of the shared peer that was resolved against a different version #12079.v11.5.0Compare Source
Minor Changes
Added a new
hoistingLimitssetting fornodeLinker: hoistedinstalls, mirroring yarn'snmHoistingLimits. It acceptsnone(the default — hoist as far as possible),workspaces(hoist only as far as each workspace package), ordependencies(hoist only up to each workspace package's direct dependencies). Originally proposed in #6468, closing #6457.Replaced
enquirerwith@inquirer/promptsfor all interactive prompts. Fixes theupdate -iscrolling overflow bug where long choice lists were clipped in the terminal #6643.User-facing changes:
pnpm update -i/pnpm update -i --latest: Scrolling now works correctly when many packages are available; the new library uses visual-line-aware pagination viausePaginationpnpm audit --fix -i: Same scrolling fix for vulnerability selectionpnpm approve-builds: Interactive build approval prompts updatedpnpm patch: Version selection and "apply to all" prompts updatedpnpm patch-remove: Patch removal selection updatedpnpm publish: Branch confirmation prompt updatedpnpm login: Credential prompts updatedpnpm run/pnpm exec(withverifyDepsBeforeRun=prompt): Confirmation prompt updatedVim-style
j/kkeys still work for up/down navigation in all interactive prompts.Internal: The
OtpEnquirerandLoginEnquirerDI interfaces changed from{ prompt }to{ input }/{ input, password }respectively. Plugins or custom builds that inject their own enquirer mock will need to update.Staged publishes are now recognized in the trust scale. When a package version's registry metadata carries an
approverfield, it is treated as the strongest trust evidence (ranked above trusted publishers and provenance attestations), since staged publishes require 2FA publish approvals. This prevents false-positive trust downgrade errors when moving from a staged publish to a lower trust level #11887.Patch Changes
Fix pnpm hanging during peer resolution when an aliased install pulls in transitive packages with mutual peer cycles at different depths in the dependency tree (for example,
pnpm i nuxt@npm:nuxt-nightly@5x). Cycles whose members hit thefindHitcache instead of running their owncalculateDepPathare now short-circuited by sibling resolutions at the level where the cycle is detected, so the cached path promises no longer deadlock. #11999.Fix
pnpm dist-tag addandpnpm dist-tag rmagainst npmjs.org failing without--otpwith[ERR_PNPM_UNAUTHORIZED] You must be logged in to set dist-tag … "You must provide a one-time pass. Upgrade your client to npm@latest in order to use 2FA.". pnpm now sendsnpm-auth-type: webon dist-tag writes and surfaces the resulting OTP challenge through the existing browser-based 2FA flow (the samewithOtpHandlinghelper used bypnpm publish), so the browser opens, the user authenticates, and the dist-tag is set on retry.--otp=<code>continues to work via the classic flow.Fix
minimumReleaseAgeExcludehandling in npm resolution fast paths so excluded packages do not get pinned to stale versions. Excludes are honored consistently duringpublishedBymetadata selection and cache-mtime shortcuts.Fix the
integrityfield being dropped from the lockfile entry of a remote (non-registry) https-tarball dependency when an unrelated package is installed afterwards. URL/tarball resolvers do not return an integrity (it is only known after the tarball is downloaded), so when such a dependency was reused from the lockfile without being re-fetched, its integrity was lost. It is now carried over from the existing resolution. With pnpm's lockfile-integrity hardening, the missing integrity made subsequent--frozen-lockfileinstalls fail withERR_PNPM_MISSING_TARBALL_INTEGRITY. #12001.Skip dependency re-resolution when
pnpm-lock.yamlis missing butnode_modules/.pnpm/lock.yamlexists and still satisfies the manifest.pnpm installnow reuses the materialized snapshot to regeneratepnpm-lock.yamlinstead of walking the registry to rebuild it from scratch, turning the cache+node_modules variation into a near-no-op for users who deleted the lockfile but kept the install #11993.--frozen-lockfilestill refuses to proceed whenpnpm-lock.yamlis absent — the regenerated lockfile must be committed, so failing loudly is the correct behavior for CI.v11.4.0Compare Source
Minor Changes
Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously,
pnpm install(non-frozen) would logERR_PNPM_TARBALL_INTEGRITY, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile.pnpm installnow exits withERR_PNPM_TARBALL_INTEGRITYand a hint pointing at the new opt-in flag.The only opt-in is
pnpm install --update-checksums— narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable.--forceandpnpm updatedeliberately do not bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide.--frozen-lockfilebehavior is unchanged.--fix-lockfilekeeps its documented purpose (filling in missing lockfile entries) and is also not a bypass.pnpm runtime set <name> <version>now saves the runtime todevEngines.runtimeby default instead ofengines.runtime. Pass--save-prod(or-P) to save it toengines.runtimeinstead #11948.Patch Changes
Fix a credential disclosure issue where an unscoped
_authToken(or_auth, orusername+_password, ortokenHelper) defined in one source —~/.npmrc,~/.config/pnpm/auth.ini, a workspace.npmrc, CLI flags, etc. — would be sent as anAuthorizationheader to whichever registry a different (potentially untrusted) source named. The same fix extends to client TLS credentials (cert,key) so they aren't presented to a registry their author didn't choose.pnpm now rewrites each unscoped per-registry setting (
_authToken,_auth,username,_password,tokenHelper,cert,key) to its URL-scoped form at load time, using theregistry=value declared in the same source (or the npmjs default registry if the source declares none). A later layer overridingregistry=therefore cannot pull an unscoped credential along, because it is already pinned to the URL its author intended.ca/cafileare intentionally not rescoped — they're trust anchors, not credentials, and corporate MITM-proxy setups rely on them applying globally.Every rescope emits a deprecation warning telling the user where the setting was pinned and how to write it directly. npm has rejected unscoped credentials outright since
npm@9, and pnpm intends to remove support in a future major release. To target a specific registry, write the setting URL-scoped (e.g.//registry.example.com/:_authToken=...or//registry.example.com/:cert=...).@pnpm/network.auth-header: removed thedefaultRegistryparameter fromcreateGetAuthHeaderByURIandgetAuthHeadersFromCreds. Now that credentials are URL-scoped at load time, the mergedconfigByUrinever contains the empty-string "default registry" placeholder slot, so re-keying it onto the merged default registry is no longer needed.Fix
pnpm deploycrashing withENOENT: ... lstat '<deployDir>/node_modules'whenconfigDependenciesdeclares pacquet (pacquetor@pnpm/pacquet). The deploy directory never installs config dependencies, so the install engine they designate isn't on disk to invoke; the nested install now skips them.Reject git resolutions whose
commitfield is not a 40-character hexadecimal SHA before invokinggit. A malicious lockfile could otherwise smuggle a value such as--upload-pack=<command>throughgit fetch/git checkout, which on SSH or local-file transports executes the supplied command.Limit concurrent project manifest reads while listing large workspaces to avoid
EMFILEerrors.Reject patch files whose
diff --githeaders reference paths outside the patched package directory. Previously a malicious.patchfile added via a pull request could write, delete, or rename arbitrary files reachable by the user runningpnpm install.Improve the log message that pnpm prints after auto-adding entries to
minimumReleaseAgeExcludewhenminimumReleaseAgeis set withoutminimumReleaseAgeStrict. The message previously referred to the internal "loose mode" terminology, which wasn't searchable in the docs; it now tells the user to setminimumReleaseAgeStricttotrueif they want these updates gated behind a prompt instead #11747.Reject dependency aliases that contain path-traversal segments (such as
@x/../../../../../.git/hooks) when reading them from a package manifest or symlinking them intonode_modules. A malicious registry package could otherwise use a transitive dependency key to makepnpm installcreate symlinks at attacker-chosen paths outside the intendednode_modulesdirectory.Reject
pnpm-lock.yamlentries whose remote tarballresolution:block is missing theintegrityfield. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that stripsintegrity:) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under--frozen-lockfile. pnpm now fails closed at lockfile-read time withERR_PNPM_MISSING_TARBALL_INTEGRITY. Git-hosted tarballs (gitHosted: trueor a URL on codeload.github.com / bitbucket.org / gitlab.com) andfile:tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes.Validate
devEngines.runtimeandengines.runtimeversion ranges fornode,deno, andbunwhenonFailis set toerrororwarn. Previously these settings only had an effect withonFail: 'download'— theerrorandwarnmodes silently did nothing #11818. Violations now throwERR_PNPM_BAD_RUNTIME_VERSION.Require provenance before treating trusted publisher metadata as the strongest trust evidence.
v11.3.0Compare Source
Minor Changes
Added
pnpm stagewithpublish,list,view,approve,reject, anddownloadsubcommands for npm staged publishing.Added a new setting
trustLockfile. Whentrue,pnpm installskips the supply-chain verification pass that re-appliesminimumReleaseAge/trustPolicy='no-downgrade'to every entry in the loaded lockfile. The install treats the lockfile as already-trusted — useful for closed-source projects where every commit comes from a trusted author. Defaults tofalse; verification stays on by default. Set inpnpm-workspace.yaml.Also cut the memory footprint of the verification pass itself: the per-(registry, name) trust-meta cache previously retained the full packument — dependency graphs, scripts, README, and per-version manifests — for the entire install. On large workspaces (
~4klockfile entries withminimumReleaseAge+trustPolicy: no-downgradeenabled) this could OOM CI runners with a 2GB heap cap. The cache now stores only the fields the trust check actually reads (time, per-version_npmUser.trustedPublisher,dist.attestations.provenance). The abbreviated-metadata cache is similarly projected to just the package-levelmodifiedfield and the set of currently-listed version names. Fixes #11860.Implemented
pnpm pkgcommand natively, followingnpm pkgstandards.Implemented
pnpm repocommand natively, followingnpm repostandards.Implemented
pnpm set-script(aliasss) natively. Adds or updates an entry in thescriptsfield of the project manifest, supportingpackage.json,package.json5, andpackage.yamlformats.Add a
skip-manifest-obfuscationoption forpnpm packandpnpm publish. When enabled, the originalpackageManagerfield and publish lifecycle scripts are kept in the packed/published manifest instead of being stripped. The pnpm-specificpnpmfield continues to be omitted.Patch Changes
pnpm dlxfailing withERR_PNPM_NO_IMPORTER_MANIFEST_FOUNDwhen the installed package's CAS slot is missing itspackage.json. Observed in the wild forpnpm dlx node@runtime:<version>when the GVS slot was populated without the synthesized manifest runtime archives need (they don't ship apackage.jsonof their own, so the synthesized one is the only way it gets there; an existing slot from an earlier code path that skipped the synthesis stays incomplete). The bin link itself is wired up from the resolution and remains valid, sodlxnow falls back to the scopeless package name when the slot's manifest is unreadable — for single-bin packages (the dlx common case, including everyruntime:spec) this matches whatmanifest.binwould have named. Multi-bin packages already require--package=<spec> <bin>to disambiguate and don't enter this code path.pnpm dedupeandpnpm installwhen a dependency graph contains packages with transitive peer dependencies on each other (e.g.@aws-sdk/client-stsand@aws-sdk/client-sso-oidc) andauto-install-peersis enabled. The lockfile no longer flips between two equally-valid forms across consecutive runs. The root cause was thatresolveDependenciespushed onto itspkgAddresses/postponedResolutionsQueuearrays from insidePromise.all-spawned callbacks, so completion-order timing leaked into the array order and downstream cyclic-peer suffix assignment. Fixes #8155.pnpm add <github-shorthand>(and any other wanted-dependency whose alias can't be parsed from the user-supplied spec, e.g. tarball URLs orpnpm/test-git-fetch#sha) was silently dropped from the manifest update and frompendingBuilds. The alias-keyed lookup added in that PR couldn't find awantedDependencywhosealiaswasundefinedat parse time but resolved to a package name only after fetching, so the entry never made it intospecsToUpsert. Restored the original index-based pairing betweendirectDependenciesandwantedDependencies; the catalog-protocol preservation that PR was originally fixing is unaffected because it's driven byrdd.catalogLookup.userSpecifiedBareSpecifier, not by the lookup. Fixes the threerebuilds dependencies/rebuilds specific dependencies/rebuild with pending optionfailures inbuilding/commands/test/build/index.ts.pnpm add --configleaving orphan entries inpnpm-lock.env.yaml(the optional subdependencies of the previously resolved version of the updated config dependency).v11.2.2Compare Source
Patch Changes
configDependencies, the user's CLI flags passed topnpm install(e.g.--no-runtime,--prod,--dev,--no-optional,--node-linker,--cpu/--os/--libc,--offline,--prefer-offline) are now forwarded to pacquet'sinstallsubcommand verbatim. Previously pacquet was invoked with a fixed argument list, so flags like--no-runtimewere silently dropped. Flag forwarding is gated on the command beinginstall/i;add,update, anddedupestill don't forward (their flag surface doesn't line up with pacquet'sinstall).pnpm up(andpnpm add/pnpm remove) failing withpacquet_package_manager::outdated_lockfilewhen pacquet is declared inconfigDependencies. pnpm now passes--ignore-manifest-checkto pacquet so its--frozen-lockfilecheck doesn't fire against the (pre-mutation)package.jsonpnpm hasn't written yet #11797. Requires a pacquet release that supports the flag — bumpPACQUET_VERSIONin the e2e tests once it ships.v11.2.1Compare Source
Patch Changes
optional: truein the env lockfile, matching how optional dependencies are recorded elsewhere inpnpm-lock.yaml. Previously, snapshots for the platform-specific subdeps pulled in via a config dep'soptionalDependencieswere written as empty objects, which was inconsistent with the rest of the lockfile and made it look like those non-host platform variants were required.pickRegistryForPackagereturning the wrong registry for an unscopednpm:alias under a scoped local name. A manifest entry like"@​private/foo": "npm:lodash@^1"was routing thelodashfetch throughregistries["@​private"], even thoughlodashis unscoped and doesn't live on that registry. The npm-alias branch now returns the alias target's own scope (ornullfor an unscoped target, falling through toregistries.default) instead of leaking into the local key's scope.v11.2.0Compare Source
Minor Changes
Experimental: Adding
@pnpm/pacquet(the Rust port of pnpm) toconfigDependenciesinpnpm-workspace.yamlnow delegates the materialization phase ofpnpm installto the pacquet binary. pnpm still owns dependency resolution; pacquet only fetches and imports from the freshly-written lockfile. This is an opt-in preview of the Rust install engine #11723.To configure pacquet in a project, run:
You'll see changes in
pnpm-workspace.yamlandpnpm-lock.yamlthat should be committed. If you experience any issues with pacquet, please let us know by mentioning this in the GitHub issue you create.configDependenciesnow resolve and install one level ofoptionalDependenciesdeclared by the config dependency, withos/cpu/libcplatform filtering applied at install time. This unlocks the esbuild/swc-style pattern where a package ships platform-specific binaries viaoptionalDependencies— a config dependency can now do the same and have the matching binary symlinked next to it in the global virtual store, sorequire('pkg-platform-arch')from inside the config dependency resolves correctly.The env lockfile records all platform variants regardless of host platform, so it remains portable across machines. Each entry in a config dependency's
optionalDependenciesmust declare an exact version — ranges and tags are rejected to keep installs reproducible.Implement the documented
pnpm login --scope <scope>flag. The scope is normalized (a leading@is added if missing; blank values are ignored) and an@<scope>:registry=<registry>mapping is written to the pnpm auth file alongside the auth token. Subsequent installs of@<scope>/*packages then route to the chosen registry. Previouslypnpm login --scope fooerrored withUnknown option: 'scope'despite the flag being listed in the online documentation #11716.pnpm outdatedandpnpm update --interactivenow report Node.js, Deno, and Bun runtimes installed as project dependencies (runtime:specifiers). Previously these were silently skipped.Patch Changes
Fix
cafile=<relative-path>in.npmrcbeing read from the wrong directory when pnpm is invoked from a different cwd (e.g.pnpm --dir <project> installfrom a CI wrapper or monorepo script). The path is now resolved against the directory of the.npmrcthat declared it, notprocess.cwd(). Before this fix the CA file silently failed to load — the install proceeded without the configured CA and the user only saw TLS errors against a private registry, with no log line tying back to the wrongly resolved path #11624.Fix
config.registrygetting a trailing slash appended whenregistryis set in.npmrcand noregistries.defaultis provided bypnpm-workspace.yaml. The sync fromregistries.defaulttoconfig.registryintroduced in #11744 now only fires when the workspace manifest actually contributes a different default.Fix global add/update to handle minimumReleaseAge policy violations instead of surfacing an internal resolver guardrail error.
Fix two crashes with
injectWorkspacePackages: truewhen the lockfile has been pruned (e.g. byturbo prune --docker):Cannot use 'in' operator to search for 'directory' in undefined: a peer-dependency-variant injected snapshot inherits itsresolutionfrom the basepackages:entry; when a pruner drops that base entry the readers crash.convertToLockfileObjectnow reconstructs the directory resolution from thefile:depPath at load time — a single normalization point, so every reader sees a fully-formed snapshot.ERR_PNPM_ENOENTonnode_modules/.bin/<tool>: afterprepare/postinstall,runLifecycleHooksConcurrentlyre-imported each injected workspace package; thescanDir-into-filesMapworkaround fed target-internal paths to the importer, which themakeEmptyDirfast path (#11088) then wiped. Drop the workaround and passkeepModulesDir: trueso the importer preserves the target's existingnode_modules(bin links + transitive deps) and source files keep their hardlinks.Fixed
pnpm loginandpnpm logoutignoringregistries.defaultfrompnpm-workspace.yaml#10099.Fix the
minimumReleaseAge(publishedBy) maturity shortcut to be inclusive at the cutoff. Previously, abbreviated metadata whosemodifiedfield equalled the cutoff fell off the fast path and triggered a full-metadata re-fetch (or aMISSING_TIMEerror when full metadata wasn't permitted). Sincemodifiedis an upper bound on every version's publish time,modified == publishedByalready implies every version passes the per-version<=filter infilterPkgMetadataByPublishDate, so the shortcut now accepts the boundary case directly. Strictly>(was>=) at the rejection branch.Honor
publishConfig.accesswhen publishing packages.v11.1.3Compare Source
Patch Changes
pnpm installnow re-validatespnpm-lock.yamlentries against the activeminimumReleaseAgeandtrustPolicy: 'no-downgrade'policies before any tarball is fetched. Lockfiles resolved elsewhere (committed to the repo, restored from a CI cache, produced by an older pnpm) under a weaker or absent policy can no longer install a freshly-published or trust-downgraded version silently. Violating entries abort the install withERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION,ERR_PNPM_TRUST_DOWNGRADE, or the genericERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATIONwhen both policies trip in the same batch;minimumReleaseAgeExcludeandtrustPolicyExcludeare honored. Verification results are cached so repeat installs against an unchanged lockfile take a fast path, and pnpm shows a transient progress line while the registry round-trip runs.When fresh resolution picks an immature version, the behavior depends on
minimumReleaseAgeStrict:minimumReleaseAgekeeps its built-in 24-hour value — auto-adds the immature picks tominimumReleaseAgeExcludeinpnpm-workspace.yamland lets the install proceed. A single info message lists what was persisted.minimumReleaseAgeExcludeand the install continues; declining aborts before the lockfile,package.json, ornode_modulesis touched.ERR_PNPM_NO_MATURE_MATCHING_VERSIONlisting every offending entry, instead of failing on the first one the resolver hit.minimumReleaseAgeStrictauto-enables whenever the user explicitly setsminimumReleaseAge(CLI flag, env var, globalconfig.yaml, orpnpm-workspace.yaml); setminimumReleaseAgeStrict: falseto keep loose-mode auto-collect even with an explicitminimumReleaseAgevalue. Closes #10438, #10488, #11687.Allow redundant trailing base64 padding in
.npmrcauth values and report invalid auth base64 with a pnpm error.Make
pnpm self-updaterespectminimumReleaseAge(andminimumReleaseAgeExclude) when resolving which pnpm version to install.When the
latestdist-tag points to a version newer than the configured age threshold,self-updatenow selects the newest mature version instead unless excluded byminimumReleaseAgeExclude.Also makes
dlxandoutdatedsurface invalidminimumReleaseAgeExcludepatterns under the sameERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDEerror code already used byinstall, instead of leaking the internalERR_PNPM_INVALID_VERSION_UNION/ERR_PNPM_NAME_PATTERN_IN_VERSION_UNIONcodes.Global installs respect global config build policy (e.g.,
dangerouslyAllowAllBuildsfrom config.yaml) when GVS is enabled #9249.The global virtual-store (GVS) default
allowBuilds = {}was applied before workspace manifest settings were read and before global config values (stripped byextractAndRemoveDependencyBuildOptions) were re-applied viaglobalDepsBuildConfig. This causedhasDependencyBuildOptionsto returntrue(because{}is not null), blocking restoration of global config values likedangerouslyAllowAllBuilds. As a result, global installs skipped all build scripts even when the config explicitly allowed them.This fix moves the GVS default to after workspace manifest reading and
globalDepsBuildConfigre-application, so that:allowBuildstakes precedence (if present)dangerouslyAllowAllBuildsis properly restored (if set and no workspace policy exists){}is only applied as a last resort when no policy is configured anywhereHonor
--silentwhenverifyDepsBeforeRun: installauto-installs dependencies beforepnpm runorpnpm exec, preventing install output from being written to stdout #11636.Fix lockfile parsing failures when
pnpm-lock.yamlcontains CRLF line endings and multiple YAML documents #11612.Anchor the side-effects-cache key and global-virtual-store hash to the project's script-runner Node —
engines.runtimepin when present, shellnodeotherwise — instead of pnpm's own runtime.ENGINE_NAME(the<platform>;<arch>;node<major>prefix used as the side-effects-cache key and the engine portion of the GVS hash) was computed fromprocess.version— the Node that runs pnpm itself. That was wrong in two situations:@pnpm/exeSEA bundle. The bundle has its own embedded Node, not thenodeon the user'sPATHthat actually spawns lifecycle scripts. Two pnpm installations on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store across two Node majors even though both installs would run scripts on the same shellnode.engines.runtime/devEngines.runtimepin. When a project pins a Node version viadevEngines.runtime(pnpm v11+), pnpm downloads that Node intonode_modules/node/and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node — so two installs of the same project with two different runner Nodes would still disagree on the GVS slot path even though scripts run on the same pinned Node.Three changes:
@pnpm/engine.runtime.system-node-versionnow exportsengineName(nodeVersion?). Resolves the version in this order: explicit override →getSystemNodeVersion()(which already prefersnode --versionoverprocess.versionin SEA contexts) →process.version.@pnpm/deps.graph-hashernow exportsfindRuntimeNodeVersion(snapshotKeys)— scans an iterable of lockfile snapshot keys for anode@runtime:<version>entry and returns its bare version string.calcDepStateandcalcGraphNodeHash/iterateHashedGraphNodesaccept anodeVersion?(in the options bag for the first, as a trailing parameter / ctx field for the others), forwarded toengineName(). The default (no override) preserves the pre-change behaviour. The legacyENGINE_NAMEconstant in@pnpm/constantsis unchanged so external consumers and existing tests keep working; in non-SEA, non-pinned contexts every value lines up.@pnpm/installing.deps-resolver,@pnpm/installing.deps-restorer,@pnpm/installing.deps-installer,@pnpm/building.during-install,@pnpm/building.after-install,@pnpm/deps.graph-builder) now derives the project's pinned runtime viafindRuntimeNodeVersion(Object.keys(graph))once per invocation and threads it through.On upgrade, two one-time GVS slot churns are possible:
node26) now hash under the shell-Node major (e.g.node24), matching what pacquet, the npm-publishedpnpmpackage, and any other pnpm-compatible tool already produce.devEngines.runtimepin: slots that previously hashed under the runner's Node major now hash under the pinned Node major, matching what the lifecycle scripts will actually run on.In both cases the old slots become prune-eligible.
Resolve the GVS hash's engine portion per-snapshot when a dependency declares its own
engines.runtime, instead of using an install-wide value.Pnpm's resolver desugars a dep's
engines.runtimeintodependencies.node: 'runtime:<version>', and the bin linker spawns that dep's lifecycle scripts through the pinned Node downloaded into<pkgDir>/node_modules/node/. The GVS hash and the side-effects-cache key prefix were still anchored to the install-wide runtime — so a pinning snapshot's slot encoded the wrong Node major, and a reinstall on the same host could read the cached side-effects under a key whose<platform>;<arch>;node<major>triple disagreed with the Node the build actually ran on.Per-snapshot resolution now matches what
bins/linkeralready does on a per-package basis:@pnpm/deps.graph-hasheraddsreadSnapshotRuntimePin(children)— reads thenodeentry from one snapshot's graph children and extracts the version from anode@runtime:value. Pairs with the existingfindRuntimeNodeVersion(snapshotKeys)install-wide fallback (also now exported from@pnpm/deps.graph-hasherrather than@pnpm/engine.runtime.system-node-version, where it was a poor fit —system-node-versionis about probing the host Node, not parsing lockfile-derived strings).calcDepStateandcalcGraphNodeHashconsultreadSnapshotRuntimePin(graph[depPath].children)first and only fall back to the install-widenodeVersionparameter when the snapshot doesn't pin its own Node.Pacquet mirrors the same precedence at the
calc_graph_node_hashcall site inpackage-manager/src/virtual_store_layout.rs— a newfind_own_runtime_node_major(snapshot)helper reads each snapshot'sdependenciesfor anodeentry withPrefix::Runtimeand overrides the install-wide engine when present.On upgrade, snapshots of dependencies that declare their own
engines.runtimere-hash under that dep's pinned Node instead of the install-wide value. The old slots become prune-eligible. Closes #11690.Fixed
pnpm publishfailing with a 404 when authentication relied on OIDC trusted publishing alongside an.npmrcwritten byactions/setup-node(_authToken=${NODE_AUTH_TOKEN}) withoutNODE_AUTH_TOKENbeing set. Unresolved${VAR}placeholders in auth values are now treated as empty rather than passed through verbatim, so the literal placeholder no longer surfaces as a bearer token when OIDC fallback is the intended auth source #11513.Fix
devEngines.packageManager(singular form, withoutonFail) defaulting toonFail: "error"instead of the documentedpmOnFail: "download". As a result, a project that pinned a different pnpm version viadevEngines.packageManagerand ranpnpm installfrom a mismatched pnpm version failed with a hard error, even though the migration table frommanagePackageManagerVersions: truetopmOnFail: download (default)promises the install would auto-download the wanted version #11676.The array form of
devEngines.packageManagerkeeps its existing per-element defaults (errorfor the last entry,ignorefor the rest), since those reflect explicit prioritization by the user. ExplicitonFailvalues continue to win.Fix
devEngines.packageManagernot writingpackageManagerDependenciestopnpm-lock.yamlwhen the lockfile lacks an env-doc entry. Previously the lockfile sync skipped resolution unless an existingpackageManagerDependencies.pnpmentry needed refreshing, so a fresh install withoutonFail: "download"left the resolved pnpm version unrecorded — contradicting the documented behavior that the resolved version is stored inpnpm-lock.yaml#11674.Warn when
package.jsoncontains a legacypnpmfield with settings pnpm no longer reads frompackage.json(e.g.pnpm.overrides,pnpm.patchedDependencies). Previously these were silently ignored after the upgrade from v10, leaving users unaware that their overrides/patched dependencies had stopped taking effect #11677.v11.1.2Compare Source
Patch Changes
convertEnginesRuntimeToDependencies: switch the runtime-dependency write toObject.definePropertyso the CodeQLjs/prototype-polluting-assignmentrule treats the assignment as safe regardless of the property name (follow-up to #11609).Address CodeQL static-analysis findings: guard manifest dependency writes against prototype-polluting keys (
__proto__,constructor,prototype), and replace a potentially super-linear semver-detection regex in registry 404 hints with an O(n) parser.Strip
sec-fetch-*headers from outgoing HTTP requests. These headers are automatically added by undici'sfetch()implementation per the Fetch spec but cause Azure DevOps Artifacts to return HTTP 400 for uncached upstream packages, as ADO interprets them as browser requests #11572.Fix
minimumReleaseAgehandling for cached abbreviated metadata.The version-spec cache fast path no longer rethrows
ERR_PNPM_MISSING_TIMEunderstrictPublishedByCheck; it now falls through to the registry-fetch path, consistent with the adjacent mtime-gated cache block.When the registry returns 304 Not Modified for a package whose cached metadata is abbreviated (no per-version
time), pnpm now re-fetches withfullMetadata: trueifminimumReleaseAgeis active and the package was modified after the cutoff. The upgraded metadata is persisted to disk so subsequent installs don't repeat the fetch. Previously the abbreviated meta was used as-is and the maturity check fell back to its warn-and-skip path, silently bypassing the quarantine and emitting a misleading "metadata is missing the time field" warning.Closes #11619.
Fix
pnpm upgrade --interactive --latest -rnot respecting named catalog groups. Previously, upgrading a dependency using a named catalog (e.g."catalog:foo") would incorrectly rewritepackage.jsonto"catalog:"and place the updated version in the default catalog instead of the named one #10115.Fixed
optimisticRepeatInstallskippingpnpm-lock.yamlmerge conflict resolution when the existingnode_modulesstate appears up to date.Fix
minimumReleaseAge/resolutionMode: time-basedinstalls failing on lockfiles whosetime:block is missing entries. The npm-resolver's peek-from-store fast path now surfacespublishedAtfrom the lockfile rather than discarding it, and falls through to a registry metadata fetch when the time-based cutoff can't be computed from the data on hand.v11.1.1Compare Source
Patch Changes
checkDepsStatus(run byverifyDepsBeforeRun). Previously the status check calledfindWorkspaceProjects, which validates each project'senginesandos/cpu/libcand warns about useless fields in non-root manifests — work that the install pipeline already performs. With nonodeVersionthreaded through, the engine check also fell back to the system Node fromPATHand emitted spurious "Unsupported engine" warnings before scripts ran. Status-only callers now usefindWorkspaceProjectsNoCheck; install paths continue to validate.pnpm add <alias>:@​scope/pkgfor named registries. The local resolver was claiming any specifier containing/as a local directory, sopnpm add bit:@​teambit/bit(withbitconfigured undernamedRegistries) installed a bogus link tobit:@​teambit/bit/instead of resolving from the configured registry. The local resolver now runs after the named-registry resolver in the resolution chain.@zkochan/cmd-shimto 9.0.3. The sh shim it writes for.cmd/.battargets now escapes the/Cswitch as//C, so it survives the path translation Git Bash applies when launchingcmd.exe. Without this, a bare/Cwas rewritten toC:\before reaching cmd.exe — the switch was dropped, cmd started interactively, and the calling script saw the cmd banner instead of the wrapped command's output. Affects any cmd-shim-wrapped batch script invoked from Git Bash / MSYS / Cygwin on Windows. See pnpm/cmd-shim#55.v11.1.0Compare Source
Minor Changes
Added
pnpm audit signaturesto verify ECDSA registry signatures for installed packages against keys from/-/npm/v1/keys#7909. Scoped registries are respected, and registries without signing keys are skipped.Added support for installing packages from the GitHub Packages npm registry via a built-in
gh:prefix (e.g.pnpm add gh:@​acme/private), and, more broadly, for arbitrary named registries in the style of vlt's named-registry aliases. Authentication is picked up from the existing per-URL.npmrcentries (e.g.//npm.pkg.github.com/:_authToken=...), so no separate auth mechanism is required.Additional aliases — or an override for the built-in
ghalias, for GitHub Enterprise Server — can be configured undernamedRegistriesinpnpm-workspace.yaml:With this,
work:@​corp/lib@^2.0.0resolves againsthttps://npm.work.example.com/. #11324.Allow setting sbom spec version using
--sbom-spec-version#11389.Add
--no-runtimeflag (config:runtime=false) to skip installing runtime entries (e.g. Node.js downloaded viadevEngines.runtime) without modifying the lockfile. The lockfile keeps the runtime entry so frozen-lockfile validation still passes; only the runtime fetch and.binlinking are skipped. Useful in CI matrices where the runtime is provisioned externally (e.g. viapnpm runtime -g set node <version>) beforepnpm installruns.Added the
pnpm bugscommand that opens a package's bug tracker URL in the browser. With no arguments, it reads the current project'spackage.json; with one or more package names, it fetches each package's metadata from the registry and opens its bug tracker. Falls back to<repository>/issueswhen thebugsfield is missing #11279.Added
pnpm ownercommand to manage package owners on the registry.Patch Changes
Added "published X ago by Y" information to the
pnpm viewcommand output, similar tonpm view. This is useful when comparing againstminimumReleaseAge.For example,
pnpm view pnpmnow shows:pnpm publishnow honors the configured HTTP/HTTPS proxy (includinghttps_proxy/http_proxy/no_proxyenvironment variables) when polling the registry'sdoneUrlduring the web-based authentication flow. Previously the poll bypassed the proxy, causing the registry to respond403from a different source IP and the login to never complete #11561.pnpm add -gnow installs each space-separated package into its own isolated directory by default. To bundle multiple packages into the same isolated install (so that they share dependencies and are removed together), pass them as a comma-separated list. For example:pnpm add -g foo barinstallsfooandbaras two independent globals — removing one does not affect the other.pnpm add -g foo,bar qarbundlesfooandbarinto a single isolated install whileqaris installed on its own.Related: #11587.
pnpm runtime set <name> <version>no longer fails in the root of a multi-package workspace with theADDING_TO_ROOTerror. Installing the workspace root is a valid target for a runtime, so the command now bypasses that safety check.Fix
pnpm --versionhanging for the lifetime of the worker pool after the version was printed.main.ts's--versionshort-circuit returned before reaching the command-handlerfinallythat callsfinishWorkers(), so the worker pool thatswitchCliVersionhad spawned during integrity resolution stayed alive and held the Node event loop open. The CLI entry now runsfinishWorkers()from its ownfinally, so every exit path tears the pool down.Repro:
pnpm --versionin a workspace whosedevEngines.packageManagerversion already matches the running pnpm +onFail: "download".switchCliVersionresolves the integrity (spawning workers), finds nothing to swap, returns. The version prints, then the process hangs.v11.0.9Compare Source
Patch Changes
https://gitlab.com/<user>/<project>/-/archive/<sha>/<project>-<sha>.tar.gzinstead of the GitLab API endpoint that contained an encoded slash (%2F) between user and project. The encoded slash both triggered406 Not Acceptableresponses from GitLab and produced virtual store directory names that Node refused to import (ERR_INVALID_MODULE_SPECIFIER) #11533.NPM_CONFIG_USERCONFIG(and its lowercasenpm_config_userconfigform) as a low-priority fallback when locating the user-level.npmrc. This restores compatibility with environments that point npm at a custom auth file via that env var — most notablyactions/setup-node, which writes registry credentials to${runner.temp}/.npmrcand exportsNPM_CONFIG_USERCONFIGto reference it. Without this, GitHub Actions workflows usingactions/setup-nodeto authenticate to private registries broke after upgrading to pnpm v11. PNPM-prefixed env vars andnpmrcAuthFilefrom the globalconfig.yamlcontinue to take precedence #11539.pnpm packnot bundling dependencies listed inbundleDependencies(orbundledDependencies). The npm-packlist upgrade in pnpm 11 changed its API to require the caller to pre-populate the dependency tree, which the wrapper was not doing —bundleDependencieswere silently dropped from the tarball #11519.SyntaxError: Invalid regular expression flagsinstead of printing a clear "requires Node.js v22.13" error when launched on an unsupported Node.js version. The Node.js version check inbin/pnpm.mjswas effectively dead code because the staticimportof the bundleddist/pnpm.mjswas hoisted by the ES module loader and parsed before the check could run #11546.pnpm --prefix=<dir> installoverwriting the existingpnpm-workspace.yamlin<dir>withset this to true or falseplaceholders. The renamed--prefixoption (which maps todir) was not honored when locating the workspace root, so the workspace manifest'sallowBuildssettings were not loaded into config and got clobbered when ignored builds were auto-populated #11535.pnpm publish --provenancefailing with a 422 from the registry when the package version contained semver build metadata (e.g.1.0.0-canary.0+abc1234). The+<build>segment is now stripped before packing so that the version embedded in the tarball, the metadata sent to the registry, and the sigstore provenance subject all agree #11518.v11.0.8Compare Source
Patch Changes
pnpm-lock.yamlwhen they cannot be derived from name+version+registry, even with the defaultlockfileIncludeTarballUrl: false. Without this,pnpm install --frozen-lockfilefrom an empty store fails withERR_PNPM_FETCH_404for packages on registries that serve tarballs from a non-standard path — most notably GitHub Packages (https://npm.pkg.github.com/download/<scope>/<name>/<version>/<hash>) and JSR.lockfileIncludeTarballUrl: truecontinues to force the URL into the lockfile for every package #11276.preversion,version, andpostversionlifecycle scripts forpnpm version.ERR_PNPM_BAD_TARBALL_SIZEwhen a registry serves tarballs with an end-to-endContent-Encoding(e.g.gzip). Tarballs are already compressed, so the fetcher now requests them withAccept-Encoding: identity(matching pnpm v10's effective behavior) and, as defense in depth against misbehaving servers, no longer enforces the strictContent-Lengthcheck when the response declares aContent-Encoding—Content-Lengthin that case refers to the encoded payload, not the decoded bytes the fetch implementation yields #11506.v11.0.7Compare Source
Patch Changes
Restore the execute bit on the
node-gypshims packed inside@pnpm/exe(dist/node-gyp-bin/node-gyp,dist/node-gyp-bin/node-gyp.cmd, anddist/node_modules/node-gyp/bin/node-gyp.js). Without this,pnpm/action-setup's standalone path (used on runners with Node.js < 22.13) failed any install whose lifecycle script invokednode-gyp rebuildwithsh: 1: node-gyp: Permission denied#11483.Fixed the
pn,pnpx, andpnxaliases failing in Git Bash / MSYS2 on Windows when pnpm was installed via@pnpm/exe(or afterpnpm self-update) #11486. Runningpnpx(orpnx) printed the cmd.exe banner and dropped the user into an interactive command prompt instead of runningpnpm dlx. Thebinfield rewrite on Windows was pointing those aliases at.cmdfiles; cmd-shim's Bash shim for a.cmdtarget wraps it inexec cmd /C ..., and MSYS2 mangles/Cinto a Windows path before cmd.exe sees it. The aliases are now.exehardlinks of the SEA binary, which detects which name it was launched as viaprocess.execPathand prependsdlxforpnpx/pnx.Fix
pnpm installrecreatingnode_modulesafterpnpm fetch.pnpm fetchrecords emptyhoistPatternandpublicHoistPatternin.modules.yaml; since v11 removed the explicit-config gate, the follow-up install treated those as a hoist-pattern change and purged the modules directory. The fetch step now flags the modules manifest withvirtualStoreOnly: trueso the next install skips the hoist-pattern comparison and completes the missing post-import linking in place #11488.Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.
A new
gitHosted: truefield is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.Allow user-level preferences in the global
config.yaml. The following settings can now be set in~/.config/pnpm/config.yaml(or viapnpm config set --location global) instead of being restricted topnpm-workspace.yaml:agent,globalVirtualStoreDir,initPackageManager,initType,registrySupportsTimeField,scriptShell,shellEmulator,sideEffectsCache,sideEffectsCacheReadonly,stateDir,strictDepBuilds,trustPolicy,trustPolicyExclude,trustPolicyIgnoreAfter,updateNotifier,useStderr,verifyDepsBeforeRun,verifyStoreIntegrity,virtualStoreDir,virtualStoreDirMaxLength#11474.Make trusted publishing (OIDC) take precedence over a configured static
_authTokeninpnpm publish, mirroring the npm CLI's behavior. When OIDC succeeds, the OIDC-derived token overrides any pre-configured_authToken; when OIDC is not applicable (no CI environment, exchange fails, registry has no trusted publisher configured), the static token is used as a fallback. This applies on every package during recursive publish, so each workspace package independently attempts trusted publishing.Additionally, the
NPM_ID_TOKENenv var is now honored as a CI-agnostic injection point for an OIDC ID token. Previously OIDC was only attempted on GitHub Actions or GitLab; now any CI provider that exposes its own OIDC mechanism (e.g. CircleCI'sCIRCLE_OIDC_TOKEN_V2, Buildkite, etc.) can forward its token viaNPM_ID_TOKENand trusted publishing will work without pnpm needing to recognize the provider explicitly.--pm-on-fail=ignore(and other universal options like--loglevel,--reporter) is now honored when combined with--helpor--version. Previously the CLI argument parser short-circuited those flags before universal options were preserved, sopnpm audit --pm-on-fail=ignore --helpandpnpm --pm-on-fail=ignore --versionreported the strict packageManager mismatch instead of running the requested action #11487.Fix a regression where
pnpm --recursive --filter '!<pkg>' run/exec/test/addwould include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative--filterarguments are provided, matching the documented behavior. To include the root, pass--include-workspace-root#11341.Restore npm-CLI-compatible
--jsonstdout output forpnpm publish(#11476). pnpm 11 reimplemented publish natively (#10591) and inadvertently dropped the per-package JSON object that pnpm 10 emitted transitively via the npm CLI, silently breaking downstream tooling — most notablynx release publish, which parses stdout JSON to confirm success (nrwl/nx#35575). On success, the output is now:pnpm publish --json→ single object{ id, name, version, size, unpackedSize, shasum, integrity, filename, files, entryCount, bundled }, mirroringnpm publish --json.pnpm publish -r --json→ array of those objects, mirroringpnpm pack --json's shape choice.pnpm publish -r --report-summary→ existingpnpm-publish-summary.jsonenvelope{ publishedPackages: [...] }is preserved, but each entry is upgraded to the same per-package shape (additive —nameandversionare still present).pnpm config get @​<scope>:registrynow reports the same URL thatpnpm publishand the resolvers actually use. Previously,config getonly consulted.npmrc, whilepublish/install used the merged map that includespnpm-workspace.yaml'sregistriesblock — so the two could diverge silently and a publish could go to the wrong registry #11492.v11.0.6Compare Source
Patch Changes
pnpm_config_npmrc_auth_fileandpnpm_config_userconfigenv vars not actually loading the custom.npmrc. The env vars were parsed and assigned to the resolved config, but only afterloadNpmrcConfighad already read the default~/.npmrc— so the custom file path was set but never read. The relevant env vars are now consulted before the user-level.npmrcis loaded #11465.pnpm-workspace.yamlwhen updating it. Existing keys keep their position, and new keys are inserted in alphabetical position when the existing keys are already sorted (with a leadingpackageskey allowed) or appended at the end otherwise.pnpm self-updateon installations originally set up by pnpm v10. v10 addedPNPM_HOMEdirectly to PATH and wrote apnpmbootstrap shim there. v11 setup writes shims underPNPM_HOME/bininstead, so when a v10 user upgrades to v11 the legacy shim atPNPM_HOMEkeeps pointing into the old.tools/<version>install —pnpm --versioncontinues to report the pre-update version even though the new version was installed underglobal/v11. Self-update now detects this layout, refreshes the legacy shims so the upgrade actually takes effect, and prints a hint suggestingpnpm setupto migrate PATH to the v11 layout. #11464.nodeLinker,hoistPattern) are present inconfig.yamland silently ignored. Previously these settings were dropped without any feedback, leaving users unsure why their global configuration had no effect. The warning suggests moving those settings to a project-levelpnpm-workspace.yaml, or sharing them across projects via config dependencies.overrideshas an invalid shape or contains a non-string value.readPackagedependency map fields, includingdevDependencies, and reject falsy non-object invalid values instead of silently accepting them.pnpm config,pnpm set, andpnpm getby toleratingconfigDependenciesinstall failures. For these commands, a failure to installconfigDependencies(for example because the registry auth token has not been written yet) is now logged at debug level and the command proceeds. All other commands still surface the install error #10684.allowBuildsas an install-state input and clear previously ignored builds when they are explicitly disallowed.catalog:protocol with thepnpm dlx/pnpxcommand, resulting in a catalog entry not found error.PNPM_CONFIG_*(uppercase) environment variables in addition topnpm_config_*. Previously, only the lowercase form was honored, so env vars renamed per the v11 migration guide (e.g.PNPM_CONFIG_USERCONFIG) silently had no effect on case-sensitive systems like macOS and Linux #11465.v11.0.5Compare Source
Patch Changes
Drop the
darwin-x64artifact from@pnpm/exeand from the GitHub release page. The Node.js SEA mechanismpnpm pack-appuses produces a binary that segfaults at startup on Intel Macs because of an upstream Node.js bug (nodejs/node#62893, tracked alongside #59553; the Node.js team has opted not to fix it on the grounds that x64 macOS is being phased out). Re-signing withcodesignorldiddoesn't help — the corruption is in LIEF's Mach-O surgery, before signing.Intel Mac users should install pnpm via
npm install -g pnpm(uses the system Node.js, no SEA), or stay on pnpm 10.x.@pnpm/exe's preinstall on Intel Mac now exits with a clear error pointing at these alternatives.Closes #11423.
pnpm dlx(andpnpx/pnx/pnpm create) now runs the same interactiveapprove-buildsprompt aspnpm add -gwhen the package being launched depends on transitive packages with install scripts. Previously, the v11strictDepBuildsdefault made dlx fail withERR_PNPM_IGNORED_BUILDSand required users to re-run with--allow-build=<pkg>for every offending dependency. dlx also now removes the partially-populated cache directory when the install fails, so a subsequent run starts clean instead of reusing a broken install whose builds were silently skipped #11444.72629fc: Fixpnpm -g ls --jsonandpnpm -g ls --parseableso they emit valid JSON and parseable output respectively, matching pnpm 10 behavior. Since the isolated global packages refactor in pnpm 11, the global list command had a custom path that always printed plain text and ignored--json/--parseable, which broke tools likenpm-check-updatesthat parse the JSON output #11440.pnpm -g ls --depth=<n>(with n > 0) now errors when more than one isolated global install would be involved, since each install has its own lockfile and merging their transitive trees would be incoherent. When the request can be narrowed to a single install group, the regularlistflow is used and the full dependency tree is shown.Fixed
pnpm publishto honorpublishConfig.registryfrompackage.jsonwhen publishing a single package. The native publish flow introduced in v11 was reading the registry from.npmrconly, ignoring the per-package override #11419.When
strictPeerDependenciesistrue, theERR_PNPM_PEER_DEP_ISSUESerror once again renders the peer dependency issues inline using the same format aspnpm peers check, so users (and CI tools like Renovate) can see what failed without runningpnpm peers checkseparately #11439.The
WARNand error code labels in pnpm's output now wrap in brackets ([WARN],[ERR_PNPM_FOO]). Previously the labels relied entirely on a colored background to stand out, which meant they blended into the surrounding text in terminals without color (e.g. whenNO_COLORis set or output is piped). The brackets are painted in the same color as the badge background, so they appear as ordinary padding in color-capable terminals — only the no-color rendering changes.v11.0.4Compare Source
Patch Changes
pnpm cinot reinstalling workspace packagenode_modulesdirectories after the clean step #11427.pnpm ciperforms a fresh install after the clean step.pnpm-lock.yamlduringpnpm cleanwhenlockfile: trueis configured inpnpm-workspace.yaml. The lockfile is only removed when the--lockfileoption is passed topnpm clean.pnpm self-update(with no version argument) no longer downgrades pnpm when the registry'slatestdist-tag points to an older release than the currently active version. Runpnpm self-update latestto force a downgrade #11418.minimumReleaseAgeStrictnow defaults totruewhenever the user explicitly setsminimumReleaseAge(viapnpm-workspace.yaml, the globalconfig.yaml, the CLI, orpnpm_config_*env vars).v11.0.3Compare Source
Patch Changes
node_modules/.bin#11412.ERR_PNPM_FETCH_404when installing a project whose lockfile depends on afile:tarball. The previous behavior dropped thetarballfield fromfile:and git-hosted resolutions whenlockfile-include-tarball-url=false(the default), even though those URLs cannot be reconstructed from the package name, version, and registry #11407.v11.0.2Compare Source
Patch Changes
ENOENTsymlink failure whenpnpm add -gtriggers the approve-builds prompt. The global add flow used to forward an absolutemodulesDir(<installDir>/node_modules) into the install run byapprove-builds. The install layer treatedmodulesDiras a path relative tolockfileDirand joined it again, producing a doubled path on Windows becausepath.joindoes not collapse an embedded absolute path. The hoist step then tried tomkdirand symlink under<installDir>\<installDir>\node_modules\.pnpm\node_modules\...and failed withENOENT#11403.packageManagerDependenciesgoing stale when pnpm is invoked through corepack. The lockfile sync (and thedevEngines.packageManagerversion check) previously ran only when pnpm was invoked directly; under corepack the entire block was skipped, so a stale entry would persist even after the running pnpm version changed. The lockfile sync now runs regardless of how pnpm was invoked, while the pnpm-managed version switch (onFail: 'download') remains skipped under corepack so it doesn't fight corepack's own version selection #11397.publishConfig.directorywhen packages publish from a generated directory #11239.os/cpuentries (e.g.["!win32"]) being incorrectly rejected whensupportedArchitecturesexpands to multiple platforms #11375.v11.0.1Compare Source
Patch Changes
pnpm runscripts.nullnamed catalogs in workspace manifests withInvalidWorkspaceManifestErrorinstead of crashing with a rawTypeError.pnpm sbomemittedNOASSERTION(SPDX) and omitted the distribution reference (CycloneDX) for git dependencies. Now emits the git URL with commit hash, e.g.git+https://github.com/user/repo.git#commit.pnpm self-updatenow keepspackage.json'spackageManageranddevEngines.packageManagerin sync. When the legacypackageManagerfield pins pnpm, both fields are rewritten to the new exact pnpm version on update —packageManagertopnpm@<version>(without an integrity hash), anddevEngines.packageManager.versionto the same exact<version>(dropping any range operator). When onlydevEngines.packageManageris declared, the existing range-preserving behavior is unchanged #11388.pnpm audit --fixso that the log output order matches the order written topnpm-workspace.yaml.packageManagerDependenciesentry whendevEngines.packageManagerdeclares a pnpm version that the lockfile no longer satisfies. Previously, the stale entry was kept even though the running pnpm matched the declared version, silently breaking the integrity record #11387.v11.0.0Compare Source
Highlights
Major
minimumReleaseAgedefaults to 1 day (newly published packages are not resolved for 24h) andblockExoticSubdepsdefaults totrue.allowBuildsreplaces the old build-dependency settings —onlyBuiltDependencies,onlyBuiltDependenciesFile,neverBuiltDependencies,ignoredBuiltDependencies, andignoreDepScriptshave been removed.pnpm add -ggets its own directory with its ownpackage.json,node_modules, and lockfile.pnpm publish,login,logout,view,deprecate,unpublish,dist-tag, andversionno longer delegate to the npm CLI, and the remaining npm passthrough commands now throw "not implemented".pnpm audituses npm's bulk advisories endpoint — the legacy/security/auditsendpoints are gone. CVE-based filtering has been replaced with GHSA-based filtering: migrateauditConfig.ignoreCvesentries toauditConfig.ignoreGhsas..npmrcis auth/registry only — all other settings must live inpnpm-workspace.yamlor the new globalconfig.yaml, and environment variables use thepnpm_config_*prefix.node@runtime:<version>no longer extracts the bundlednpm,npx, andcorepack, roughly halving the files pnpm has to hash, write, and link.Minor
pnpm ci,pnpm sbom,pnpm clean,pnpm peers check,pnpm runtime set,pnpm docs/home,pnpm ping,pnpm search,pnpm star/unstar/stars,pnpm whoami,pnpm with, andpnpm pack-app, pluspn/pnxshort aliases..pnpmfile.mjs, which takes priority over.pnpmfile.cjswhen present.pnpm audit --fix=updatefixes vulnerabilities by updating packages in the lockfile instead of adding overrides, andpnpm audit --fix --interactivelets you select which advisories to fix.pnpm pack-apppacks a CommonJS entry into a standalone executable for one or more target platforms using Node.js Single Executable Applications.Major Changes
Requirements
Security & Build Defaults
Changed default values:
optimisticRepeatInstallis nowtrue,verifyDepsBeforeRunis nowinstall,minimumReleaseAgeis now1440(1 day), andminimumReleaseAgeStrictisfalse. Newly published packages will not be resolved until they are at least 1 day old. This protects against supply chain attacks by giving the community time to detect and remove compromised versions. To opt out, setminimumReleaseAge: 0inpnpm-workspace.yaml#11158.strictDepBuildsistrueby default.blockExoticSubdepsistrueby default.Removed deprecated build dependency settings:
onlyBuiltDependencies,onlyBuiltDependenciesFile,neverBuiltDependencies,ignoredBuiltDependencies, andignoreDepScripts#11220.Use the
allowBuildssetting instead. It is a map where keys are package name patterns and values are booleans:truemeans the package is allowed to run build scriptsfalsemeans the package is explicitly denied from running build scriptsSame as before, by default, none of the packages in the dependencies are allowed to run scripts. If a package has postinstall scripts and it isn't declared in
allowBuilds, an error is printed.Before:
After:
Removed
allowNonAppliedPatchesin favor ofallowUnusedPatches.Removed
ignorePatchFailures; patch application failures now throw an error.Store
<algo>-<digest>). Using hex format improves performance since file paths in the content-addressable store use hex representation, eliminating base64-to-hex conversion during path lookups.package.jsonfrom the content-addressable store during resolution and installation. This reduces I/O and speeds up repeat installs #10473.$STORE/index/, package metadata is stored in a single SQLite database at$STORE/index.dbwith MessagePack-encoded values. This reduces filesystem syscall overhead, improves space efficiency for small metadata entries, and enables concurrent access via SQLite's WAL mode. Packages missing from the new index are re-fetched on demand #10500 #10826.Global Packages
Global installs (
pnpm add -g pkg) andpnxnow use the global virtual store by default. Packages are stored at{storeDir}/linksinstead of per-project.pnpmdirectories. This can be disabled by settingenableGlobalVirtualStore: false#10694.Isolated global packages. Each globally installed package (or group of packages installed together) now gets its own isolated installation directory with its own
package.json,node_modules/, and lockfile. This prevents global packages from interfering with each other through peer dependency conflicts, hoisting changes, or version resolution shifts.Key changes:
pnpm add -g <pkg>creates an isolated installation in{pnpmHomeDir}/global/v11/{hash}/pnpm remove -g <pkg>removes the entire installation group containing the packagepnpm update -g [pkg]re-installs packages in new isolated directoriespnpm list -gscans isolated directories to show all installed global packagespnpm install -g(no args) is no longer supported; usepnpm add -g <pkg>insteadGlobally installed binaries are now stored in a
binsubdirectory ofPNPM_HOMEinstead of directly inPNPM_HOME. This prevents internal directories likeglobal/andstore/from polluting shell autocompletion whenPNPM_HOMEis on PATH #10986. After upgrading, runpnpm setupto update your shell configuration.Breaking changes to
pnpm link:pnpm link <pkg-name>no longer resolves packages from the global store. Only relative or absolute paths are accepted. For example, usepnpm link ./fooinstead ofpnpm link foo.pnpm link --globalis removed. Usepnpm add -g .to register a local package's bins globally.pnpm link(no arguments) is removed. Usepnpm link <dir>with an explicit path instead.Configuration
pnpm no longer reads all settings from
.npmrc. Only auth and registry settings are read from.npmrcfiles. All other settings (likehoistPattern,nodeLinker,shamefullyHoist, etc.) must be configured inpnpm-workspace.yamlor the global~/.config/pnpm/config.yaml#11189.Network settings (
httpProxy,httpsProxy,noProxy,localAddress,strictSsl,gitShallowHosts) are now written toconfig.yaml(global) orpnpm-workspace.yaml(local) instead of.npmrc/auth.ini. They are still readable from.npmrcfor easier migration from the npm CLI #11209.pnpm no longer reads
npm_config_*environment variables. Usepnpm_config_*environment variables instead (e.g.,pnpm_config_registryinstead ofnpm_config_registry).pnpm no longer reads the npm global config at
$PREFIX/etc/npmrc.pnpm loginwrites auth tokens to~/.config/pnpm/auth.ini.New
registriessetting inpnpm-workspace.yaml:Auth tokens in
~/.npmrcstill work — pnpm continues to read~/.npmrcas a fallback for registry authentication. The newnpmrcAuthFilesetting can be used to point to a different file instead of~/.npmrc.Replace workspace project specific
.npmrcwithpackageConfigsinpnpm-workspace.yaml.A workspace manifest with
packageConfigslooks something like this:Or this:
pnpm no longer reads settings from the
pnpmfield ofpackage.json. Settings should be defined inpnpm-workspace.yaml#10086.pnpm config get(without--json) no longer prints INI formatted text. Instead, it prints JSON for objects and arrays, and raw strings for strings, numbers, booleans, and nulls.pnpm config get --jsonstill prints all types of values as JSON, as before.pnpm config get <array>now prints a JSON array.pnpm config listnow prints a JSON object instead of INI formatted text.pnpm config listandpnpm config get(without argument) now hide auth-related settings.pnpm config listandpnpm config get(without argument) now show top-level keys as camelCase. Exception: keys that start with@or//are preserved (their cases don't change).pnpm config getandpnpm config listno longer load non-camelCase options from the workspace manifest (pnpm-workspace.yaml).Removed Commands & npm Passthrough
pnpm no longer falls back to the npm CLI. Commands that were previously passed through to npm (
access,bugs,docs,edit,find,home,issues,owner,ping,prefix,profile,pkg,repo,search,set-script,star,stars,team,token,unstar,whoami,xmas) and their aliases (s,se) now throw a "not implemented" error, with a suggestion to use the npm CLI directly #10642. Other previously passed-through commands —view(info,show,v),login(adduser),logout,deprecate,unpublish,dist-tag, andversion— have been reimplemented natively in pnpm (see New Commands below).pnpm publishnow works without thenpmCLI.The One-time Password feature now reads from
PNPM_CONFIG_OTPinstead ofNPM_CONFIG_OTP:If the registry requests OTP and the user has not provided it via the
PNPM_CONFIG_OTPenvironment variable or the--otpflag, pnpm will prompt the user directly for an OTP code.If the registry requests web-based authentication, pnpm will print a scannable QR code along with the URL.
Since the new
pnpm publishno longer callsnpm publish, some undocumented features may have been unknowingly dropped. If you rely on a feature that is now gone, please open an issue at https://github.com/pnpm/pnpm/issues. In the meantime, you can usepnpm pack && npm publish *.tgzas a workaround.Removed the
pnpm servercommand #10463.Removed support for the
useNodeVersionandexecutionEnv.nodeVersionfields.devEngines.runtimeandengines.runtimeshould be used instead #10373.Removed support for
hooks.fetchers. We now have a new API for custom fetchers and resolvers via thefetchersfield ofpnpmfile.Lifecycle Scripts
npm_config_*environment variables from the pnpm config during lifecycle scripts. Only well-knownnpm_*env vars are now set, matching Yarn's behavior #11116.CLI Output
$ commandinstead of> pkg@version stage path\n> command, and shows project name and path only when running in a different directory. The$ commandline is printed to stderr to keep stdout clean for piping #11132.pnpm peers checkto view the issues #11133.Lockfile
patchedDependencieslockfile format fromRecord<string, { path: string, hash: string }>toRecord<string, string>(selector to hash). Existing lockfiles with the old format are automatically migrated #10911.Other
The default value of the
typefield in thepackage.jsonfile of the project initialized bypnpm initcommand has been changed tomodule.Added support for lowercase options in
pnpm add:-d,-p,-o,-e#9197.When using the
pnpm addcommand only:-pis now an alias for--save-prodinstead of--parseable-dis now an alias for--save-devinstead of--loglevel=infoThe root workspace project is no longer excluded when it is explicitly selected via a filter #10465.
Audit
pnpm auditnow calls npm's/-/npm/v1/security/advisories/bulkendpoint. The legacy/-/npm/v1/security/audits{,/quick}endpoints have been retired by the registry, so the legacy request/response contract is no longer supported.The bulk endpoint does not return CVE identifiers. CVE-based filtering has been replaced with GitHub advisory ID (GHSA) filtering:
auditConfig.ignoreCves→auditConfig.ignoreGhsas(the previous key is no longer recognized)pnpm audit --ignore <id>/pnpm audit --ignore-unfixablenow read and write GHSAs instead of CVEsurl(https://github.com/advisories/GHSA-xxxx-xxxx-xxxx)To migrate: replace each
CVE-YYYY-NNNNNentry in yourauditConfig.ignoreCveswith the correspondingGHSA-xxxx-xxxx-xxxxvalue (visible in theMore infocolumn ofpnpm auditoutput) and move it underauditConfig.ignoreGhsas.Package Manager Settings
Breaking: removed the
managePackageManagerVersions,packageManagerStrict, andpackageManagerStrictVersionsettings. They existed only to derive theonFailbehavior for the legacypackageManagerfield, and thepmOnFailsetting introduced alongsidepnpm withsubsumes all three — it directly sets theonFailbehavior of bothpackageManageranddevEngines.packageManager. TheCOREPACK_ENABLE_STRICTenvironment variable is no longer honored (it only gatedpackageManagerStrict); usepmOnFailinstead.Migration:
managePackageManagerVersions: truepmOnFail: download(default)managePackageManagerVersions: falsepmOnFail: ignorepackageManagerStrict: falsepmOnFail: warnpackageManagerStrictVersion: truepmOnFail: errorCOREPACK_ENABLE_STRICT=0pmOnFail: warnRuntime Installs
node@runtime:<version>(includingpnpm env useandpnpm runtime set node) no longer extracts the bundlednpm,npx, andcorepackfrom the Node.js archive. This cuts roughly half of the files pnpm has to hash, write to the CAS, and link during installation, making runtime installs noticeably faster. Users who still neednpmcan install it as a separate package.Minor Changes
New Commands
pnpm view(info,show,v) command for viewing package metadata from the registry #11064.pnpm login(andpnpm adduseralias) command for authenticating with npm registries. Supports web-based login with QR code as well as classic username/password login #11094.pnpm logoutcommand for logging out of npm registries. Revokes the authentication token on the registry and removes it from the local auth config file #11213.pnpm deprecateandpnpm undeprecatecommands for setting and removing deprecation messages on package versions without delegating to the npm CLI #11120.pnpm unpublishcommand. Supports unpublishing specific versions, version ranges via semver, and entire packages with--force#11128.pnpm dist-tagcommand (ls,add,rmsubcommands) #11218.pnpm sbomcommand for generating Software Bill of Materials in CycloneDX 1.7 and SPDX 2.3 JSON formats #9088.pnpm cleancommand that safely removesnode_modulesdirectories from all workspace projects #10707. Use--lockfileto also removepnpm-lock.yamlfiles.pnpm runtime set <runtime name> <runtime version spec> [-g]for installing runtimes. Deprecatedpnpm env usein favor of the new command.pnpm audit --fix=update#10341.pnpm cicommand for clean installs #6100. The command runspnpm cleanfollowed bypnpm install --frozen-lockfile. Designed for CI/CD environments where reproducible builds are critical. Aliases:pnpm clean-install,pnpm ic,pnpm install-clean#11003.pnpm peers checkcommand that checks for unmet and missing peer dependency issues by reading the lockfile #7087.versioncommand natively in pnpm to support workspaces andworkspace:protocols correctly. The new command allows bumping package versions (major, minor, patch, etc.) with full workspace support and git integration #10879.pnpm audit --fixnow supports a new interactive mode via--interactive/-i.pnpm docscommand and its aliaspnpm home. This command opens the package documentation or homepage in the browser. When the package has no valid homepage, it falls back tohttps://npmx.dev/package/<name>.pnpm pingcommand to test registry connectivity. Provides a simple way to verify connectivity to the configured registry without requiring external tools.searchcommand and its aliases (s,se,find).star,unstar,stars, andwhoamicommands.pnpm with <version|current> <args...>command. Runs pnpm at a specific version (or the currently active one) for a single invocation, bypassing the project'spackageManageranddevEngines.packageManagerpins.pnpm pack-appcommand that packs a CommonJS entry file into a standalone executable for one or more target platforms, using the Node.js Single Executable Applications API under the hood.Configuration
Added support for a global YAML config file named
config.yaml.Configuration is now split into two categories:
rcfile and local.npmrc.config.yamland localpnpm-workspace.yaml.Added support for loading environment variables whose names start with
pnpm_config_into config. These environment variables override settings frompnpm-workspace.yamlbut not CLI arguments.Added support for reading
allowBuildsfrompnpm-workspace.yamlin the global package directory for global installs.Added support for
pnpm config get globalconfigto retrieve the global config file path #9977.Added a new setting
virtualStoreOnlythat populates the virtual store without creating importer symlinks, hoisting, bin links, or running lifecycle scripts. This is useful for pre-populating a store (e.g., in Nix builds) without creating unnecessary project-level artifacts.pnpm fetchnow uses this mode internally #10840.Added support for specifying the pnpm version via
devEngines.packageManagerinpackage.json. Unlike thepackageManagerfield, this supports version ranges. The resolved version is stored inpnpm-lock.yamland reused if it still satisfies the range #10932.Added a new
dedupePeerssetting that reduces peer dependency duplication. When enabled, peer dependency suffixes use version-only identifiers (name@version) instead of full dep paths, eliminating nested suffixes like(foo@1.0.0(bar@2.0.0)). This dramatically reduces the number of package instances in projects with many recursive peer dependencies #11070.Config dependencies are now installed into the global virtual store (
{storeDir}/links/) and symlinked intonode_modules/.pnpm-config/. This allows config dependencies to be shared across projects that use the same store, avoiding redundant fetches and imports #10910. Config dependency and package manager integrity info is now stored inpnpm-lock.yamlinstead of inlined inpnpm-workspace.yaml: the workspace manifest contains only clean version specifiers forconfigDependencies, while the resolved versions, integrity hashes, and tarball URLs are recorded in the lockfile as a separate YAML document. The env lockfile section also storespackageManagerDependenciesresolved during version switching and self-update. Projects using the old inline-hash format are automatically migrated on install #10912 #10964.Added
nodeDownloadMirrorssetting to configure custom Node.js download mirrors inpnpm-workspace.yaml. This replaces thenode-mirror:<channel>.npmrcsetting, which is no longer read #11194:pnpm dlxandpnpm createnow respect security and trust policy settings (minimumReleaseAge,minimumReleaseAgeExclude,minimumReleaseAgeStrict,trustPolicy,trustPolicyExclude,trustPolicyIgnoreAfter) from project-level configuration #11183.pnpm initnow writes adevEngines.packageManagerfield instead of thepackageManagerfield wheninit-package-manageris enabled.Added a new setting
runtimeOnFailthat overrides theonFailfield ofdevEngines.runtime(andengines.runtime) in the root project'spackage.json. Accepted values:ignore,warn,error,download. For example, settingruntimeOnFail=downloadmakes pnpm download the declared runtime version even when the manifest does not setonFail: "download".Added a new setting
minimumReleaseAgeIgnoreMissingTime, which istrueby default. When enabled, pnpm skips theminimumReleaseAgematurity check if the registry metadata does not include thetimefield. Set tofalseto fail resolution instead.Store
Hooks & Pnpmfiles
.mjsextension. When.pnpmfile.mjsexists, it takes priority over.pnpmfile.cjsand only one is loaded #9730.CLI & Other
clean,setup,deploy, andrebuildcommands now prefer user scripts over built-in commands. When a project'spackage.jsonhas a script with the same name,pnpmexecutes the script instead of the built-in command. Addedpurgeas an alias for the built-incleancommand, which always runs the built-in regardless of scripts #11118.-Fas a short alias for the--filteroption..are hidden and cannot be run directly viapnpm run. They can only be called from other scripts. Hidden scripts are also omitted from thepnpm runlisting #11041.pnpm approve-buildsnow accepts positional arguments for approving or denying packages without the interactive prompt. Prefix a package name with!to deny it. Only mentioned packages are affected; the rest are left untouched #11030.allowBuildsare automatically added topnpm-workspace.yamlwith a placeholder value, so users can manually set them totrueorfalse#11030.pnandpnxshort aliases forpnpmandpnpx(pnpm dlx) #11052.pnpm store prunenow displays the total size of removed files #11047.pnpm audit --fixnow adds the minimum patched version for each advisory tominimumReleaseAgeExcludeinpnpm-workspace.yaml, so the security fix can be installed without waiting forminimumReleaseAge#11216.optimisticRepeatInstallskipsshouldRefreshResolutionhooks #10995.Performance
node-fetchwith nativeundicifor HTTP requests throughout pnpm #10537.node_modules, avoiding the overhead of creating a temp dir and renaming per package #11088.gunzipSyncchunk size for fewer buffer allocations during tarball decompression #11086.If-Modified-Sincefor conditional metadata fetches, avoiding re-downloading unchanged registry metadata #11161.minimumReleaseAge, reducing the amount of data fetched from the registry #11160.Patch Changes
Switched to
process.stderr.writeinstead ofconsole.errorfor script logging #11140.Respected the
frozen-lockfileflag when migrating config dependencies #11067.Removed the
--workspaceflag from theversioncommand #11115.Handled
ENOTSUPerror in the clone import path during parallel I/O #11117.Fixed
pnpm auditcommand.Updated dependencies to fix vulnerabilities.
pnpm now checks whether a package is installable for non-npm-hosted packages (e.g., git or tarball dependencies) after the manifest has been fetched.
pnpm now explicitly passes the path of the global
rcconfig file tonpm.Fixed YAML formatting preservation in
pnpm-workspace.yamlwhen running commands likepnpm update. Previously, quotes and other formatting were lost even when catalog values didn't change.Closes #10425
The parameter set by the
--allow-buildflag is now written toallowBuilds.Fixed a bug in which specifying
filterinpnpm-workspace.yamlwould cause pnpm to not detect any projects.Deferred patch errors until all patches in a group are applied, so that one failed patch does not prevent other patches from being attempted.
pnpm now fails on incompatible lockfiles in CI when frozen lockfile mode is enabled #10978.
Fixed
strictDepBuildsandallowBuildschecks being bypassed when a package's build side-effects are cached in the store #11039.In GVS mode,
pnpm approve-buildsnow runs a full install instead of rebuild, ensuring that GVS hash directories and symlinks are updated correctly after changingallowBuilds#11043.Fixed a crash in the lockfile merger when merging non-semver version strings (e.g.
link:,file:, git URLs) #11102.Handled
ENOTSUPerror inlinkOrCopyduring parallel imports #11103.Skipped linking bins that already reference the correct target. This avoids redundant I/O during repeated installs and prevents permission errors when the store is read-only (e.g. Docker layer caching, CI prewarm, NFS) #11069.
Fixed
_passwordhandling for the default registry to decode from base64 before use, consistent with scoped registry behavior #11089.Fixed a bug where the CAS locker cache was not updated when a file already existed with correct integrity #11085.
Prevented catalog entries from being removed by
cleanupUnusedCatalogswhen they are referenced only from workspaceoverrides#11075.Resolved patch file paths during
pnpm fetch#11054.Fixed invalid specifiers for peers on all non-exact version selectors #11049.
Fixed false "Command not found" error on Windows when the command exists but exits with a non-zero exit code #11000.
Prepended
Bearerto the authorization token generated bytokenHelperif it is missing, aligning with npm's behavior #11097.Propagated error cause when throwing
PnpmErrorin@pnpm/npm-resolver#10990.Fixed SQLite race condition during store initialization on Windows.
Removed
rimrafSyncinimportIndexedDirfast-path error handler #11168.Fixed
pnpm dedupe --checkunexpectedly failing due to non-deterministic resolution #11110.Fixed empty files not being rejected in
isEmptyDirOrNothing#11182.Fixed
.bat/.cmdtoken helpers not working on Windows due to missingshell: trueoption.v10.34.4: pnpm 10.34.4Compare Source
Patch Changes
352ae48: Security: validate config dependency names and versions before using them to build filesystem paths. Apnpm-workspace.yamlwith a traversal-shapedconfigDependenciesname (such as../../PWNED) or version (such as../../../PWNED) could previously causepnpm installto create symlinks or write package files outsidenode_modules/.pnpm-configand the store. Names must now be valid npm package names and versions must be exact semver versions. See GHSA-qrv3-253h-g69c.352ae48: Reject path-traversal and reserved dependency aliases (such as../../../escape,.bin,.pnpm, ornode_modules) that come from a lockfile rather than a freshly resolved manifest. A crafted lockfile alias could otherwise be joined directly under a hoistednode_modulesdirectory, letting package files be written outside the intended install root or overwrite pnpm-owned layout.The
nodeLinker: hoistedgraph builder now validates each alias at the directory sink (safeJoinModulesDir), matching the validation pnpm already performs when resolving aliases from manifests. See GHSA-fr4h-3cph-29xv.352ae48: Preventpnpm patch-removefrom removing files outside the configured patches directory.217fbe0: Hardened the warning printed when a project.npmrcuses environment variables in registry/auth settings: the suggestedpnpm config setcommand is now only included for keys made up of shell-inert characters. Because the key comes from a repository-controlled.npmrcand a shell expands$(...), backticks, and$VAReven inside double quotes, a crafted key could otherwise have turned the suggested copy-paste command into command execution.Platinum Sponsors
Gold Sponsors
v10.34.3: pnpm 10.34.3Compare Source
⚠️ Security fix — environment variables in a project
.npmrc(action may be required)Following GHSA-3qhv-2rgh-x77r, pnpm no longer expands
${ENV_VAR}placeholders that come from a repository-controlled config file, because a malicious repository could otherwise use them to leak your environment secrets (npm tokens, CI job tokens, etc.) to an attacker-controlled registry during install. This applies to:.npmrc—registry,@scope:registry, proxy URLs, URL-scoped keys (//host/…), and credential values (_authToken,_auth,_password,username,tokenHelper,cert,key);pnpm-workspace.yaml.This release also closes a bypass where a project
.npmrccould setuserconfig,globalconfig, orprefixto make pnpm load a repo-supplied file as trusted config (via@pnpm/npm-conf@3.0.3).Environment variables are still expanded in trusted config: your user-level
~/.npmrc, the global config, CLI options, and environment config.If your authentication broke after upgrading, move the token out of the committed
.npmrc:Or keep the
${NPM_TOKEN}line but put it in your user-level~/.npmrcinstead of the repo. In GitHub Actions,actions/setup-nodewithregistry-urlalready writes a user-level.npmrc, soNODE_AUTH_TOKENkeeps working. For other CI where editing each pipeline is hard, setNPM_CONFIG_USERCONFIG=.npmrcin the CI environment to declare the project.npmrctrusted.See https://pnpm.io/npmrc for full migration details.
Patch Changes
.npmrcuses an environment variable in a registry/proxy URL or in registry credentials. The message now explains why the setting was ignored and how to migrate it to a trusted source — for example by runningpnpm config set "<key>" <value>to store it in the global config, or by keeping the${...}line in the user-level~/.npmrc— with a link to https://pnpm.io/npmrc..npmrccan no longer redirect which files pnpm loads as its trusted user and global configuration. Previously such a file could setuserconfig,globalconfig, orprefixto point at an attacker-supplied file shipped in the repository, and pnpm would load it as a trusted config source — bypassing the protection that prevents repository config from expanding environment variables into registry request destinations and credentials, and allowing it to settokenHelper. The user/global config file locations are now resolved only from trusted sources (CLI options, environment config, the npm builtin config, and defaults) before the project and workspace.npmrcfiles are read. Fixed by upgrading@pnpm/npm-confto3.0.3.Platinum Sponsors
Gold Sponsors
v10.34.2: pnpm 10.34.2Compare Source
⚠️ Security fix — environment variables in a project
.npmrc(action may be required)Following GHSA-3qhv-2rgh-x77r, pnpm no longer expands
${ENV_VAR}placeholders that come from a repository-controlled config file, because a malicious repository could otherwise use them to leak your environment secrets (npm tokens, CI job tokens, etc.) to an attacker-controlled registry during install. This applies to:.npmrc—registry,@scope:registry, proxy URLs, URL-scoped keys (//host/…), and credential values (_authToken,_auth,_password,username,tokenHelper,cert,key);pnpm-workspace.yaml.This release also closes a bypass where a project
.npmrccould setuserconfig,globalconfig, orprefixto make pnpm load a repo-supplied file as trusted config (via@pnpm/npm-conf@3.0.3).Environment variables are still expanded in trusted config: your user-level
~/.npmrc, the global config, CLI options, and environment config.If your authentication broke after upgrading, move the token out of the committed
.npmrc:Or keep the
${NPM_TOKEN}line but put it in your user-level~/.npmrcinstead of the repo. In GitHub Actions,actions/setup-nodewithregistry-urlalready writes a user-level.npmrc, soNODE_AUTH_TOKENkeeps working. For other CI where editing each pipeline is hard, setNPM_CONFIG_USERCONFIG=.npmrcin the CI environment to declare the project.npmrctrusted.See https://pnpm.io/npmrc for full migration details.
Patch Changes
packageManagerfield, the registry it fetches from (and the proxy/TLS settings used for that traffic) now come exclusively from trusted config sources — CLI options, env config, user and global.npmrc— defaulting to the public npm registry, instead of the repository's project/workspace settings.packageManagerfield (orpnpm self-update) makes pnpm download another pnpm version, the staged install is verified corepack-style: the integrity recorded in the staged lockfile must carry a valid npm registry signature for the exactname@version, validated against npm's public signing keys that ship embedded in the pnpm CLI. Verification fails closed — a tampered download, an unsigned package, or an unreachable registry refuses the version switch rather than running an unverified binary. It runs only when the wanted version is actually downloaded (a tools-directory cache miss), so repeated commands pay no extra network round trip..npmrcandpnpm-workspace.yaml) can no longer expand${...}placeholders in registry/proxy request destinations, URL-scoped keys, or registry credential values, preventing repository-controlled configuration from exfiltrating environment secrets through request URLs. Trusted user/global/CLI/env config keeps full env expansion, so existing token and registry setup flows continue to work.binnames ("",".","..", and scoped forms such as@scope/..) when resolving a package's bins. These names previously passed the bin-name guard and, when joined to the global bin directory during global remove/update/add operations, could resolve to the global bin directory itself or its parent and have it recursively deleted.onlyBuiltDependencies(andallowBuilds) entries can approve lifecycle scripts for git, git-hosted tarball, direct tarball, and local directory artifacts. To approve one of those artifacts explicitly, use its peer-suffix-free lockfile depPath as the key. Lockfile entries are now rejected when a registry-style dependency path (name@semver) is backed by a git, directory, or git-hosted tarball resolution (ERR_PNPM_RESOLUTION_SHAPE_MISMATCH), so the dependency path is a reliable artifact identity by the time scripts can run.SHASUMS256.txtagainst the Node.js release team's public keys (embedded in the pnpm CLI) before trusting its hashes. The Node.js download mirror is repository-configurable (node-mirror:<channel>in.npmrc), and the integrity check previously trusted aSHASUMS256.txtfetched from that same mirror — a circular check that a malicious mirror could satisfy with a tampered binary and matching hashes. A mirror that proxies the real signed SHASUMS keeps working unchanged. Only thereleasechannel publishes signed SHASUMS files, so pre-release channels (rc, nightly, …) remain unverified.Platinum Sponsors
Gold Sponsors
v10.34.1: pnpm 10.34.1Compare Source
Patch Changes
pnpm-lock.yamlentries whose remote tarballresolution:block is missing theintegrityfield. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that stripsintegrity:) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under--frozen-lockfile. pnpm now fails closed at lockfile-read time withERR_PNPM_MISSING_TARBALL_INTEGRITY. Git-hosted tarballs (gitHosted: trueor a URL on codeload.github.com / bitbucket.org / gitlab.com) andfile:tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes.Platinum Sponsors
Gold Sponsors
v10.34.0: pnpm 10.34Compare Source
Minor Changes
Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously,
pnpm install(non-frozen) would logERR_PNPM_TARBALL_INTEGRITY, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile.pnpm installnow exits withERR_PNPM_TARBALL_INTEGRITYand a hint pointing at the new opt-in flag.The only opt-in is
pnpm install --update-checksums— narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable.--forceandpnpm updatedeliberately do not bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide.--frozen-lockfilebehavior is unchanged.--fix-lockfilekeeps its documented purpose (filling in missing lockfile entries) and is also not a bypass.Patch Changes
_authToken,_auth,username/_password,tokenHelper, inlinecert/key) to the registry declared in the same config source at load time, so a later layer overridingregistry=(workspace.npmrc,pnpm-workspace.yaml, CLI--registry) cannot redirect a credential or client certificate authored for a different host. A deprecation warning is emitted whenever an unscoped per-registry setting is encountered, naming the source and the URL it was pinned to. Reported by JUNYI LIU.minimumReleaseAgehandling when cached metadata is abbreviated. The npm registry returns abbreviated package metadata (without the per-versiontimefield) by default, which made the maturity check throwERR_PNPM_MISSING_TIMEwhenever cached abbreviated metadata was reused. pnpm now upgrades cached abbreviated metadata to the full document via a follow-up fetch whenminimumReleaseAgeis active, persists the upgrade to the on-disk cache so subsequent installs skip the extra fetch, and letsERR_PNPM_MISSING_TIMEfrom the cache fast-path fall through to the network fetch even under strict mode.commitfield is not a 40-character hexadecimal SHA before invokinggit. A malicious lockfile could otherwise smuggle a value such as--upload-pack=<command>throughgit fetch/git checkout, which on SSH or local-file transports executes the supplied command.diff --githeaders reference paths outside the patched package directory. Previously a malicious.patchfile added via a pull request could write, delete, or rename arbitrary files reachable by the user runningpnpm install.--prefix=<dir>not being honored when locating the workspace root. The--prefix → dirrename was applied after workspace detection, so workspace settings declared in<dir>/pnpm-workspace.yamlwere not loaded when pnpm was invoked from outside<dir>#11535.@x/../../../../../.git/hooks) when reading them from a package manifest or symlinking them intonode_modules. A malicious registry package could otherwise use a transitive dependency key to makepnpm installcreate symlinks at attacker-chosen paths outside the intendednode_modulesdirectory.Platinum Sponsors
Gold Sponsors
v10.33.4: pnpm 10.33.4Compare Source
Patch Changes
Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.
A new
gitHosted: truefield is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.Fix a regression where
pnpm --recursive --filter '!<pkg>' run/exec/test/addwould include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative--filterarguments are provided, matching the documented behavior. To include the root, pass--include-workspace-root#11341.Platinum Sponsors
Gold Sponsors
v10.33.3: pnpm 10.33.3Compare Source
Patch Changes
@pnpm/exeto v11+ on Intel macOS (darwin-x64),pnpm self-updatenow transparently switches to the JS-onlypnpmpackage on npm instead of installing@pnpm/exe@v11+(which doesn't ship a working binary for Intel Macs because of an upstream Node.js SEA bug — see #11423 and nodejs/node#62893). Without this, the self-update would silently leave the user with no workingpnpmbinary. The new install requires Node.js to be available onPATH; a warning is printed when the swap happens. All other host/version combinations are unchanged.pnpm self-update(with no version argument) no longer downgrades pnpm when the registry'slatestdist-tag points to an older release than the currently active version. Runpnpm self-update latestto force a downgrade #11418.Platinum Sponsors
Gold Sponsors
v10.33.2: pnpm 10.33.2Compare Source
Patch Changes
Globally-installed bins no longer fail with
ERR_PNPM_NO_IMPORTER_MANIFEST_FOUNDwhen pnpm was installed via the standalone@pnpm/exebinary (e.g.curl -fsSL https://get.pnpm.io/install.sh | sh -) on a system without a separate Node.js installation. Previously, whenwhich('node')failed duringpnpm add --global, pnpm fell back toprocess.execPath, which in@pnpm/exeis the pnpm binary itself — and that path was baked into the generated bin shim, causing the shim to invoke pnpm instead of Node #11291, #4645.Fix an infinite fork-bomb that could happen when pnpm was installed with one version (e.g.
npm install -g pnpm@A) and run inside a project whosepackage.jsonselected a different pnpm version via thepackageManagerfield (e.g.pnpm@B), while apnpm-workspace.yamlalso existed at the project root.The child's environment is now forced to
manage-package-manager-versions=false(v10) andpm-on-fail=ignore(v11+), which disables the package-manager-version handling in whichever pnpm runs as the child.Fixes #11337.
Platinum Sponsors
Gold Sponsors
v10.33.1: pnpm 10.33.1Compare Source
Patch Changes
packageManagerfield selects pnpm v11 or newer, commands that v10 would have passed through to npm (version,login,logout,publish,unpublish,deprecate,dist-tag,docs,ping,search,star,stars,unstar,whoami, etc.) are now handed over to the wanted pnpm, which implements them natively. Previously they silently shelled out to npm — making, for example,pnpm version --helpprint npm's help on a project withpackageManager: pnpm@11.0.0-rc.3#11328.Platinum Sponsors
Gold Sponsors
microsoft/TypeScript (typescript)
v6.0.3: TypeScript 6.0.3Compare Source
For release notes, check out the release announcement blog post.
Downloads are available on:
v6.0.2: TypeScript 6.0Compare Source
For release notes, check out the release announcement blog post.
Downloads are available on:
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
This PR has been generated by Mend Renovate.
fc664a6a2dtoc4c0202a48c4c0202a48to2275c060d52275c060d5todfdc488725dfdc488725to8bcd5d3e3e8bcd5d3e3etoa379cc70e1a379cc70e1to43aaacf1e043aaacf1e0tofd7a494552View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.