build: update pnpm to v11 - #284
Open
angular-robot wants to merge 1 commit into
Open
Conversation
angular-robot
requested review from
AndrewKushnir,
crisbeto and
devversion
as code owners
May 8, 2026 06:28
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
4 times, most recently
from
May 13, 2026 11:19
6e89300 to
5805a29
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
3 times, most recently
from
May 21, 2026 17:23
83250cd to
20319a6
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
3 times, most recently
from
May 28, 2026 14:15
feabe73 to
452089c
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
3 times, most recently
from
June 6, 2026 06:52
67b4c1d to
4a0240e
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
3 times, most recently
from
June 16, 2026 07:14
8b72b66 to
4fa3307
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
2 times, most recently
from
June 24, 2026 16:14
4f7a84a to
c7ddba0
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
2 times, most recently
from
July 10, 2026 20:55
7326f6b to
0483e69
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
from
July 12, 2026 21:42
0483e69 to
8ba0c25
Compare
Contributor
Author
|
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
5 times, most recently
from
July 18, 2026 22:41
0935299 to
a62c829
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
4 times, most recently
from
July 24, 2026 18:00
d6c01f2 to
ebde8a1
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
3 times, most recently
from
August 4, 2026 15:17
8e5213b to
ab17cd5
Compare
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
from
August 10, 2026 14:50
ab17cd5 to
c6741e9
Compare
See associated pull request for more information.
angular-robot
force-pushed
the
ng-renovate/pnpm-11-x
branch
from
August 16, 2026 17:35
c6741e9 to
b077c6a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
10.33.0→11.22.0Release Notes
pnpm/pnpm (pnpm)
v11.22.0: pnpm 11.22Compare Source
Minor Changes
Added
pnpm cache path, which prints the directory pnpm uses for its metadata cache. CI setups can use it to cache that directory — including the lockfile verification log, which lets a job skip re-checking an unchanged lockfile against the configured supply-chain policies.--config.config-dirno longer reaches the config through a project'spnpm-workspace.yaml, and neither do the--config.spellings of the other settings a project manifest may no longer contribute (--config.pnpm-home-dir,--config.workspace-dir,--config.global-pkg-dir,--config.root-project-manifest-dir). None of them was ever a supported way to set those directories: pnpm resolves them from the environment, and these flags took effect only because the project-manifest merge re-applied the command line afterwards. The dedicated flags, such as--dirand--global-dir, are unaffected #13629.pnpm config setrefuses to write a setting to a project'spnpm-workspace.yamlthat pnpm does not read from there, rather than leaving a key in the file that does nothing. Those settings areconfigDir,pnpmHomeDir,stateDirand the others that name machine-level state. The command fails withERR_PNPM_CONFIG_SET_NOT_A_PROJECT_SETTING, naming where the setting does belong when it belongs somewhere.pnpm config deletestill clears one that a file already carries, in whichever spelling it uses #13629.Added a new setting
minimumReleaseAgeExcludePrune. When enabled,pnpm add,pnpm update, andpnpm removeprune the entries ofminimumReleaseAgeExcludeinpnpm-workspace.yamlthat the freshly written lockfile no longer resolves: versions that are gone are dropped (an entry is removed once none of its versions remain), and entries for packages that are no longer in the lockfile are removed too. Name patterns (@scope/*) are always kept. The cleanup is skipped when the install's lockfile does not cover the whole workspace (sharedWorkspaceLockfile: false), since entries another project still needs would look stale.Renamed
cleanupUnusedCatalogstocatalogPrune, so that catalog pruning and release-age exclude pruning use one vocabulary.cleanupUnusedCatalogscontinues to work; when both are set,catalogPrunewins.A project's
pnpm-workspace.yamlcan no longer choose where pnpm keeps its credentials, its own installation, or the registry it downloads its next version from. One of those settings isconfigDir, which decided wherepnpm loginwrites the granted token.bin,dir,globalBinDir,globalDir,npmrcAuthFile,pnpmHomeDir,stateDir,userconfigandworkspaceDirare ignored there now too, and pnpm warns about the ones it finds.cacheDirandstoreDirare unaffected #13629.Resolving a Node.js runtime version (
devEngines.runtime/runtime:specifiers) is now much faster: the per-version release metadata is cached in the pnpm cache directory after its signature is verified, and an exact stable version such asruntime:22.23.2no longer downloads the Node.js release index. A pinned runtime whose metadata was fetched once resolves without any network access, which removes the noticeable delay on the firstnodeinvocation in a project pinning an already-downloaded runtime #13899.Patch Changes
Fixed intermittent
ERR_PNPM_ENOENTandERR_PNPM_ENOTEMPTYerrors while renaming_tmp_*directories during installation withnodeLinker: hoisted, in workspaces that also usepatchedDependencies.pnpm addno longer re-resolves the dependency graph whenpnpm-lock.yamlalready holds a version satisfying the request — promoting a transitive dependency to a direct one, or adding to a second workspace package what a first one already depends on, now only saves the dependency inpackage.jsonand records its importer entry. A satisfying locked version is necessary but not sufficient: the install still falls back to a full resolution for a dist tag, an alias, aworkspace:/catalog:/git/tarball specifier,--save-peer, an overridden package, acatalogModeother thanmanual, and — underresolutionMode: time-basedorlowest-direct, which resolve a direct dependency to the low end of its range — a range several locked versions satisfy.Global installs now switch over atomically. The command shims in the global bin directory point at a stable per-package link rather than at the directory a particular install produced, so
pnpm add -gandpnpm update -gactivate a new version by moving that one link instead of rewriting every shim. A command can no longer be missing fromPATHwhile an install is in progress, and a failed install leaves the previous version in place.pnpm audit --fixandpnpm audit --fix updateno longer addminimumReleaseAgeExcludeentries for patched versions that were published before theminimumReleaseAgecutoff. The publish time of each minimum patched version is now checked against the registry metadata, and only versions young enough to be blocked by the age gate get an exclusion entry #11563.pnpm add <pkg>@<version>andpnpm update <pkg>@<version>under a non-manualcatalogModenow move the catalog entry's resolution to the requested version. Previously, when the catalog entry was a range that covered the requested version but resolved to a different one, the request was dropped silently: nothing was installed, nothing was written, and no error was raised.A project that wasn't part of an install that moved a catalog entry now follows the entry the next time it is installed. It used to keep the version the entry resolved to before — a version the entry no longer allowed — and no later install corrected it, so one catalog entry ended up resolved to two versions.
pnpm add <pkg>@<version>andpnpm update <pkg>@<version>undercatalogMode: strictno longer fail withERR_PNPM_CATALOG_VERSION_MISMATCHwhen the catalog entry is a range that the wanted version satisfies. The dependency keeps using the catalog; only a version that really falls outside the catalog's range is rejected #13715.A changed
catalogsorpnpm.overridesblock no longer has to be the only change forpnpm installto update the lockfile in place. Editing an override while also removing a dependency, or changing a catalog entry in the same commit as a range bump, is now absorbed in one pass instead of re-resolving the whole dependency graph #13799.Fixed the lockfile an in-place override update wrote when the overridden package was also a catalog entry: the entry kept the version it had before the override moved the package. The same could happen in reverse, when a catalog entry moved a package an override pins. Both cases now re-resolve instead.
pnpm installnow updates the lockfile in place even when several kinds of changes happened since the last install — for example a removed dependency together with a widenedignoredOptionalDependencieslist, or a dependency edit alongside a patch or settings change. Previously any combination of changes forced a full re-resolution #13763.pnpm deployinjects workspace dependencies again, so the deploy directory is self-contained instead of symlinking back into the source workspace #13754. EnablinginjectWorkspacePackageswithdedupeInjectedDepsdisabled now also rewrites already-linked workspace dependencies to injected copies.pnpm deploy --no-optionalno longer writes a lockfile whose snapshots reference optional dependencies that the deploy excluded.Removing the last dependency that references a catalog entry via the fast lockfile update no longer leaves the stale catalog entry in
pnpm-lock.yaml.A git dependency whose clone (or shallow fetch) fails now reports which package it belongs to, under the
ERR_PNPM_GIT_FETCH_FAILEDcode, with credentials in the repository URL redacted. When the lockfile records an SSH remote, the error also explains that fetching it needs an SSH key for that host, and that a lockfile entry written before pnpm v11.21 can be re-recorded over HTTPS withpnpm update <package>#13743.An
integrityrecorded on a git dependency's resolution (resolution: {type: git, repo, commit, integrity: sha512-…}) is no longer treated as a checksum. pnpm never verifies a git checkout against such a hash — the commit pins the content — so it is now dropped when the lockfile is rewritten, andpnpm sbomno longer republishes it as a CycloneDX/SPDX checksum. Lockfiles carrying one also load again instead of failing withERR_PNPM_BROKEN_LOCKFILE#13042.pnpm sbomnow also publishes the checksum of atype: binaryruntime archive, which pnpm does verify.A git dependency whose
git ls-remotefails now reports theERR_PNPM_GIT_RESOLVE_FAILEDcode, naming the dependency instead of printing a baregitinvocation, with credentials in the repository URL redacted. A specifier that does not ask for SSH resolves over HTTPS, because the URL recorded in the lockfile has to work on every machine that installs it, so the error explains how to substitute the transport on a machine that can only reach the host over SSH (git config --global url."git@<host>:".insteadOf "https://<host>/") #13743.A missing
gitexecutable is reported as one, instead of surfacing the raw failure to start the process.Credentials embedded in a git specifier are redacted from the "Could not resolve <ref> to a commit of <repo>" errors too.
Resolving a public repository makes one
git ls-remoteround-trip instead of two.pnpm installafter moving a dependency betweendependencies,devDependencies, andoptionalDependenciesnow updates the lockfile in place instead of re-resolving the whole dependency graph #13696.syncInjectedDepsAfterScriptsno longer fails withERR_PNPM_UNSUPPORTED_INODE_TYPEwhen a workspace package contains an inode that is neither a file nor a directory, such as the FIFO 1Password's environments create for.env. Such an inode cannot be hardlinked into the injected copy, so it is skipped and the rest of the package still syncs #13550.syncInjectedDepsAfterScriptsalso no longer fails withEEXISTwhen a workspace package replaced a file with a directory of the same name since the injected copy was last synced.syncInjectedDepsAfterScriptsno longer fails withENOTDIRwhen a workspace package replaced a directory with a file of the same name and the injected copy still held that directory's contents.syncInjectedDepsAfterScriptsnow removes the bin link of a bin the script dropped. Previously only new bins were linked, so a build step that stopped declaring one left its shim behind, pointing at a command that was no longer there.syncInjectedDepsAfterScriptsnow identifies a file by its device as well as its inode number. An inode number is only unique within one filesystem, so on its own it could match an unrelated file on another device and leave that path stale in the injected copy.pnpm store pruneno longer deletes the lockfile verification log. The log records which lockfile passed which supply-chain policies, so it stays valid across a prune of the store; keeping it lets the next install skip re-verifying an unchanged lockfile.Widening a dependency's range no longer leaves the project on an older version. The lockfile update now points the project at the highest version of that dependency already in the lockfile that satisfies the new range — matching what a full resolution records — instead of keeping the locked version whenever it happened to satisfy, which could leave a duplicate behind. A range change that only an already-locked version satisfies is now also handled without re-resolving #13778.
resolutionModeis no longer ignored whenminimumReleaseAgeis in effect.lowest-directandtime-basedpick the lowest satisfying version of a direct dependency again; previously any active release-age cutoff — including the built-in default — silently forced the highest, soresolutionModeonly worked whenminimumReleaseAge: 0was set explicitly #13752.Adding a package to a workspace no longer forces a full re-resolution when every dependency it declares is already locked for a sibling. The lockfile update writes the new project's importer entry from the versions the lockfile already holds; a dependency no locked version satisfies still reaches the resolver #13696.
pnpm config delete <key>no longer fails withENOENTwhen the config file it would edit does not exist. Clearing a setting that was never set is a no-op #13651.Changing a
pnpm.overridesentry to a version range now updates the lockfile in place when a version the lockfile already holds satisfies the range, instead of re-resolving the whole dependency graph. Only exact versions were handled before #13696.Changing a parent-scoped
pnpm.overridesentry ("parent>child": "2.0.0") now updates the lockfile in place instead of re-resolving the whole dependency graph. Only the named parent's dependency moves; every other package keeps the version it had #13795.Removing a dependency, or moving one to another already-locked version, no longer re-resolves the whole dependency graph just because some package resolves a peer with the same name. The lockfile update now compares the peer suffixes against the exact
name@versionthe removal severed, so a suffix that names a different — still present — version of that dependency is left alone #13781.Projects with a pnpmfile now use the fast lockfile update paths: an unchanged pnpmfile (proven by the recorded
pnpmfileChecksum) no longer forces a full re-resolution for removals, dependency group moves, compatible range changes, and the other in-place lockfile rewrites #13696.A lockfile entry whose resolution is unchanged no longer loses its recorded
deprecatedmarker when a registry serves the package's metadata inconsistently — re-resolving to the same version keeps the deprecation instead of silently dropping the line #13846.pnpm pruneis now recursive by default inside a workspace, just likepnpm install. This fixespnpm prune --prodin a workspace root emptying thenode_modulesdirectories of the other workspace projects, dropping the links to the workspace packages they depend on in production #13718.A setting written in kebab-case in the global
config.yamlis now reported instead of being silently ignored #13650.pnpm removeno longer re-resolves the dependency graph. The removed dependency's entries are dropped frompnpm-lock.yamland anything they made unreachable is pruned, without registry access. The install still falls back to a full resolution when a surviving package resolves a peer dependency through the removed one.Removing a package from a workspace no longer forces a full re-resolution. The lockfile update drops the departed project's importer entry and prunes whatever only it depended on. A project that is still linked from a surviving project continues to be reported as an error #13696.
An install sharing a global virtual store no longer removes an incomplete package directory that another importer is still writing, which could fail with
failed to remove existing directory ... prior to swap: Directory not empty. Such a directory is now repaired in place, and a package file left damaged by an interrupted install is restored instead of being kept.pnpm sbomno longer emits components for optional platform-specific dependencies that cannot be installed on the current platform (for example, the native@rolldown/binding-*variants for other operating systems). Such packages are present in the lockfile but are never downloaded, so their license (and other metadata) could not be resolved and they appeared in the SBOM without one.pnpm sbom --lockfile-onlystill describes the whole lockfile graph, which is platform-independent by design.An
ssh://git dependency pointing at a bracketed IPv6 host, such asssh://[::1]/repo.git, is resolved now. Its colons were read as an SCP-style path separator, which turned the address into[:/1]and left the specifier unresolvable. Applies to both the TypeScript CLI and pacquet.In the TypeScript CLI, an
ssh://git dependency written without user info —ssh://git.example.com/team/repo.git,git+ssh://git.example.com:2222/team/repo.git— no longer fails withTypeError: Cannot read properties of undefined (reading 'includes'). Only theuser@hostform worked before.packageExtensionsis now validated when the configuration is read, so a malformed entry (for instance a dependency range set tonull) fails with an actionable error instead of crashing later during peer dependency resolution #13756.Projects using
resolutionMode: time-basednow benefit from the fast lockfile update paths. A removal, a dependency group move, or a compatible range change no longer forces a full re-resolution just because the lockfile carries atimefield #13696.An install that drops the last dependent of a patched package no longer updates the lockfile in place and succeeds silently. Removing a dependency, widening
ignoredOptionalDependencies, or adding a removal override could each prune the package while the patch stayed configured; such an install now falls back to a full resolution, which reports the unused patch withERR_PNPM_UNUSED_PATCH. UnderallowUnusedPatches, where the lockfile update is kept, the same install now warns that the patch went unused instead of saying nothing #13827.Platinum Sponsors
Gold Sponsors
v11.21.0: pnpm 11.21Compare Source
Minor Changes
Added interactive group selection to
pnpm update --global --interactive.Running
pnpm setup,pnpm self-update, or a command that modifies the global installation (such aspnpm add --global) throughsudonow prints a warning. pnpm keeps global packages and configuration in the invoking user's home directory, so running these commands as root silently operates on the root user's home directory instead of yours. They will fail withERR_PNPM_SUDO_NOT_SUPPORTEDin pnpm v12. Read-only global commands (such aspnpm bin --global) are unaffected.Patch Changes
Fixed pnpm failing to start under asynchronous Node.js module loaders when no
.pnpmfile.mjsexists pnpm/pnpm#11701.Fixed
minimumReleaseAgefallback for custom dist-tags so the selected version does not exceed the registry’s original tag target.Removing a dependency from
package.jsonand reinstalling no longer re-resolves the dependency graph. The importer's entry is dropped frompnpm-lock.yaml, anything it made unreachable is pruned, and a catalog entry that loses its last referent is removed — all without registry access. Installs still fall back to a full resolution when a package that stays resolves a peer dependency through the removed one, since that would change the surviving package's entry rather than only prune.Changing a catalog entry to a different exact version no longer re-resolves the dependency graph. The package is replaced in
pnpm-lock.yamldirectly, reusing the same check thepnpm.overridesfast path applies: every locked dependency of the package must still satisfy the new version's manifest. Installs fall back to a full resolution when anything other than the catalog reaches the package — an importer that depends on it directly, or another package that depends on it — since the graph would then need both versions.Fixed a CI regression where
github:owner/repodependencies (and other shorthand Git specifiers) would fail to install withPermission denied (publickey)on CI runners that lack SSH keys. The Git resolver no longer records an SSH URL unless the user explicitly wrote one (e.g.git+ssh://orgit@host:...):429 Too Many Requests, so host throttling of CI runners is no longer mistaken for a private repository.git ls-remoteaccess is now tried before SSH, so a public repository whose visibility probe fails still resolves to a portable HTTPS URL instead of an SSH URL that only works where SSH keys are configured.codeload.github.com, which would fail to download for a private repository); it stays a regulargitresolution so installs can use ambient Git credentials such as credential helpers and tokens.Note that a private repository that is reachable both over authenticated HTTPS and over SSH now resolves to its HTTPS URL, where previous versions recorded the SSH URL.
Fixes pnpm/pnpm#13276.
ng buildandnuxt buildnow work under the global virtual store: pnpm's built-in compatibility extensions add thetslibdependency that@angular/builduses without declaring and theunplugindependency that@nuxt/vite-builderv4 uses without declaring.Fixed
link:dependencies underenableGlobalVirtualStoreso linked children are materialized and slots remain isolated by their resolved link targets.An install that skips resolution because
pnpm-lock.yamlis already up to date now reacts fully to packages the lockfile removed — for example after pulling a lockfile in which a dependency was deleted. The hoist layer is recomputed, so a package that became hoistable when a direct dependency was removed is hoisted, andpendingBuildsentries for removed packages are dropped instead of staying pending forever.The held-back-update warning printed by
pnpm updateno longer fires whenminimumReleaseAgeis the actual reason a newer version was not picked. The warning's baseline now applies the same maturity cutoff as the pick itself, so it no longer wrongly attributes the hold-back to "your manifests and already installed dependencies" or recommends an override that would defeat the age gate. See #13071.Checking whether
ignoredOptionalDependenciesis up to date no longer reorders the configured patterns. The check sorted them in place, which could move an!exclusion ahead of the pattern it excludes from and flip which optional dependencies were ignored.Changing
autoInstallPeers,dedupePeers,peersSuffixMaxLength,excludeLinksFromLockfile, orinjectWorkspacePackagesno longer re-resolves the dependency graph when the lockfile proves the setting cannot affect it: no package or project declares a peer dependency for the peer settings, and no project depends on a directory or on another workspace project for the link and injection settings. The new setting is recorded inpnpm-lock.yamland the install proceeds from the existing resolution. Every other case still falls back to a full resolution.Adding, editing, or removing an entry in
patchedDependenciesno longer re-resolves the dependency graph. Resolution never reads a patch — it only records the patch file's hash against the package it matches — so the install now rewrites the affected entries inpnpm-lock.yamland materializes the patched package from the store instead. Installs still fall back to a full resolution when the patched package is reachable as a peer dependency, and when the new configuration would leave a patch unused whileallowUnusedPatchesis off, soERR_PNPM_UNUSED_PATCHis still reported.Resolving a private git repository no longer blocks on an interactive credential prompt:
git ls-remotenow fails fast with an authentication error when git has no credentials for the repository #13522.Lockfile verification now honors offline mode by using cached registry metadata instead of reaching the registry. When the required metadata is not available locally, verification reports the same
ERR_PNPM_NO_OFFLINE_METAcondition used by offline resolution.POSIX shell shims now follow symbolic links before computing
basedir, preventing execution failures when a shim is invoked via an external symlink onPATH#13405.The automatic
packageManagerversion switch works again on registries whose tarball URLs point at a different host than the registry itself (load-balanced feed proxies, Artifactory-style mirrors). Package-manager entries are now always recorded with integrity-only resolutions — the download URL is derived from the trusted bootstrap registry instead — and entries persisted in an invalid shape by an earlier pnpm are discarded and re-resolved instead of failing every command #13619.Registries that serve no npm signature metadata (private mirrors and feed proxies commonly strip
dist.signatures) no longer break the automaticpackageManagerversion switch andpnpm self-update#13147. When the configured registry cannot provide a verifiable signature, pnpm now fetches the signature fromregistry.npmjs.organd verifies it against the same embedded npm keys over the installed integrity — which proves exactly the same thing. If no signature can be obtained from either source (for example, both are unreachable, or the registry publishes only ashasum), pnpm proceeds with a warning instead of failing, but only when the packages resolve through a registry configured in the user's own (non-project) configuration; the download stays pinned by the lockfile integrity, and a signature that exists but does not validate still fails the switch.pnpm fetch, and any install run withvirtualStoreOnly, no longer writes a.pnp.cjsloader undernodeLinker: pnp. These installs populate the virtual store without linking the project, so the loader would have claimed the project resolves out of a store it was never linked into. The importer links andnode_modules/.package-map.jsonwere already skipped; the PnP loader now follows the same rule.Prevent pnpm from removing project files when
modulesDirresolves to the project root.Speed up installs after adding
ignoredOptionalDependenciespatterns by removing newly ignored optional dependencies and pruning packages that are no longer reachable without resolving the dependency graph again.When a failed install re-copies a bin script from the store, rerunning
pnpm installnow reapplies the executable bit to the bin instead of leaving it non-executable #12742.pnpm root -gandpnpm bin -gnow print warnings to stderr instead of stdout, so their stdout stays a clean, machine-readable path. Previously, running either command with--globalin a project that pins a package manager (e.g. via thepackageManagerfield) printed a warning like[WARN] Using --global skips the package manager check for this projectahead of the path, breaking programs that capture the output as a path #13672.In pnpm 12,
pnpm root -gandpnpm prefix -gare now supported (they previously failed withERR_PNPM_CLI_ROOT_GLOBAL_UNSUPPORTED/ERR_PNPM_CLI_PREFIX_GLOBAL_UNSUPPORTED), and the reporter output ofdlx,create,config,sbom,with,store,prefix,root, andbingoes to stderr, matching pnpm 11.pnpm setupno longer makes Node.js print aMODULE_TYPELESS_PACKAGE_JSONwarning aboutdist/worker.json every command. Thepackage.jsonit writes next to a standalone executable now declares"type": "module".pnpm updatewithout saving no longer records a version that the manifest's range excludes. The kept range stays authoritative: a requested version outside it is skipped with a warning, and a requested range, a dist tag, or--latestresolves within it instead of past it. Previously each of these could write a lockfile entry that contradicted its own specifier, which the nextpnpm install --frozen-lockfilerejected withERR_PNPM_OUTDATED_LOCKFILE#12764.pnpm version -r --jsonnow outputs[]instead of human-readable text when no pending changes existpnpm/pnpm#13217.Platinum Sponsors
Gold Sponsors
v11.20.0: pnpm 11.20Compare Source
Minor Changes
Security fix. Affects projects using
namedRegistrieson pnpm 11.1.0–11.19.x. It is semi-breaking for those projects — see "If you use named registries" below.The lockfile recorded no marker for which registry a package came from. Packages were keyed by
name@versionalone, and entry lookup went throughrefToRelative(ref, name), so a dependency you declared against one registry could be satisfied by an entry that was actually resolved from another. When two registries served the same name and version, both collapsed onto a singlepackages:entry and whichever resolved first decided the tarball every consumer got.That is a package-substitution risk: a package you expect from your private registry could be installed from a different registry that publishes the same name and version, and the lockfile recorded nothing that would let you tell.
Packages resolved from a named registry are now recorded under registry-qualified keys (
<name>@<registryName>:<version>, e.g.foo@work:1.0.0), so each registry gets its own entry and the lockfile pins which one a dependency came from.The lockfile format version is unchanged. Registry-qualified keys appear only for packages resolved from a named registry, so a project that does not use
namedRegistriessees no difference, and older pnpm versions keep reading the file.If you use named registries
Your next non-frozen install re-keys those entries, which shows up as a lockfile diff. Commit it — that diff is the fix being applied. Review it: an entry that moves to a registry you did not expect is worth investigating.
Everyone working on the project should be on this version or newer before you do. An older pnpm reads the re-keyed lockfile fine — frozen installs are unaffected — but it does not produce registry-qualified keys itself, so any install that updates the lockfile writes those entries back to the old shape, and the next install on a current pnpm re-qualifies them. The result is a lockfile that flips back and forth, and while it is in the old shape the project is exposed again. Because the lockfile format version is deliberately unchanged, pnpm cannot detect this and warn you about it.
There is no setting to keep the old behavior: the old shape is the vulnerability.
Tarball URLs that follow the standard registry layout are no longer written to the lockfile for named-registry packages; they are recomputed from the
namedRegistriessetting on demand.To use named registries, map your aliases in
pnpm-workspace.yaml:New built-in
npmjs:aliasnpmjs:now resolves tohttps://registry.npmjs.org/with no configuration, alongside the existinggh:alias for GitHub Packages. It pins a dependency to the public registry even whenregistrypoints elsewhere, such as an internal proxy:{ "dependencies": { "left-pad": "npmjs:^1.3.0" } }npm:cannot do this — it is the alias protocol (npm:<name>@<range>) and resolves through whateverregistrypoints at.If you mirror or proxy npmjs, point the alias at your mirror:
Built-in registry URLs are also the prefixes a lockfile's recorded tarball URL is matched against when pnpm verifies a package. Without the override, an entry whose tarball URL is on
registry.npmjs.orgis verified against the public registry rather than your mirror. This only affects lockfiles that record such URLs — a canonical URL for your configured registry is omitted from the lockfile and unaffected — and only when a tarball-URL,minimumReleaseAge, ortrustPolicycheck runs. Overriding the alias is the same escape hatch GHES users already have forgh.Every alias the lockfile references must stay in
namedRegistries: reading an entry whose alias is gone fails withERR_PNPM_MISSING_NAMED_REGISTRYrather than silently falling back to the default registry, since that would fetch a different package. Renaming an alias re-resolves the packages that used it.Named registry aliases that shadow a reserved dependency specifier prefix (
file,link,workspace,runtime,npm,jsr, ...) are now rejected withERR_PNPM_RESERVED_NAMED_REGISTRY_NAMEinstead of being silently shadowed by the corresponding resolver.pnpm licensesandpnpm sbomnow keep the two artifacts apart as well: license records carry the registry alias, and SBOM components carry the purlrepository_urlqualifier.Patch Changes
An empty
http-proxy,https-proxy,proxy, orno-proxyvalue — from the.npmrc,pnpm-workspace.yaml, the CLI, or theHTTP_PROXY/HTTPS_PROXY/PROXY/NO_PROXYenvironment variables — no longer fails the install withERR_PNPM_INVALID_PROXY. Empty settings read as unset, so a shell exportingHTTP_PROXY=disables the proxy, and an emptyproxy=in the.npmrcno longer suppressesHTTPS_PROXY#13533.proxy=falsein the.npmrcorproxy: falseinpnpm-workspace.yamlnow turns proxying off instead of being read as a proxy host namedfalse.falseandnullonhttps-proxy/http-proxy/no-proxyread as unset, and on the command line they are ordinary host names, since a flag carries its value verbatim.The env lockfile no longer pins
@pnpm/exealongsidepnpmwhen the wanted pnpm version is 12 or newer. From v12 the unscopedpnpmpackage is itself the native executable, so@pnpm/exeis not published for it and resolving it would fail. The engine identity check now verifies the native binary through whichever package ships it.lexCompareandnerfDartare now published as@pnpm/text.ordinal-comparatorand@pnpm/config.registry-auth-key. Use these instead of@pnpm/util.lex-comparatorand@pnpm/config.nerf-dart.Fixed the order in which pnpm matches a lockfile's recorded tarball URL against known registry URLs. Two registry URLs of equal length were previously ordered arbitrarily, so which one a tarball URL matched could differ between runs.
Dependency resolution is faster: package metadata is now filtered once per packument instead of once per dependency edge when
minimumReleaseAgeis active, and parsed semver versions and ranges are reused instead of re-parsed on every comparison.Security:
pnpm rebuildnow refuses a lockfile whosepackageskey carries a path traversal in the package name (e.g.../../../escaped@1.0.0), instead of running that package's lifecycle scripts and linking its bins in a directory outside the virtual store. Such a name is rejected withERR_PNPM_INVALID_DEPENDENCY_NAME.Platinum Sponsors
Gold Sponsors
v11.19.0: pnpm 11.19Compare Source
Minor Changes
pnpm loginno longer requires an interactive terminal when the registry supports web-based login: without a TTY it prints the authentication URL (skipping the QR code and the "Press ENTER to open the URL in your browser" prompt) and polls the registry until the browser approval completes. Only the classic username/password login still fails withERR_PNPM_LOGIN_NON_INTERACTIVEin a non-interactive terminal.The
save-prefixsetting now accepts=: newly added dependencies are saved with an explicit=operator (=1.2.3) instead of the setting being silently treated as the default^.Patch Changes
allowBuildsentries can now approve git-hosted packages that pnpm downloads as a tarball, such asgithub:dependencies (which are fetched fromcodeload.github.comrather than cloned), by their repository URL without the resolved commit hash. This matches the hashlessgit+matching already supported for cloned git dependencies. For example:This approves the package whether pnpm clones it or downloads a tarball, so the entry no longer has to be updated every time the pinned commit changes. GitLab and Bitbucket tarball downloads are matched the same way. Approving or denying a specific resolved commit by its full tarball dep path continues to work.
pnpm outdated --include-github-actionsno longer blocks on an interactive git credential prompt when a workflow uses a private action repo.Prevented
minimumReleaseAgefrom replacinglatestwith a SemVer-greater version than the registry tag target #13034.Fixed empty
bundledDependenciesandbundleDependenciesarrays causing nondeterministic lockfile changes. See #13123.The install summary no longer prints
(X is available)when the registry'sdist-tags.latestis still held back by the activeminimumReleaseAgepolicy. The hint only ever names the actual latest tag, so an immature latest suppresses the hint instead of advertising the version pnpm just refused to install #11698.pnpm updatekeeps the explicit=operator of an exact version pin: a dependency saved as=3.5.1now updates to=3.5.2instead of the bare3.5.2. See #13168.Preserve a workspace dependency's
link:entry when a run does not target it — e.g.pnpm update <other-pkg>(with or without--recursive), or a plain install after a root/catalog dependency change — withinjectWorkspacePackages, instead of spuriously rewriting it to a peer-suffixedfile:protocol. See #10433.Workspace dependencies declared with a relative path (e.g.
"foo": "workspace:../foo") are no longer silently dropped from the workspace projects graph, so--filterselection and the topological order of recursive commands take them into account.Platinum Sponsors