deps: update pnpm to v12 #79
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "renovate/pnpm-12.x"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
11.21.0→12.9.1Release Notes
pnpm/pnpm (pnpm)
v12.9.1: pnpm 12.9.1Compare Source
This release moves the WebContainer build into a separate
@pnpm/wasmpackage, shrinks thepnpmpackage back to about 4 MB, and fixespnpm publishwith provenance from GitLab CI.Patch Changes
The WebAssembly build for StackBlitz WebContainers now ships as a separate
@pnpm/wasmpackage. Thepnpmand@pnpm/exepackages no longer include it, which brings their unpacked size back from about 55 MB to about 4 MB. In a WebContainer, install@pnpm/wasmwith npm to get thepnpmcommand.pnpm publishwith provenance from GitLab CI is no longer rejected by the npm registry with a 422 error. The provenance statement now includes the GitLab CI variables ininvocation.parameters, as npm does #16551.pnpm audit signaturesnow uses the TLS settings of the redirect target when a registry redirects its signing-keys request, for example to registry.npmjs.org. Acafilescoped to a private registry no longer makes the redirected request fail #16541.Fixed
pnpm install --frozen-lockfilerejecting an up-to-date lockfile when an injected workspace package uses a catalog entry inpeerDependencies#16557.The
[<since>]filter selector works again with Git 2.24 through 2.27 #16561. With Git older than 2.24, the selector now fails with an error that names the required Git version.It also detects changes in projects whose directory names contain non-ASCII characters. Such a change used to be credited to the parent project.
changedFilesIgnorePatternandtestPatternnow match changed files whose names contain non-ASCII characters.The
pnpmexecutable is about 10% smaller. On macOS arm64 it went from 45.1 MB to 40.3 MB.Sped up trust downgrade checks for packages with long release histories.
With
optimisticRepeatInstall: false,pnpm installnow runs the projects' own lifecycle scripts, such asprepare, even whennode_modulesis already up to date #16545.pnpm self-updatenow fails for Homebrew-installed pnpm and prints thebrew upgradecommand for the installed formula, such asbrew upgrade pnpmorbrew upgrade pnpm@11. It used to install a second copy of pnpm that the Homebrew one kept shadowing #16547.Platinum Sponsors
Gold Sponsors
v12.9.0: pnpm 12.9Compare Source
This release runs pnpm in StackBlitz WebContainers, adds a per-registry
networkConcurrencysetting, and records every installed project in the store. It also carries a security fix forpnpm login.Minor Changes
pnpm now automatically uses WebAssembly in StackBlitz WebContainers, including when installation scripts are disabled. Native installations continue to use the native executable when installation scripts are enabled.
A
registriesentry can now setnetworkConcurrency, the most requests pnpm keeps in flight to that registry's origin. Requests to other registries keep the overall limit. The setting may live inpnpm-workspace.yamlor the globalconfig.yaml.pnpm installnow records every project it installs in the store'sprojectsdirectory, as a symlink to the project directory. A--frozen-storeinstall without the global virtual store still records nothing. Only projects that used the global virtual store were recorded before #6929.Patch Changes
pnpm loginno longer forwards credentials in its request body to another origin during redirects.Installing packages
Fixed
pnpm installfailing on Android withERR_PNPM_STORE_DIR_ACQUIRE_OPERATION_LOCK#16508.pnpm install --frozen-lockfileagain succeeds when a workspace project recorded inpnpm-lock.yamlhas no directory, such as a project left out of a Docker build context. It still fails if the project's directory exists without apackage.json#16453.pnpm install --frozen-lockfileno longer requires apnpm-lock.yamlin a project that has no dependencies. It also succeeds whenpnpm-lock.yamlrecords only the pinned pnpm version, as other commands write it when they run before the first install #16477.Fixed
pnpm install --frozen-lockfilerejecting a fresh lockfile when an injected workspace dependency has an optional peer supplied by another workspace project #16428.With
nodeLinker: hoisted, a filtered install now keeps the packages of the workspace projects an earlier install put innode_modules. This also covers the install thatpnpm --filter <selector> runandpnpm --filter <selector> execstart before the command. Before, these installs removed every package that only the unselected projects needed #16483.pnpm installwithnodeLinker: hoistednow refreshes directories supplied by custom fetchers when reinstalling. pnpm also keeps the symlinks inside those directories.With
enableGlobalVirtualStoreon, scripts can run entry points that a CommonJS require hook loads again, such asts-node index.ts. They failed withERR_UNKNOWN_FILE_EXTENSIONon Node.js versions without built-in TypeScript support #16436.pnpm now keeps each project's current lockfile and hidden hoisted dependencies in its own
node_modules/.pnpmwhenvirtualStoreDirpoints at a shared global virtual store.--virtual-store-dirnow sets the global virtual store's location too pnpm/tasks#47.pnpm cleanno longer deletes the project whenvirtualStoreDirorglobalVirtualStoreDiris set to the project directory. It also leaves a directory outside the project alone when the setting reaches it through a symlink. It now removes a global virtual store thatglobalVirtualStoreDirplaces inside the project, as it does forvirtualStoreDir.Optional dependencies
pnpm installno longer fails when a dependency of an optional dependency is missing from the registry. Like npm, pnpm now leaves out the nearest optional dependency above it, together with its subtree #16511.When an optional dependency fails to build, pnpm now removes its link from
node_modules. A repeatpnpm installthen reports "Already up to date" and no longer reruns the failing build #16468.pnpm installnow prints a warning with the error when an optional dependency cannot be fetched and is skipped. The skipped package is no longer counted in thePackages: +Nsummary. Thepnpm:skipped-optional-dependencylog reports the skip with thefetch_failurereason #16514.Resolving dependencies
Fixed
pnpm installchanging an unchanged project's direct dependency to a sibling workspace's pinned version when its dependency tree contains a cycle #16417.With
autoDedupeenabled, downgrading a dependency in one workspace project now moves the other projects to that version when it satisfies their ranges. This also applies to a filteredpnpm --filter <project> add#16432.pnpm installandpnpm dedupenow move an optional peer to the version already in the dependency graph when no other package provides its locked version anymore. After a bump such asvue3.5.40 to 3.5.43, the lockfile kept a second copy of@vue/server-rendererfor@vue/test-utils#16443.pnpm dedupe --checkno longer fails right afterpnpm installwhen a project's optional peer is satisfied by a package another workspace project installs.pnpm dedupenow picks the same versions for that package's dependencies aspnpm install#16447.pnpm installno longer re-resolves an up-to-date lockfile on every run when a patched package is a peer in a peer cycle #16418.With
autoDedupeenabled,pnpm install --lockfile-onlyno longer resolves the dependency graph again when nothing changed since an earlier--lockfile-onlyinstall deduplicated the lockfile. Such an install keeps the lockfile even if versions were published since it was written, or if only a setting such asresolutionModechanged. Runpnpm dedupeto apply such a change #16458.Speed and network
A repeat
pnpm installin a large workspace reports "Already up to date" faster #16487.Sped up dependency resolution of workspaces with many peer dependencies.
pnpm installsends fewer registry metadata requests when the lockfile already decides which version a range resolves to. This now also covers ranges that several locked versions satisfy when one of them outranks the others, and direct dependencies kept at their locked version. Packages thatminimumReleaseAgeExcludelists without a version now reuse cached registry metadata the same way they do whenminimumReleaseAgeis not set #16458.pnpm no longer downloads every packument again on each install from a registry whose metadata responses forbid caching, such as
Cache-Control: no-store. pnpm revalidates the cached metadata with a conditional request, so a registry that supports conditional requests answers with a 304 when the package has not changed #16528.Cached metadata for a package published within
minimumReleaseAgeis now revalidated with its ETag, so the npm registry can answer304 Not Modified. Before, the next install that checked the cache downloaded the whole document again #16506.A fetch timeout while other downloads from the same host are still running now lowers concurrency for that host to one connection. Retries of that request, and later downloads from that host, use the lower concurrency. Other hosts keep the configured concurrency #12791.
Sped up
pnpm install --offlinewhen the version a range picks is not in the store. While it looks for a version the store holds, pnpm now reads only the versions the range admits #16495.pnpm install --offlinenow reuses config dependency tarballs that are already present in the store pnpm/tasks#46.Running scripts
pnpm -s <script>runs the script again, with-smeaning--sequentialas it does forpnpm run -s <script>. pnpm rejected it with "unexpected argument '-s' found" #16446.pnpm runandpnpm execnow warn and run the command when the install thatverifyDepsBeforeRunstarts fails. This lets scripts run in sandboxes where pnpm cannot install, such as containers with a read-only store or no network #15173.A filtered
pnpm runorpnpm execnow finds dependencies out of date when a workspace dependency of a selected project has nonode_modulesdirectory, as after a filtered install. WithverifyDepsBeforeRun: install, pnpm installs that dependency before running the command pnpm/tasks#45.Scripts run without a terminal no longer start a second
sheach. One watchdog per pnpm command now ends every script's process group if pnpm is killed, sopnpm -r runacross many projects starts half as many processes #16489.Terminate batch job (Y/N)?no longer appears after pressing Ctrl+C in a script started withpnpmfrom PowerShell or cmd on Windows #16502.pnpm rebuildandpnpm approve-buildsrefresh command launchers when a build changes a command's interpreter or replaces it with a native executable. Dependent packages' build scripts use the refreshed launchers.When
pnpm run <script>orpnpm <script>finds nothing to run and--filterfollows the script name, the error now suggests putting the filter option before the script name #4655.Package-name filters now support
?to match one character #2817.The pinned pnpm and
pnpm self-updatepnpm no longer downloads the project's pinned pnpm version again on every command when
nodeVersioninpnpm-workspace.yamlnames a different Node.js major than thenodeonPATH. Before, each of those commands took about a second longer and failed without network access #16497.Several
pnpmcommands started at once in a project that pinspackageManagerno longer fail withThe process cannot access the file because it is being used by another processon Windows while the pinned pnpm is being installed.pnpm can now switch to a
packageManagerversion below 11 on x64 musl Linux, such as Alpine #16467.A
devEngines.packageManagerrange no longer makes pnpm replace the version recorded inpnpm-lock.yamlwith the running pnpm while the recorded version still satisfies the range. When pnpm does record a version, it records the running pnpm only if it meetsminimumReleaseAge. Otherwise it records the newest version in the range that meets it, or the running pnpm if none does #16431.On Windows,
pnpm self-updatenow replaces apnpm.exeleft inPNPM_HOMEor inPNPM_HOME\bin. Windows ran that executable in place of the updatedpnpm.cmdshim, sopnpm --versionkept printing the old version after a successful update. If the executable was inPNPM_HOME,self-updatenow asks you to runpnpm setup#9094.pnpm self-updatenow checks that a version installed as the JavaScriptpnpmcan start before making it the globalpnpm. If Node.js is missing, the update fails and the currentpnpmstays in place.Other commands
pnpm deploynow finds patches and local dependencies when the target directory sits under a symlink, such as/tmpon macOS. It failed withERR_PNPM_PATCH_NOT_FOUND#16470.pnpm deploy --legacynow resolves the deployed project's relativefile:,link:, and path dependencies from the project's own directory #16475.pnpm update --globalnow removes hard-linked executables fromPNPM_HOMEwhen migrating packages from the old global layout #16420.pnpm store pruneno longer fails on store index entries that pnpm 11 wrote for git-hosted packages without apackage.json. Entries that still cannot be read are kept and counted in the prune summary.pnpm store prunenow aborts when an error other than a missing directory occurs while scanning project directories in the mark phase.pnpm config getandpnpm config listnow report a setting given on the command line with--config.<name>=<value>. Before, a value such as--config.node-linker=hoistedreached the install but was absent from the reported configuration #16276.pnpm -r pkg getnow reports every selected project when several share a package name. Projects with the same name are keyed by their directory relative to the workspace root. Before, only one of them appeared in the output.The install summary shows a
link:dependency as+ name <- path, and the Node.js API'shideLinkedPkgsDiffreporter option leaves matching linked dependencies out of the summary.The
--forcehelp text ofpnpm installandpnpm addnow says that--forcekeeps skipping optional dependencies built for other platforms. It points toforceIgnoresPlatformand the--os,--cpu, and--libcoptions for installing them #16435.The
homepagefield of the publishedpnpmpackage points to https://pnpm.io again.Platinum Sponsors
Gold Sponsors
v12.8.2: pnpm 12.8.2Compare Source
pnpm 12.8.2 fixes a startup crash on Linux ppc64le and
UnknownIssuererrors on systems without CA certificates.pnpm runno longer installs before every script on CI whenautoDedupeis enabled, and resolution and hoisted installs on macOS are faster.Patch Changes
Platforms and environments
Fixed pnpm crashing on startup on Linux ppc64le #16380.
Fixed installs failing with
UnknownIssueron Linux systems without CA certificates, such asnode:24-slim, whenNODE_EXTRA_CA_CERTSis set. The extra certificates now extend the bundled CA roots #16365.pnpm now creates its store operation locks and other per-user lock files in
$XDG_RUNTIME_DIRwhen it points to a directory only the user can write to. Otherwise, pnpm still uses/tmpon Linux and macOS. Sandboxes that block writes to/tmpcan pointXDG_RUNTIME_DIRat a writable directory #16390.POSIX bin shims and the
pnpm,pn,pnpx, andpnxlaunchers now run inside a Nix build, where the system default path holds none of the utilities they call. Installing again replaces the shims already innode_modules#16377.In a project that pins another pnpm version, pnpm now passes a command with an option it does not know to the pinned version. Before, pnpm rejected the option before switching, so
pnpm install --auto-dedupefailed with "Unknown option" even though the pinned pnpm supports it #16353.Installing and resolving dependencies
pnpm install --frozen-lockfilenow fails whenCargo.lockdoes not satisfy a dependency requirement inCargo.toml. The error names the crate and the version the lockfile holds #16355.pnpm installreturns "Already up to date" again in a workspace with injected workspace dependencies and a shared lockfile. Since 12.7.0 every repeat install in such a workspace ran the full install and copied the injected projects again.With
injectWorkspacePackages: true, a freshpnpm installnow records a workspace dependency aslink:when its injected copy differs from the project only by an optional peer that peer-dependent dedupe merges. It was recorded as a peer-suffixedfile:copy #16354.pnpm dedupe --checknow passes right afterpnpm dedupewhen deduplication merges variants of a package that differ only in their peers. A lockfile key whose peer suffix named a merged variant now names the variant that replaced it #16356.When
minimumReleaseAgehides the version thatlatestpoints to, pnpm now falls back to a prerelease of the same major before a stable version of an older major. A stable version of the same major is still preferred. Before, while a new1.0.0was too new,latestfell back to an old0.0.1even though1.0.0-beta.4had beenlatestuntil then #16388.Git-hosted dependencies now respect
pmOnFail. If it is set to anything other thandownload, a git-hosted dependency that pins a pnpm version is prepared by the running pnpm, and pnpm does not download the pinned version #16376.pnpmfile hooks such as
readPackagenow run once for a dependency that several packages request at the same time. They could run twice for it before.childConcurrencynow defaults to 5, the documented value. It used to be capped at 4 and to follow the host's CPU count.Running scripts
pnpm runandpnpm execno longer install dependencies before every script on CI whenautoDedupeis enabled.pnpm install --frozen-lockfilenow keeps the deduplication record left by an earlier install #16374.On macOS and Linux, lifecycle scripts and
pnpm runnow always getPATHfrom thePATHvariable. When the environment also held aPathvariable, a script sometimes gotPath's value, and failed withnode: not found#16308.pnpm runnow exits after aSIGTERMin a container where pnpm is PID 1 and the script runs pnpm again, as"start": "pnpm serve"does. Since 12.6.0 it kept waiting after the script had shut down, until the container runtime killed it.Other commands and settings
pnpm config get globalShims,pnpm shim list, and global installs no longer readglobalShimsfrom a project'spnpm-workspace.yaml. Only the global config file, the pnpm home's ownpnpm-workspace.yaml, andPNPM_CONFIG_GLOBAL_SHIMSset it, so a repository cannot choose which globally installed packages get project-aware shims.pnpm config set --location=projectrefuses a machine-level setting such asstateDirorscopewithERR_PNPM_CONFIG_SET_NOT_A_PROJECT_SETTING, which names where the setting belongs.pnpm config deletestill clears such a key from a project'spnpm-workspace.yaml.pnpm deployno longer fails withERR_PNPM_DEPLOY_AMBIGUOUS_PEERin a workspace withinjectWorkspacePackages: truewhen a workspace package lists its peer dependency as a dev dependency too #16375.pnpm deployno longer copies the workspace root'spackageManageranddevEngines.packageManagerfields into the deployedpackage.json#16403.pnpm publishnow includes bareREADMEfiles and README files with Markdown extensions such asreadme.markdownin registry metadata #12704.pnpm store prunenow removes the packages that only expiredpnpm dlxcache entries used. They were left in the store until the nextpnpm store prune#16383.Performance
Sped up dependency resolution in large workspaces, and when many dependencies request different ranges of the same package. Resolution also uses less memory.
Sped up
pnpm installwithnodeLinker: hoistedon macOS when the lockfile is re-resolved, such as withautoDedupeenabled #16397.Sped up extracting package tarballs.
pnpm installwithout--frozen-lockfileis faster on some machines in projects with apnpm-workspace.yaml. Those installs linked with one worker thread per core, half of what a frozen install uses.On Windows, warm
pnpm install --frozen-lockfileruns are 4-5% faster on 4- and 8-core machines. pnpm now links with one worker thread per core on Windows, between 4 and 16. This changes frozen installs and installs in projects without apnpm-workspace.yamlon machines with 3 to 15 cores.Platinum Sponsors
Gold Sponsors
v12.8.1: pnpm 12.8.1Compare Source
pnpm 12.8.1 fixes
pnpm install --frozen-lockfilerejecting lockfiles with injected workspace packages that have peers, restores the executable bit on files of local directory dependencies, makespnpm dedupeconverge, and uses less CPU on many-core machines.Patch Changes
pnpm install --frozen-lockfileno longer rejects a freshly generated lockfile when an injected workspace package has peer dependencies #16332.Executable files in a
file:directory dependency or an injected workspace package keep their executable bit again. Since 12.8.0, pnpm installed these files without the permissions they have in their project.pnpm dedupenow reaches a stable lockfile when a package's peer suffix is long enough to be hashed. Before, each run could switch that package's key between the hashed and the spelled-out suffix, sopnpm dedupe --checkalways failed #16331.pnpm install --frozen-lockfile, the default in CI, now uses less CPU on machines with more than 8 cores. Warm installs on many-core Windows machines got up to 10% faster. Frozen installs now link with at most 16 worker threads.verifyDepsBeforeRunno longer reports dependencies as outdated after a filtered install just becausepnpm-lock.yamlhas a newer modification time. It checks the lockfile against the packages that install put in place. Before,pnpm runreinstalled the whole workspace with lifecycle scripts on, for example after a DockerCOPYbrought in a lockfile with a newer mtime #16322.After a filtered install,
verifyDepsBeforeRunnow also checks that the install put the selected projects' dependencies in place. Anode_modulesdirectory alone no longer counts as proof.pnpm runandpnpm execno longer install a project that has never been installed and has nothing to install. Such a project declares no dependencies, no peer dependencies thatautoInstallPeerswould fetch, and no install lifecycle scripts. The command now runs without writingnode_modulesorpnpm-lock.yaml#16313.pnpm update -g --latestnow upgrades globally installed packages beyond their saved version ranges #16320.Platinum Sponsors
Gold Sponsors
v12.8.0: pnpm 12.8Compare Source
pnpm 12.8.0 warns when
pnpm packorpnpm publishwould ship a.envfile thatfilesdoes not list, installssharedWorkspaceLockfile: falseworkspaces concurrently, applies every setting passed as--config.<name>=<value>, and no longer leaves the Windows terminal stuck after Ctrl+C in a script.Minor Changes
pnpm packandpnpm publishnow warn when the tarball includes a.envor.env.*file that thefilesfield ofpackage.jsondoes not list. Templates such as.env.exampleare not reported. List the file infilesto publish it on purpose, or exclude it in.npmignoreor.gitignore#7826.pnpm packnow honors--silent,--reporter=silent, and--loglevel=silentto hide the tarball contents and summary. With--json, lifecycle script output and the final JSON output remain visible #10297.Patch Changes
Installing packages
Installing through a
pnprserver now records the pnpmfile checksum in the lockfile, so a laterpnpm install --frozen-lockfileaccepts that lockfile #14460. A frozen install through the pnpr server now fails if the pnpmfile changed. If the pnpmfile defines areadPackage,afterAllResolvedorpreResolutionhook or custom resolvers, pnpm resolves dependencies locally and prints a warning that the pnpr server was not used.Installing through a
pnprserver also links a workspace project at the directory itspublishConfig.directorynames. A server that does not forward the setting makes the install fail withERR_PNPM_PNPR_PUBLISH_DIRECTORY_MISMATCH, so pnpm never writes a lockfile that points at the wrong directory. The server rejects apublishConfig.directorythat points outside its project.Installing a git-hosted dependency that has to be built no longer fails when that dependency's own dependencies have build scripts nobody approved. pnpm skips those builds while preparing the dependency, as it does without
strictDepBuilds#9764.A git-hosted dependency that is a pnpm workspace with no committed lockfile is now detected as a pnpm project #14011.
pnpm install --devandpnpm fetch --devnow install the optional dependencies of devDependencies, such as the platform binaries of Biome and oxlint. The project's ownoptionalDependenciesare still skipped #9678.pnpm install --offlineandpnpm add --offlinenow resolve a version range to the newest matching version whose tarball is already in the store. They used to pick the newest version in the cached metadata and fail withERR_PNPM_NO_OFFLINE_TARBALLwhen its tarball was missing #10715.If an offline install fails because the registry metadata cache uses the layout from before pnpm 11.27 and 12.4, the error now names the older mirror on disk and explains that one online install repopulates the cache. The error also carries the
ERR_PNPM_NO_OFFLINE_METAcode.pnpm cache prune --helpnow says that pnpm 11.26 and earlier, and pnpm 12.3 and earlier, depend on the directories it removes #15656.Running
pnpm installnow refreshes dependencies when a package declared with a localfile:directory changes its dependencies #4623.A repeat
pnpm installnow keeps its fast up-to-date check when an override replaces a declared localfile:dependency #12892.pnpm installnow removes an optional dependency fromnode_modulesif its install script fails. Code that checks whether the package is installed no longer finds a package that cannot load #8756.With
nodeLinker: hoisted,pnpm installnow restores a workspace project'snode_modulesafter it was deleted. Before, the install printed "Already up to date" and left the project without the dependencies nested under it. On Windows, the install also no longer fails with "Access is denied" when another project's copy of a shared dependency links to the deleted directory.Under
nodeLinker: hoisted,pnpm installnow clears orphaned package directories that an interrupted or failed install leaves in a project'snode_modules. A directory recorded by the previous install is removed, while an unrecorded directory is moved tonode_modules/.ignored. A copy already in.ignoredis never overwritten #13676.Concurrent installs no longer fail when they replace the same stale hoisted dependency link. Virtual store cleanup now keeps the temporary lockfiles that concurrent installs write.
Resolving and linking dependencies
pnpm installno longer aborts on a failed allocation of many gigabytes when peer dependency ranges combine overlapping||alternatives #15867.pnpm installno longer fails when a package from the registry declares afile:dependency on a directory inside itself, such as"@types/css-tree": "file:./typings/css-tree". pnpm links that dependency to the directory inside the package, as npm and Yarn do. The lockfile records it aslink:<root>/typings/css-tree#9141.An
npm:alias written byoverridesnow stays in place when a change elsewhere makes pnpm re-resolve the aliased dependency. Before, pnpm could look up the alias name at the aliased version, which failed withERR_PNPM_NO_MATCHING_VERSIONor locked an unrelated package #16309.A peer dependency no longer resolves to two different versions for one package. This happened when the package peer-depends on another package and on one of that package's peers, and it is installed deeper than a direct dependency of the package that provides them #12098.
An optional peer dependency is no longer resolved from another workspace project's package when the project provides one of that package's own peers at a version it rejects. This avoids bogus unmet peer errors #13989.
pnpm dedupeno longer changes the lockfile on every run when a nested peer dependency is provided through an npm alias #15709.With
resolutionMode: time-basedandminimumReleaseAgeboth set,pnpm installno longer reports a subdependency as too new when only the time-based cutoff excludes it. Such subdependencies used to fail a strict install withERR_PNPM_NO_MATURE_MATCHING_VERSION, or were added tominimumReleaseAgeExclude#13569. A transitive dependency that has no matching version published before the time-based cutoff now resolves to the lowest matching version allowed byminimumReleaseAge. pnpm picks a version younger thanminimumReleaseAgeonly if no older version matches #16298.pnpm installretries registry metadata fetches that fail with a timeout, a dropped connection, or an interrupted response body before it appliestrustPolicyorminimumReleaseAge. A transient fetch failure is not reported asTRUST_DOWNGRADEorMINIMUM_RELEASE_AGE_VIOLATION#12031.pnpm's built-in package compatibility database no longer applies to a project's own manifest. A project named like a published package, such as
vue-loader, no longer gains dependencies onpnpm installorpnpm update. User-configuredpackageExtensionsstill apply to project manifests #11700.Packages in an external
virtualStoreDircan resolve the project's direct dependencies selected byhoistPattern. Runpnpm install --forceto repair an existing installation #5652.pnpm installnow links the executables of auto-installed peer dependencies into the workspace root'snode_modules/.bin, including after a frozen-lockfile reinstall #8511.Lockfiles and frozen installs
pnpm install --frozen-lockfilenow works on a detached HEAD whengitBranchLockfileis enabled. The install reads the lockfiles of the local and remote-tracking branches that contain the checked-out commit. It still writes the sharedpnpm-lock.yaml#7672.pnpm install --frozen-lockfilenow accepts a lockfile that has no importer entry for a workspace package without dependencies. Such a package added after the lockfile was written made the install fail withERR_PNPM_PACKAGE_MANAGER_NO_IMPORTER#15875.pnpm installnow fails withERR_PNPM_LOCKFILE_MISSING_DEPENDENCYwhen an importer references a dependency version that has no snapshot entry. Before, the install succeeded and left anode_modulessymlink pointing at a missing virtual-store directory #14764.pnpm installon CI now fails on an outdated lockfile whenpreferFrozenLockfileis explicitly set totrue. Setting it totrueused to let CI update the lockfile #9072.With
gitBranchLockfileenabled, each emoji or other character outside the Basic Multilingual Plane in a branch name now becomes!!in the lockfile name. Before, each such character became one!.Workspaces and filtering
pnpm installin a workspace withsharedWorkspaceLockfile: falsenow installs projects concurrently, up toworkspaceConcurrencyat a time #14480. A project is resolved, fetched, and written to its virtual store without waiting for the workspace projects it depends on. It waits for them only before it links its dependencies and runs its lifecycle scripts, so its scripts still run after theirs. A project with apreinstallorpnpm:devPreinstallscript, or with an injected orfile:workspace dependency, waits for its workspace dependencies before it starts.The installs of the projects also share their package metadata, lockfile verification, and store caches, so they use less CPU and memory when several projects depend on the same packages. An install with a pnpmfile no longer starts an extra Node.js process when the pnpmfile has no
preResolutionhook.With
enableGlobalVirtualStoreandsharedWorkspaceLockfile: false, each project now keeps its current lockfile and its hidden hoisted dependencies in its ownnode_modules/.pnpm. Before, every project wrote them to the workspace root'snode_modules/.pnpm, so each repeat install treated the other projects' packages as its own and relinked them #14480.pnpm rebuild,pnpm approve-builds, andpnpm ignored-buildsnow work on the current project'snode_moduleswhen they run inside a project of a workspace withsharedWorkspaceLockfile: false. They used to read the workspace root'snode_modules, sopnpm rebuilddid not rebuild the project's dependencies and created a second virtual store at the workspace root #9402.pnpm installno longer creates anode_modulessymlink inside thepublishConfig.directoryof a workspace package linked withlinkDirectory. A build tool that cleaned its output directory through that symlink deleted the files of the package's dependencies.pnpm installalso removes a symlink that an earlier install left there #16226. It also no longer fails withERR_PNPM_CMD_SHIM_RESOLVE_PATHwhen such a package has abinfield and itspublishConfig.directorydoes not exist yet.pnpm installno longer fails for an injected workspace dependency whose package publishes from apublishConfig.directorythat its ownpreparescript builds. The injected copy now picks up that directory oncepreparefinishes building it.pnpm install --frozen-lockfileno longer reports the dependency as outdated while the directory has not been built yet #7811.An in-place edit to the source of an injected workspace package now shows up in its injected copy, unless a build writes to that package or
packageImportMethodis set. pnpm hardlinks such packages under the default import method #4410. Scripts listed insyncInjectedDepsAfterScriptsnow update injected dependencies while they run, so a watcher on the injected package, such as a dev server, sees each change before the script exits.With
sharedWorkspaceLockfile: false, an injected workspace package that has lifecycle scripts is now hard linked into the projects that depend on it. Before, pnpm left a plain copy, so later edits to the package did not reach those projects #9828.injectWorkspacePackagesnow hard links a workspace dependency declared with a relative path, such asworkspace:../foo, the same way it already does forworkspace:*#10446.Workspace discovery prunes dot-prefixed directories, so a
packagespattern such as**no longer matches projects inside.cacheand other hidden directories #16250.pnpm importin a workspace now keeps the versions pinned by ayarn.lockinside a workspace project #4385.Store and caches
Files imported from the store now follow the umask of the install that writes them. Installing with a umask of
077no longer leaves imported files readable by the group and others #3807.pnpm installkeeps the owner, group, and mode of files already in a shared store, includingindex.db. New store files and directories inherit the store directory's group-write bit. When that directory is setgid, new files inherit its group. pnpm does not change a file's owner or group #12765.When
pnpm installrepairs a store file that was modified through a hard link innode_modules, the repair now keeps the file's inode on Linux and macOS, so hard-linked copies in other projects are healed at the same time. On Windows the repair still replaces the file, so other projects are healed on their next install #3445.pnpm installnow reports a full store at once when writing package files fails. It no longer retries the tarball #8581.pnpm now warns when it cannot hard link packages from an existing store in the pnpm home directory and falls back to a store on the project's filesystem. This can happen when the project is on another filesystem, such as a bind-mounted workspace in a container. The warning names both stores and suggests setting
storeDir#14505.The side-effects cache now restores the symlinks that a build script creates inside a package. A warm install used to replace each of them with a copy of its target #12859.
After upgrading, every package with a build script is built once more.
The global virtual store and the side-effects cache now key built packages by the Node.js version that the root project's
devEngines.runtimeorengines.runtimepins. That is the Node.js their build scripts run with. A dependency that declares its ownengines.runtimeno longer changes the key for every other package.With
enableGlobalVirtualStore, an install into a freshnode_modulesno longer runs the build scripts of a dependency whose global virtual store slot an earlier install already built.pnpm rebuildstill runs them #14480.Concurrent installs that share a global virtual store now run a package's build in its shared slot one at a time. A failed build leaves the slot in place and marks it for the next install to rebuild #15568.
A warm
pnpm installreuses on-disk package metadata for five minutes when the registry does not send an ETag. Registries that send an ETag, including the public npm registry, still revalidate with a conditional request.pnpm updatestill fetches current metadata #13976.pnpm no longer revalidates cached registry metadata when the registry sends
Cache-Control: max-age=0,no-cache, orno-store. It downloads the metadata again, so a version newly published to such a registry is visible on the next install #13487.pnpm installhonorsCache-Controlfor dependencies named with anhttp:orhttps:tarball URL. A fresh response is taken from the store with no request, and a stale one is revalidated withIf-None-Match#15648.Patched dependencies
pnpm installnow repairs apnpm-lock.yamlwhose(patch_hash=<hash>)dependency paths disagree with itspatchedDependenciesmap, including paths that lack the hash their patch calls for. Before, pnpm accepted such a lockfile as up to date and kept the old patched files.pnpm install --frozen-lockfilenow fails on such a lockfile withERR_PNPM_INCONSISTENT_PATCH_HASH. It fails withERR_PNPM_UNCHECKABLE_PATCH_HASHwhen a patch hash in the lockfile is malformed, or when the lockfile lacks the package version or patch entry that the check needs #15336.pnpm installwithnodeLinker: hoistednow applies a patch once to each copy of a patched dependency in a workspace. Before, a copy that several workspace projects shared could receive the patch twice and end up with the patched content duplicated #7565.pnpm installandpnpm fetchnow fail withERR_PNPM_PATCH_NOT_FOUNDwhen a patch file listed inpatchedDependenciesdoes not exist #5268.engineStrictnow checks the patchedpackage.jsonwhen apatchedDependenciesentry changesengines. A patch that relaxesengines.nodeno longer fails the install against the published range #9603.pnpm patchnow applies the existing patch file to the edit directory of a git-hosted dependency, as it already does for packages from the registry #9699.Adding, updating, and removing dependencies
pnpm add <dir>now warns when the added directory declares peer dependencies, aspnpm linkdoes. The directory is saved as alink:dependency, and its peers are not resolved from the project that adds it. Use thefile:protocol to have them resolved #5523.pnpm add --save-typesno longer adds a@types/*package whose resolved version is deprecated. DefinitelyTyped publishes such stubs for packages that ship their own types, such as@types/typescriptfortypescript#15636.pnpm version,pnpm add, andpnpm pkg setkeep JSON5 style when they updatepackage.json5. ASCII identifier keys stay unquoted, strings keep JSON5 quotes, and indented files keep trailing commas #15717.Running scripts and commands
pnpm runandpnpm execno longer install dependencies automatically when the rootpackage.jsonstill keepsoverrides,packageExtensions,patchedDependencies, orignoredOptionalDependenciesin itspnpmfield. pnpm no longer reads that field, so the install rewrote the lockfile without those settings. The command now fails and asks to move the settings topnpm-workspace.yaml#16278.When
verifyDepsBeforeRuntriggers an install before a filteredpnpm runorpnpm exec, pnpm now installs only the selected projects and their dependencies. A later filtered command also installs a selected project that an earlier filtered install skipped #11865.pnpm -r run /regexp/now honors thetasksdependsOndeclared for each script the selector matches, like running the script by name does. Matched scripts that depend on each other run in order. Each matched script runs once #15596.pnpm runexits with the code of a script that handles Ctrl+C and shuts down. A script that finished cleanly is not reported as a lifecycle failure. The commands after it in the same script still run #9945.pnpm no longer hangs after a lifecycle script exits while a process it started in the background keeps the script's output open. pnpm stops reading that output one second after the script exits #5730.
pnpm runand lifecycle scripts use the configuredscriptShell, including Git Bash on Windows, whenshellEmulatoris also enabled.shellEmulatorstill runs scripts whenscriptShellis not set. Extra arguments passed topnpm runare quoted for the shell that runs the script, so a Windows path stays intact #14719.With
enableGlobalVirtualStore, dependency build scripts now see the workspace root'snode_modules/.bin, as they do with a local virtual store. Apostinstallscript that runsnodefinds the Node.js installed bydevEngines.runtimeand no longer fails with "command not found" on machines without a system Node.js #15652. Dependency build scripts also see the bins of privately hoisted dependencies.Dependency install scripts now find the node-gyp bundled with pnpm when pnpm runs through a symlink, such as
node_modules/.bin/pnpmor thepnpmthatnpm install -g pnpmlinks. They used to fail withnode-gyp: command not foundon macOS #15694.pnpm runand lifecycle scripts now setnpm_config_node_gypto the bundlednode-gypentry point. Tools that read the variable resolve the samenode-gyppnpm builds with. Annpm_config_node_gypvalue the environment already sets is kept as is #16270.Scripts now see the
npm_commandenvironment variable that npm sets. It holdsrun-scriptwhen the command runs a script, and the command's own name otherwise #16265.Commands run from a POSIX shell through a dependency's own
node_modules/.bin, such asnode_modules/vite/node_modules/.bin/esbuild, no longer fail withMODULE_NOT_FOUND#10189.pnpx --versionandpnpm dlx --versionnow print the pnpm version. Other unknown options before the command are reported as errors. Before, pnpm tried to download a package named after the option #16259.pnpm dlxnow keeps the virtual store of its cached installs innode_modules/.pnpm, like every other install #13955.pnpm pack-appnow names the manifest of its runtime install directorypnpm-pack-app-<target>.Publishing, packing, and deploying
pnpm packandpnpm publishnow ship a file that thefilesfield names even when another entry excludes the directory holding it. For example,["**", "!dist", "dist/index.d.ts"]shipsdist/index.d.ts#16213.pnpm packprunes a directory that afilesfield exclusion names, such as!**/test, excluding the directory and its contents from the packed package #15738.pnpm publishnow waits at least 5 minutes for the registry to answer a publish request, like npm. This fixes "409 Conflict - Failed to save packument" errors when the registry is slow to answer #11454.pnpm deploy --prodno longer fails withERR_PNPM_OUTDATED_LOCKFILEwhen the deployed project declares adevEngines.runtimewithonFail: download. The runtime stays out of the deployednode_moduleswith the rest of the dev dependencies #15703.pnpm deploywith a shared lockfile now copies workspace dependencies into the deploy directory, even whenpackageImportMethodis set tohardlink. Before, their files were hard-linked to the workspace sources, so editing a source file also changed the deployed copy #12176.pnpm deploy --legacyno longer leaves broken links to nested local dependencies of workspace packages #9575.Configuration and pnpmfile hooks
Every setting pnpm supports can now be set with
--config.<name>=<value>on the command line, not only the ones whose command also carries a matching flag. Before,pnpm install --config.frozen-lockfile=truedropped the setting and rewrotepnpm-lock.yamlas though the install had not been frozen #16276.Settings given on the command line, such as
--registryand--store-dir, now take precedence over the values a pnpmfileupdateConfighook sets #14063.pnpm config set --location=projectandpnpm config delete --location=project, run from a package inside a workspace, now write settings that belong inpnpm-workspace.yamlto the workspace root'spnpm-workspace.yaml. Before, they created a newpnpm-workspace.yamlin the current package, which made that package the workspace root. Settings stored in.npmrcare still written to the current directory #13757.pnpm now reads the workspace directory override from
PNPM_CONFIG_WORKSPACE_DIR, like other settings.NPM_CONFIG_WORKSPACE_DIRstill works as a fallback #16275.pnpm now fails with
ERR_PNPM_AUTH_INVALID_BASE64when a registry's_passwordin.npmrcis not valid base64. Before, it sent the value as the raw password. Ausernameor_passwordleft empty, for example by an unset environment variable, now supplies no credential #16273.proxy=falsenow turns proxying off even whenHTTP_PROXY,HTTPS_PROXY, orALL_PROXYis set. pnpm no longer sends requests through a proxy named only inALL_PROXY.pnpm installnow runs the install hooks of a config dependency plugin's pnpmfile, includingreadPackage,afterAllResolved, and custom resolvers. Its pnpmfile is also counted inpnpmfileChecksum. Before, only the plugin'supdateConfighook ran, so a plugin could not change the resolved dependencies.A pnpmfile
fetchershook now runs once per package on a fresh install when it handles a resolution with a customtypeor delegates a git-hosted one to the same subdirectory #15584. These packages were fetched a second time for installation, so the installed files could come from a different archive than the one their dependencies were read from. The hook also no longer runs twice when aresolvershook returns a tarball resolution without a manifest #15025.pnpm installnow re-fetches a package from a custom resolver when theintegrityof its resolution changes, with or withoutenableGlobalVirtualStore. It used to update the lockfile but keep the old files innode_modules#15670.pnpm installnow rejects invalid results from areadPackagehook. A hook that returns a non-object value fails withERR_PNPM_BAD_READ_PACKAGE_HOOK_RESULT#15730. A hook that sets a dependency range to a value other than a string, such asundefined, fails with an error that names the dependency, the package and the pnpmfile. Delete the property to remove a dependency #15705.Global packages, pnpm versions, and runtimes
pnpm update --globalnow reinstalls the global packages that pnpm 10 installed into the previous global directory,<global-dir>/5, so their commands are linked into the pnpm homebindirectory again andpnpm list --globallists them. Once every package is migrated, pnpm deletes the previous directory and the commands pnpm 10 linked into the pnpm home #11528.A signal sent to pnpm, such as
SIGTERM, now reaches the pnpm that pnpm switches to because ofpackageManagerordevEngines.packageManager, and the one thatpnpm withruns. The signal used to be dropped, so scripts running under that pnpm never got to shut down #9948.On arm64 musl Linux, such as Alpine on ARM, switching to a pinned pnpm older than 12 now runs the JavaScript
pnpmpackage. The standalone executable of those versions crashed at startup on that platform #10443.Global shims such as
nodenow work when pnpm runs through a relative symlink, as with a Homebrew install. They were copies of that symlink and did not resolve from the global bin directory #15691.pnpm env remove --globaldeletes Node.js versions that pnpm installed into its own store, including when another tool installed pnpm #8357.pnpm self-updateno longer suggests a downgrade whenminimumReleaseAgeholds back the registry'slatestrelease. It now says that release is still within the cutoff #12006.Windows
Interrupting a script with Ctrl+C on Windows no longer leaves the terminal stuck #14860. A script that runs through a batch shim, as
vite devdoes throughvite.CMD, made cmd.exe wait forever on its "Terminate batch job (Y/N)?" answer, and every following keystroke went to that prompt. pnpm now ends a cmd.exe script shell once it has sat for a second after the interrupt with nothing running under it. A script that takes longer to shut down is still waited for. A second Ctrl+C ends the script's shell at once.On Windows,
pnpm runnow passes the arguments after the script name to the script as typed. Before,cmdexpanded%VAR%in them and backslashes arrived doubled. Line breaks still arrive as the two characters\n, becausecmdcannot pass them. The command line pnpm prints for the script quotes the arguments the same way on every platform #16257.The Windows
pnpm.exeruns on a clean Windows install that does not have the Visual C++ Redistributable. It used to exit immediately on startup because that runtime was missing #15723.On Windows, the
.cmdcommand shims innode_modules/.binnow keep a%in the project path. Before, cmd.exe expanded it as a variable reference, so the command received a mangledNODE_PATH#15716. Command shims also run tools whose paths contain non-ASCII characters #6999, including the PowerShell shims in Windows PowerShell 5.1 #16217.Bin shims in
node_modules/.binrun from Cygwin on Windows again. The shims passed a/cygdrive/c/...path to the Windowsnodefound onPATH, so Node.js failed withCannot find module 'C:\cygdrive\c\...'#12845.On Windows, installing pnpm with npm inside a project now writes
node_modules/.binshims that runpnpm.exe. A global install withnpm install --location=globalnow gets the same shims asnpm install -g#15688.pnpm installno longer fails withERR_PNPM_WORKSPACE_INVALID_GLOBon Windows for a wildcard pattern such asplugins/*/*inpnpm-workspace.yamlwhen the workspace is on a different drive than the pnpm cache or state directory #16239.On Windows,
pnpm installno longer skips a dependency's build script on a later install when the package ships an executable file and the script changes nothing inside the package directory #15667.pnpm setupno longer writes thepn.ps1,pnpx.ps1, andpnx.ps1PowerShell wrappers. It also removes the ones an earlier setup wrote. PowerShell now runspn,pnpx, andpnxthrough their.cmdwrappers, likepnpmitself. Before, these aliases failed with a "not digitally signed" error wherever the execution policy blocks unsigned scripts #8444.pnpm setupon Windows no longer panics when an unrelated environment variable has a name containing a non-ASCII character. It skips that variable #15684.On Windows,
pnpm setuprepairs thePNPM_HOMEregistry type left by older pnpm versions, even when the configured directory has not changed.On Windows, the
ERR_PNPM_BAD_ENV_FOUNDerror ofpnpm setupnow shows the valuePNPM_HOMEis currently set to. Before, it showed the directory pnpm wanted to set.On Windows, pnpm expands nested
%VAR%references inPNPM_HOMEand the other directory environment variables it uses for its home, store, cache, state, and config directories. pnpm fails with an error when a%VAR%reference remains after expansion #13236.On Windows, if the global bin directory is not in
PATHand aPATHentry still contains an unexpanded variable such as%PNPM_HOME%, the error now names that entry. A variable referenced from the userPathmust be set to a full path and stored as a plain string (REG_SZ) for the entry to expand #5283.Inspecting dependencies
pnpm auditandpnpm audit signaturesnow fail with an error when the lockfile contains unresolvable dependency references #13638.pnpm licenses listnow reports the actual on-disk package locations when usingnodeLinker: hoistedorshamefully-hoist: true#8589. With--json, itspathsarray now includes every installed copy of a package, including hoisted copies and isolated installations with different peer dependencies.pnpm rootnow prints the configuredmodulesDir. It used to printnode_modulesregardless of the setting. A project's ownmodulesDirfrompackageConfigsis printed too #9113.Output and messages
With the default and append-only reporters, installs with
--loglevel warnor--loglevel errornow print the full output of a failed install script. The output of successful scripts, including the root project's own install hooks, stays hidden. With--loglevel warn, pnpm also prints ignored build script warnings.When a dependency fails to resolve, the error now shows the cause. For example, a Node.js runtime download behind a proxy that re-signs TLS now reports
invalid peer certificate: UnknownIssuer#9556.When installing a git dependency over SSH fails with
Permission denied (publickey), pnpm suggests checking the loaded keys withssh-add -l. Resolving an SSH URL that refuses the key also shows a local HTTPS rewrite that leaves the recorded URL alone #13743.Lockfile verification now fails with
ERR_PNPM_TARBALL_URL_MISMATCH,ERR_PNPM_TARBALL_REVISION_MISMATCH, orERR_PNPM_MISSING_NAMED_REGISTRYwhen every rejected entry failed that check. These failures were reported as the genericERR_PNPM_LOCKFILE_RESOLUTION_VERIFICATION.The lockfile verification error now suggests relaxing the policy that flagged an entry only if a fresh resolution still fails and you trust the affected packages. Errors from checks that no policy controls, such as a missing tarball integrity, no longer suggest relaxing a policy #14411.
pnpm installno longer prints an extraProgress:line after the progress line is markeddone#16184.Platinum Sponsors
Gold Sponsors
v12.7.0: pnpm 12.7Compare Source
pnpm 12.7.0 ships with
.nvmrcand.node-versionsupport in the globalnodeshim,pnpm install --allow-build,pnpm publish --publish-wait-timeout, andpnpm-workspace.yamlcreated from theworkspacesfield.pnpm install --forceno longer installs optional dependencies built for other platforms. This release also carries security fixes for bin shims on Nix, for lifecycle scripts of packages in astoreDirinside the workspace, and foruserAgentplaceholders inpnpm-workspace.yaml.Minor Changes
pnpm install --forcenow keeps skipping optional dependencies whoseos,cpuorlibcdo not match the host. It still refetches every package and liftsengineStrict. The newforceIgnoresPlatformsetting restores the previous behaviour, installing optional dependencies of every platform under--force#6133.The global
nodeshim created by pnpm now uses the Node.js version from the nearest.nvmrcor.node-versionfile when the project does not declare a Node.js runtime indevEngines.runtimeorengines.runtime#4471. The nearest directory with a Node.js runtime declaration decides the version. Within one directory,package.jsontakes precedence over.node-version, which takes precedence over.nvmrc. An.nvmrcvalue that only nvm can act on, such assystemor a custom alias, is ignored.pnpm installnow supports the--allow-buildoption to selectively allow or deny package lifecycle scripts and record them inpnpm-workspace.yaml#15388.Added
pnpm publish --publish-wait-timeout <milliseconds>to wait for published versions and their tarballs to become available from the registry. SetpublishWaitTimeoutinpnpm-workspace.yamlto configure a default. A value of0disables the check.Recursive publishing confirms availability before publishing dependent packages. If confirmation times out, the command fails.
When
pnpm publish -r --report-summaryfails after some uploads were accepted, the summary file now lists those packages.pnpm installnow createspnpm-workspace.yamlfrom theworkspacesfield of the rootpackage.jsonwhen the repository has nopnpm-workspace.yaml. The projects the field lists are linked on that same install. An existingpnpm-workspace.yamlis never changed. With--ignore-workspace, no file is created. If theworkspacesfield later differs frompackagesinpnpm-workspace.yaml, pnpm prints a warning #2255.When a project pins a pnpm version or a runtime that another pnpm process is installing at that moment, pnpm now waits a few seconds and then installs and runs a private copy of its own. It used to wait up to five minutes and then use the shared install directory without the lock. The private copy is removed once the command has run.
pnpm store pruneremoves any private copy that a killed process left behind #15413.pnpm now keeps the blank lines between entries of
package.jsonwhen it updates the file, for example onpnpm add#5602.Patch Changes
Security
pnpm no longer expands environment variables in a
userAgentset in a project'spnpm-workspace.yaml. AuserAgentwith a placeholder in that file is now ignored. Before this fix, pnpm sent the variable's value to the configured registry #15415.On Nix, a dependency's bin named like a system utility such as
sedcan no longer redirect a POSIX bin shim or thepnpm,pn,pnpx, andpnxlaunchers. The shims and launchers now ignorenode_modulesand relativePATHentries while they locate their own files. Installing again replaces the shims already innode_modules#14883.pnpm no longer treats manifests inside its store, cache, state, or modules directories as workspace projects. Before, a
storeDirinside the workspace could let lifecycle scripts of packages in the store run withoutallowBuildsapproval #15033.Packages that run a lifecycle script are no longer hard-linked into the virtual store, so a build script can no longer rewrite the workspace source of an injected package or the store copy it was imported from #15483.
Installing packages
Fixed
pnpm install,pnpm add,pnpm remove, andpnpm peers checkrunning out of memory when many packages share a missing peer dependency. This mostly affected projects withautoInstallPeers: false#15362.pnpm no longer hangs for up to 5 minutes after a pnpm process was killed while setting up the pnpm version pinned in
packageManagerordevEngines#15360, #15393. The killed process left behind a lock that every later pnpm command in the project waited on. pnpm now detects that the process holding a lock is gone and takes the lock over at once. The same applies to the locks pnpm takes while installing a managed runtime or writing the global bin directory. Two pnpm processes that are both still running keep waiting for each other as before.Requests to a registry or tarball server whose TLS certificate fails verification now fail at once. Such requests were retried for more than a minute without any output #9134.
On macOS, pnpm now falls back to its bundled CA roots when system trust evaluation is unavailable, such as in a sandbox or when macOS cannot create an SSL policy for a registry connection. Installs failed or crashed on the first registry request in that case. Custom
cacertificates are now honored directly #15329, #14461.pnpm installnow caps concurrent connections to a proxy at 50 sockets by default #15280. It also immediately retries transient connection resets when downloading package archives.pnpm installnow reuses a package already present in the store when an existing lockfile entry satisfies the dependency, avoiding registry requests that fail without authorization #2522.Installing or adding dependencies no longer fails when a previously installed local tarball file was deleted from disk #8367.
pnpm installnow installs the new version of a local tarball dependency whose file was replaced at the same path #2437.pnpm install --frozen-lockfilerejects such a changed tarball, even when the previous archive contents are in the store #1889.pnpm installnow fetches committed submodules of git dependencies #1470.pnpm installnow applies patches produced bypnpm patch-commitwhen an edit removes the trailing lines of a file along with its newline. The install no longer fails withERR_PNPM_INVALID_PATCH("expected end of hunk") #12451.pnpm installnow preserves existingnode_modulesdirectories when a cross-device move reportsEXDEV#14504.pnpm installno longer fails when writing the workspace state file encounters an error. Failures to update the state file now emit a warning instead of aborting the install #14550.Interrupting
pnpm installwith Ctrl+C or SIGTERM no longer leaves a temporary lockfile (.pnpm-lock.yaml.*.tmp) behind in the project #1418.pnpm installnow relinks a direct dependency whose link innode_modulespoints to a missing target. Before, it reported "Already up to date" and left the broken link #9758.pnpm installuses less CPU when it links packages from a warm store. On Windows, a warm install could take several times longer than with pnpm 11 #15439.pnpm installnow runsnode --versiononce per run. A workspace whose projects keep their own lockfiles (sharedWorkspaceLockfile: false) previously ran the probe once or twice for every project, and on macOS the concurrent launches waited on each other, so a project could wait several seconds before its linking started.A repeat
pnpm install --frozen-lockfilewithnodeLinker: hoistedin a workspace no longer re-linksnode_moduleswhen nothing changed.Custom fetcher hooks no longer run a second time during installation when an archive was already fetched during dependency resolution #15025.
Fixed a package resolved by a
resolverspnpmfile hook installing without its own dependencies. This happened when the hook returned nomanifestand afetchershook handled the resolution #15552.pnpm install --prodand other installs that skipdevDependenciesno longer run thepnpm:devPreinstallscript #7065. They skippreparelifecycle scripts too, as do installs given package arguments.pnpm prune --prodand production installs now remove devDependencies whenlockfile: falseis configured #2677.pnpm install --prod,pnpm fetch --prodandpnpm deploy --prodno longer install a devDependency that is only there to satisfy an optional peer dependency of a production dependency.pnpm list,pnpm why,pnpm licenses,pnpm sbomandpnpm auditleave it out of--prodresults too. The same applies to--dev. A peer that is not optional is still installed and audited #15344.pnpm installno longer skips optional dependencies that the Node.js version locked for adevEngines.runtimerange supports, when the range usesonFail: download. An explicitly setnodeVersionstill takes priority #14628.pnpm fetchnow also installs the pnpm version thatpnpm-lock.yamlpins, when it differs from the running pnpm. A laterpnpm install --offlinethat switches to the pinned version no longer fails because that version is missing from the store #11808.A dependency that ships a
binding.gypand setsgypfile: falseno longer gets thenode-gyp rebuildinstall script pnpm synthesizes for it. Such a dependency needs noallowBuildsentry and is no longer listed under "Ignored build scripts".pnpm installno longer addsallowBuildsplaceholder entries topnpm-workspace.yamlwhen it runs in CI or without a terminal. Interactive installs still add them #11574.pnpm now detects the same CI environments as pnpm 11, including AWS CodeBuild, which does not set
CI. On these servicespnpm installuses a frozen lockfile by default and fails withERR_PNPM_OUTDATED_LOCKFILEwhen the lockfile is outdated.Resolving and linking dependencies
Installing through a pnpr server now installs a project's peer dependencies when
autoInstallPeersis enabled. A project that declared only peer dependencies failed withERR_PNPM_OUTDATED_LOCKFILEor skipped its peers #14833.pnpm now installs a dependency that a package also declares as an optional peer dependency, for example
lightningcssin some vite builds. The dependency was missing fromnode_modules, so the package failed to import it #8912.Removal overrides such as
"parent>peer": "-"now prevent optional peers from being installed from another workspace package #15008.Removing an entry from
overridesnow re-resolves the packages it targeted. A version the override had locked is no longer kept just because the declared range still accepts it #4587.packageExtensionsandoverridesentries with a ranged selector (such as@<Xor@*) no longer match a dependency that has nopackage.json, such as a local directory dependency #15007.Trim leading and trailing whitespace from dependency override selectors in
pnpm.overrides#6356.With
trustPolicy: no-downgrade, pnpm now resolves the newest matching version that is not a trust downgrade. Previously a dependency failed withERR_PNPM_TRUST_DOWNGRADEeven when an older version satisfied its range.pnpm self-updatepicks its target version the same way. A request for an exact version still fails #14176.pnpm installnow re-resolves a dependency when its manifest range is updated from a prerelease to a stable version. The lockfile previously retained the prerelease version and caused--frozen-lockfileto fail #15528.pnpm install --ignore-pnpmfileno longer removespnpmfileChecksumfrom an up-to-datepnpm-lock.yaml.pnpm install --frozen-lockfile --ignore-pnpmfileno longer fails withERR_PNPM_LOCKFILE_CONFIG_MISMATCHwhen the lockfile records apnpmfileChecksum. A command that resolves dependencies with the pnpmfile ignored still writes the lockfile without it #10944.pnpm installandpnpm peers checknow use local tarball packages' actual versions when checking peer dependencies. Compatible packages no longer fail withstrictPeerDependenciesenabled.pnpm peers checkand the install-time peer dependency check now resolve peer dependencies from the workspace root whenresolvePeersFromWorkspaceRootis enabled #14982.autoDedupeandpnpm dedupenow move transitive dependencies to the version acatalog:dependency pins, as they already did for versions written directly inpackage.json. Previously they could move those dependencies to a higher version and keep both versions in the lockfile.pnpm dedupenow produces a stable lockfile when a dependency's range matches both a direct dependency and annpm:alias of the same package. The dependency resolves to the version of the direct dependency. Repeated runs previously alternated between two lockfiles #15588.Merging lockfiles now preserves recorded configuration fields such as
overrides,neverBuiltDependencies,patchedDependencies,packageExtensionsChecksum,settings, andcatalogs#8366.A lockfile entry whose resolution is unchanged now keeps its recorded
deprecatedmessage #5772.pnpm no longer writes a package's legacy array-form
engines, such as["node >= 0.8"], to the lockfile. It was recorded as an object keyed by index, such as{'0': node >= 0.8}#4518.Tarball URLs recorded in the lockfile now strip default HTTP and HTTPS ports (
:80and:443) #15539.node_modules/.package-map.jsonno longer contains entries that point at directories that do not exist. Such entries appeared for packages installed only with peer dependencies, most visibly withenableGlobalVirtualStore#14938.With
nodeLinker: hoisted,hoistWorkspacePackagesnow links each workspace project thathoistPatternorpublicHoistPatternselects into the rootnode_modules, unless a hoisted package or a root dependency already uses its name. The project's bins are linked into the rootnode_modules/.bin#7553.With
nodeLinker: hoisted,pnpm installnow removes the commands of the packages it removes fromnode_modules/.bin, such as a nested copy deduped into the rootnode_modules#7568.pnpm installno longer puts a dependency's bin onPATHfor that dependency's own lifecycle scripts before the bin's file exists. pnpm links such a bin after the dependency's build has run. It also removes such a bin left by an earlier install. This fixes installing thenodepackage on Windows #15501.Dependencies and executable binaries are now correctly linked and accessible for workspace packages using
publishConfig.directoryandpublishConfig.linkDirectory#8338.Bin linking leaves workspace and linked dependency files outside node_modules unchanged. Already executable bin files no longer receive redundant permission changes.
Workspaces and filtering
pnpm installnow finds workspace projects reached through a symlink, such as apackagesdirectory that links to a folder outside the workspace. It installs their dependencies, and the links in theirnode_modulesresolve #1044.A dependency declared with
catalog:now counts as a workspace dependency when its catalog entry points at a workspace project, for exampleworkspace:*#15587. WithlinkWorkspacePackagesenabled, so does annpm:alias of a workspace project, such as"math-alias": "npm:math@^1.0.0".pnpm -r runruns that project first.--filter <pkg>...selects it.A
workspace:dependency now resolves to a workspace project whose version is not valid semver, such as1or1.0.workspace:*,workspace:^, andworkspace:~match it. A range identical to the version also matches it #4567.A
workspace:dependency with an exact version now resolves to a workspace project whose version carries SemVer build metadata. For example,workspace:0.5.6-next.3matches a project at0.5.6-next.3+f60facc#6483.Secondary dependencies now prefer the version resolved by the local project's direct dependencies over versions from sibling workspace projects #7191.
pnpm installnow re-resolves a workspace project's auto-installed peer dependency when another workspace project changes its specifier for that package to one that excludes the locked version but still overlaps the peer range. The peer then resolves to the version a fresh install would pick #11800.pnpm install --frozen-lockfilenow fails withERR_PNPM_OUTDATED_LOCKFILEwhenpnpm-lock.yamllists a workspace project whose directory or manifest file is missing. The install used to report success without installing that project's dependencies #7667.pnpm install -rnow installs every workspace project whenrecursiveInstallis set tofalseinpnpm-workspace.yaml#7504.pnpm installwith--filternow installs only the dependencies of the selected projects when usingnodeLinker: hoisted#8882.pnpm installnow updates an injected workspace dependency after that package's own dependencies change, whenshared-workspace-lockfileisfalse#7209.pnpm installnow copies the output of a workspace package's ownprepare,install, orpostinstallscript into the injected copies of that package. Before, the injected copies kept only the files that existed before the script ran.syncInjectedDepsAfterScriptsnow also works whenmodulesDiris set #9464.syncInjectedDepsAfterScriptsnow copies files into injected dependencies whennode_modulesis on another filesystem than the package sources. The sync previously failed with a cross-device link error and made the script run exit with an error #14703.A
modulesDirwith several path segments, such aswww/modules, now puts each workspace project's dependencies in<project>/www/moduleson both fresh and frozen installs, andpnpm binprints<project>/www/modules/.bin#15484.With
nodeLinker: hoisted, pnpm now installs the root project's dependencies into a custommodulesDirinstead ofnode_modules. With a custommodulesDir, the virtual store and itslock.yamlnow default to<modulesDir>/.pnpm.A repeat
pnpm installin a workspace with a custommodulesDirnow takes the up-to-date fast path. Before, pnpm looked for each workspace project's dependencies innode_modulesand ran a full install every time.pnpm now warns when a workspace install covers a project that has its own
pnpm-workspace.yaml. The nested file's settings, such aspatchedDependencies, do not apply when the outer workspace installs that project. pnpm reads settings only from thepnpm-workspace.yamlat the workspace root #11724.The
[<since>]filter selector now compares against the commit where the current branch forked from<since>. Projects changed only by newer commits on<since>are no longer selected. Uncommitted changes are still included. In a shallow clone without that commit, pnpm compares against<since>directly, as before #9907.--filter "[<since>]"now selects workspace packages when dependency versions change in a catalog inpnpm-workspace.yaml#8718. It also selects projects that files were moved out of when git detects the move as a rename #15481.--filternow evaluates selectors in order, so later inclusion filters can re-include packages that an earlier exclusion filter excluded #9354.Adding, updating, and removing dependencies
pnpm addnow saves changes topackage.jsonbefore running lifecycle scripts, so a postinstall script failure leaves the added dependency inpackage.json#8627.pnpm addnow saves the requested exact version when adding a dependency, even when the manifest already contains a version range #6040.pnpm add <pkg>@<version>andpnpm update <pkg>@<version>now move the catalog entry onto the named version when the entry's range already covers it. For example,^7.22.17becomes^7.29.6, the same waypnpm update <pkg>moves an entry to the version it resolves #13715.pnpm addandpnpm installkeep an emptypeerDependencies,dependencies,devDependencies, oroptionalDependenciesfield that was already inpackage.json. pnpm still drops such a field when it removes the last entry itself, aspnpm removedoes #5096.pnpm updatenow keeps a version range whose shape has no save prefix, such as<= 3.0.0or>=1.0.0 <2.0.0, when the updated version still satisfies it. Before,<= 3.0.0became^3.0.0#6714.pnpm update <pkg>now moves a package off a locked version the registry no longer serves, such as an unpublished release. The lockfile check for supply-chain policies such asminimumReleaseAgeused to reject that version before the update could replace it #9953.pnpm update --prodno longer installs devDependencies when run in a project installed with--prod#8038.pnpm update --interactive --workspacenow allows external dependencies to be updated.pnpm outdatedandpnpm updatenow applyminimumReleaseAgeto GitHub Actions.minimumReleaseAgeExcludeentries match action names such asactions/checkout#13923.pnpm removenow accepts--trust-lockfileand--no-trust-lockfileto control supply-chain policy checks while removing a package #14406.pnpm unlinknow removes thelink:dependency thatpnpm link <dir>added topackage.json. The linked package is removed fromnode_modulesand the lockfile. Alink:dependency to another directory is kept #4219.pnpm installnow prunes unreferenced catalog entries frompnpm-workspace.yamlwhencatalogPrune: trueis configured #15273.minimumReleaseAgeExcludePruneandtrustPolicyExcludePrunenow work in workspaces withshared-workspace-lockfile=false. Once every project has been installed, pnpm drops an entry only if no project lockfile records it. UndecidedallowBuildsentries are pruned the same way #14612.Exclude entries that pnpm writes to
pnpm-workspace.yamlnow match the file's list indentation and dominant quote style #15571, #15079.pnpm importnow converts dependencies that use Yarn'spatch:protocol. The dependency keeps the version it patches, and the patch file is added topatchedDependenciesinpnpm-workspace.yaml. If the patch file is missing, pnpm prints a warning and imports the dependency without the patch #10278.pnpm importin a workspace now keeps the versions pinned by the rootyarn.lock,package-lock.json, ornpm-shrinkwrap.jsonwhen another workspace project's range allows a newer version. Before, the root project got the newest version in its range #4385.pnpm patch,pnpm patch-commit, andpnpm patch-removenow work in a project of a workspace withsharedWorkspaceLockfile: false.pnpm patchfailed there withERR_PNPM_PATCH_NO_LOCKFILEafter a successful install. The reinstall after committing or removing a patch left the project's ownnode_modulesunchanged #9926.pnpm patch-commitnow resolves default patch directory locations when passed a package name or package specifier (such aspnpm patch-commit <pkg>orpnpm patch-commit <pkg>@<version>).pnpm patch-commitnow updates the lockfile snapshot and prunes removed dependencies when the patch modifiespackage.json#6866.pnpm patch-commitnow falls back to copying package files when hard linking fails.Running scripts and commands
A script that pnpm runs without a terminal now ends when pnpm itself is killed. Killing pnpm's process group, as Playwright's
webServerdoes to stop the command it started, used to leave the script running and holding the caller's output pipes open #15555.pnpm --filter <project> <command>andpnpm -r <command>now run a command installed in the selected projects' dependencies when none of them has a script by that name. This matchespnpm <command>in a single project.pnpm runwith--filteror-rstill reports the missing script #10151.pnpm execandpnpm dlxnow setnpm_execpath,INIT_CWD,npm_node_execpath, andNODEin child environments when Node.js is available. Stale inheritedNODEandnpm_node_execpathvariables are cleared when Node.js cannot be found on PATH #7037. Scripts thatpnpxandpnxrun now get pnpm itself asnpm_execpath. A script that ran$npm_execpath installthere ranpnpm dlx install.pnpm execnow sets thePWDenvironment variable to the directory the command runs in. Shells and tools that readPWDnow report the logical path of a workspace package reached through a symlink #1550.A script that runs
pnpm runno longer adds duplicatenode_modules/.binandnode-gyp-binentries toPATH#5352.Concurrent
pnpm runandpnpm execcommands now serialize their dependency installs #14551.pnpm runandpnpm execwithverifyDepsBeforeRunnow accept a moved project whose store is on the project's volume. Before, the check reported that the workspace structure had changed whenever the default store was not on the home volume.verifyDepsBeforeRunchecks now account for project-specificpackageConfigsoverrides in workspaces withsharedWorkspaceLockfile: false#15545.pnpm restartnow runs the "stop" and "start" scripts when the package has no "restart" script. Previously it ran "stop" and then failed with "Missing script: restart" #4750.pnpm dlxnow keeps a separate cache entry for each Node.js major version. A package built under one Node.js major version, such as a native addon, is no longer reused under another #8611.pnpm pipelineno longer fails when run in a project outside a Git work tree or on a system withoutgit. Tasks in those projects run without caching, and pnpm prints a warning explaining why #15601.A
runtime:version range that contains||or a space, such as adevEngines.runtimeversion of^22.18.0 || ^24.0.0, now installs the requested runtime. pnpm used to install the npm package with the same name, such asnode#14817.When the configured
scriptShelldoes not exist, running a script now fails with an error that names the shell. Previously pnpm printed only an exit code or the package directory #7562.A script killed by a signal now fails with an error that names the signal, such as
Command failed with signal SIGKILL.#9821.Publishing, packing, and deploying
pnpm publishnow resolvesworkspace:dependencies from workspace manifests whennode_modulesis not installed. Previously, publishing withoutnode_modulesfailed withERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL#6567.pnpm publishnow honorspublishConfig["@scope:registry"]for a package in that scope. It takes precedence over the registry set for the same scope in.npmrcand overpublishConfig.registry#12071.pnpm packandpnpm publishnow include bundled dependencies when using the isolated linker. This covers workspace packages and the dependencies of each bundled package. Bundled dependencies are also included whenpublishConfig.directoryselects a build directory #1643.pnpm pack,pnpm deploy, and installs of local directory dependencies now keep symlinks that point to files or directories included in the package.pnpm packleaves out symlinks that point outside the package #8208.pnpm packnow preserves file executable permissions in the packed tarball when source files are executable on disk.pnpm publishandpnpm packnow report a missingversionornamefield on a workspace dependency. Previously, pnpm reported that the dependency was not installed #4164.pnpm publishandpnpm packnow report an error when a bin script has a shebang line ending with CRLF #7311.pnpm deploynow copies thepackageManageranddevEngines.packageManagerfields of the workspace rootpackage.jsoninto the deployedpackage.json, unless the deployed project pins a package manager itself #9079.pnpm deploynow puts the virtual store atvirtualStoreDir, resolved against the deploy directory. A shared-lockfile deploy recordsvirtualStoreDirin the deployedpnpm-workspace.yaml. With the global virtual store enabled or an absolutevirtualStoreDir, the deploy still usesnode_modules/.pnpm#8787.pnpm deploynow respects--package-import-methodpassed on the command line and reports the package import method correctly #7593.pnpm deploydoes not run thepreparescripts of the deployed project #7282.pnpm deploy --legacyno longer rewrites the source workspace'snode_modules/.pnpm-workspace-state-v1.jsonto describe only the deployed project #15352.Manifests and configuration files
pnpm now reads and updates
package.json5project manifests. Manifest updates retain comments, and workspace discovery preferspackage.json, thenpackage.json5, thenpackage.yaml#15129.pnpm packincludes exactly onepackage.jsonin the archive when the project uses an alternative manifest format, even when.npmignoreorfilesexcludes the source file.Git-hosted dependencies that use a
package.yamlorpackage.json5manifest now honor itsfilesfield #7906.Fixed
pnpm versionfailing on projects using apackage.yamlmanifest.Fixed
pnpm initcreating an extrapackage.jsonwhenpackage.yamlis already present.pnpm versionnow applies pending bumps to private workspace packages. A private package's changelog is written to its committedCHANGELOG.md, also whenversioning.changelog.storageisregistry#13736, #13519.pnpm initnow supports the--bareoption. It creates apackage.jsonfile with only the required fields #15538.The
reportersetting is now honored when it is configured inpnpm-workspace.yaml, the global configuration, or thePNPM_CONFIG_REPORTERenvironment variable. Configuredreporter: silentmakes silent output the default. An explicit--reportertakes precedence #4879..npmrcandpnpm-workspace.yamlfiles now support npm's${VAR?}placeholder. It expands to the value ofVAR, or to an empty string without a warning whenVARis unset #14404.pnpm now expands environment variables in
_auth.authTokenvalues loaded from globalconfig.yamlandpnpm_config__auth.pnpm now keeps the configured default registry when
_authholds credentials for several registries and some of those registries serve package scopes.Lockfile verification checks a tarball hosted on a scoped registry against that registry's metadata, unless the package's own scope has a registry assigned #15530.
pnpm now parses the first setting in a
.npmrcthat starts with a UTF-8 byte order mark. Previously, the leading byte order mark caused the first line's key to be ignored #15353.pnpm now prints a warning when a
.npmrc,auth.ini, or the file set bynpmrcAuthFileexists but cannot be read. The settings in such a file were ignored without any message. A.npmrcthat contains invalid UTF-8 is now read #5065.pnpm config setandpnpm config deletenow preserve comments and repeated keys such asca=in.npmrc#14851.pnpm loginnow logs back in to an existing user on registries without web login, such as verdaccio. The classic login request sends the username and password as basic auth, asnpm logindoes #12055.Commands that do not use the store no longer create a temporary file in the project directory when they load their settings. These include
pnpm view,pnpm config,pnpm root,pnpm bin,pnpm exec,pnpm run, the script shortcuts such aspnpm test, and the registry commands such aspnpm whoami,pnpm dist-tag, andpnpm search.pnpm exec,pnpm run, and the script shortcuts still create one in projects that declareconfigDependencies.Global packages, pnpm versions, and runtimes
Global commands such as
pnpm add --global,pnpm list --global, andpnpm bin --globalnow run with the pnpm you invoked, even in a project that pins another pnpm version. Previously, a pin withonFail: "download"switched them to the pinned pnpm, and a pinned pnpm 10 or older failed because its global bin directory was not inPATH#14531.pnpm add -g,pnpm update -g, andpnpm remove -gno longer fail withERR_PNPM_PACKAGE_MANIFEST_IO_ERRORwhen another global package's link into the store dangles, for example after the store was pruned. The pnpm install script failed the same way on such a machine.pnpm update --globalnow skips a global package installed from afile:path that no longer exists, prints a warning, and updates the remaining global packages. Previously the whole update failed withERR_PNPM_LINKED_PKG_DIR_NOT_FOUND#12533.pnpm self-updaterun in a project that pins pnpm throughpackageManagerordevEngines.packageManagernow also updates the global pnpm, as it does outside a project #14747.pnpm self-updateno longer leaves the previous pnpm in the global packages when it was installed as@pnpm/exe.pnpm ls -gnow lists a single pnpm #14709.pnpm setupnow installs pnpm under the package namepnpmtoo, so both commands leave the same shims in the global bin directory. After a self-update on Windows, PowerShell ran pnpm throughpnpm.cmdand asked "Terminate batch job (Y/N)?" on Ctrl+C #15567.pnpm setupfailed withText file busy (os error 26)when$PNPM_HOME/binalready heldpn,pnpx, orpnxas links to the running pnpm executable. It now replaces those files and completes #15494.pnpm setupno longer deletes aliases and other lines that sit between a# pnpmcomment and the pnpm block in a shell startup file #7067.When pnpm switches to the version a project pins, the
minimumReleaseAgeapprovals for that version are now added tominimumReleaseAgeExcludein the project'spnpm-workspace.yaml. A project without that file gets one. Global commands leave the project's settings unchanged #15396.pnpm env removenow cleans up dangling Node.js executables and shims. Surviving global commands remain intact.Node.js runtime resolution now supports Windows ARM64. Node.js 20 and newer resolve native
win-arm64builds, and older versions fall back towin-x64under emulation #7123.Windows and WSL
On Windows,
pnpm cleanand installs no longer fail immediately when another process uses a package innode_modules. pnpm waits up to a minute for an open file. It waits up to 5 seconds for a running program #15081.pnpm installin WSL now waits out Windows file locks on a Windows drive such as/mnt/c, as it already does on Windows. Before, an antivirus or indexer scan holding a file open could fail the install withEACCES#6155.On Windows, pnpm now retries saving
pnpm-lock.yamlfor up to a minute while another process holds the file open. The save used to fail at once withEPERM,EBUSY, or "Access is denied" #9461.pnpm now escapes trailing dots and spaces in
node_modules/.pnpmdirectory names. Windows strips these characters, so a dependency such as"parent-pkg": "file:../"created a directory that could not be deleted or failed to install #8101.On Windows, bin shims run from Git Bash, MSYS2, or Cygwin now pass
NODE_PATHto Node.js as Windows paths. A project installed from cmd or PowerShell gave its bins aNODE_PATHunder the Git install directory when they ran from Git Bash. Installing again replaces the shims already innode_modules#3360.Fixed scripts failing with errors such as
'an-compile' is not recognizedwhenscriptShellis set tocmd.exeon Windows #7181.On Windows, the error for a
node_modulesdirectory that pnpm cannot move out of the way now names the directory and says that a file in it is probably in use by another process #7505.Inspecting dependencies
pnpm auditandpnpm audit signaturesnow check only the dependencies of the projects selected by--filter,--filter-prod, or--workspace-root. The filter used to be ignored, so a filtered audit reported the whole workspace #10982.pnpm auditnow lists at least one dependency path from every workspace project that depends on a vulnerable package. Before, a project whose dependency was reached through more than 100 paths filled the path list, and other projects that depend on the same package were left out #12200.pnpm audit --fixnow prunes redundant overrides when one vulnerable range is a subset of another for the same package #8577.Running
pnpm listinside a workspace package without--recursiveor a filter now lists only the current package #14494.pnpm licenses listdoes the same. Use--recursiveor--filterto list the licenses of other workspace projects #5689.pnpm list --only-projectsnow prints every project selected with--filteror--recursive, including a project that has no workspace dependencies #9770. It also lists the workspace projects whensharedWorkspaceLockfileisfalse#7151, and a project that setspublishConfig.directory#10635. It no longer reports packages innode_modulesthat are missing from the lockfile #9528.pnpm licenses listfailed or reported nothing in a workspace withsharedWorkspaceLockfile: false. It now reads the lockfile of each selected project #10140.With
nodeLinker: hoisted,pnpm licenses listreported every license asUnknownand listed paths undernode_modules/.pnpmthat do not exist. It now reads each package from the directory where the hoisted linker placed it #8589.pnpm outdatedandpnpm -r outdatednow fail withERR_PNPM_NO_PACKAGE_IN_DEPENDENCIESwhen a requested package selector does not match any dependency in the inspected projects #2319.pnpm -r outdated --jsonnow includes every outdated workspace dependency when multiple projects depend on different versions or dependency types of the same package. Such a package is keyed by its current version and dependency type, for examplevue@2.7.14 (dev)#7693.pnpm sbomfiltered to a single workspace project now takes theauthor,description,license,repository, andbugsfields from the workspace rootpackage.jsonwhen the project does not declare them. A field the project declares is never taken from the root, even when it is blank ornull#14882.pnpm store statusno longer reports a package as modified when it has build or postinstall scripts, peer dependencies, or skipped optional dependencies #15383. When packages were mutated, it now lists only those packages and no longer suggests runningpnpm install --force#919.pnpm peers checkand theERR_PNPM_PEER_DEP_ISSUESerror now group peer dependency issues under the workspace project they were found in #15351.Output and messages
The error for an incompatible pnpm-lock.yaml now reports the lockfileVersion the file was generated with and the lockfileVersion the current pnpm supports #848.
When the registry stops sending data for longer than
fetchTimeout, pnpm now reports that the metadata or tarball request timed out. Previously the error did not mention the timeout #3646.Fatal peer dependency errors and their hints are now written to stderr #5419.
pnpm runwith--loglevelset towarn,error, orsilent(or the sameloglevelsetting) no longer prints the$ <command>line before a script, nor the summary of the install thatverifyDepsBeforeRunruns first. Both are info-level output #8944.pnpm runandpnpm execnow printNo projects matched the filters in "<workspace>"when--filterselects no project #8408.pnpm dedupenow counts each package reused from the store once in its progress output #15303.pnpm addnow warns when replacing an existing dependency with a specifier pointing to a different source #14869.pnpm linknow warns when linking a package that declares one or more peer dependencies, explaining that the linked dependency will not resolve peer dependencies from the targetnode_modulesand suggesting thefile:protocol instead.pnpm importnow warns when package.json lists projects in a "workspaces" array and there is no "pnpm-workspace.yaml". Without that file, the import writes a lockfile for the root project only #5240.Bash completion now completes script names that contain a colon, such as
pnpm run test:utopnpm run test:unit#5482.Platinum Sponsors
Gold Sponsors
v12.6.0: pnpm 12.6Compare Source
pnpm 12.6.0 ships with automatic dependency deduplication, relocatable node_modules, package.yaml manifest editing, and --save-types support.
Minor Changes
autoDedupededuplicates compatible dependency versions during installation #7258. Enable it inpnpm-workspace.yamlor usepnpm install --auto-dedupeorpnpm add --auto-dedupe. Frozen installs leave the lockfile unchanged.pnpm install,pnpm run, andpnpm execon macOS and Linux now reuse anode_modulesdirectory and bin shims that moved or were copied together with their project #6937. The first command after the move checks the tree and records its new location, so project commands innode_modules/.binkeep working.pnpm add --save-typessaves available@types/*packages indevDependenciesalongside registry dependencies #3868. Packages that declare bundled TypeScript types are skipped. SetsaveTypes: trueinpnpm-workspace.yamlto enable this by default.package.yamlmanifests can now be updated bypnpm add,pnpm update,pnpm remove,pnpm pkg,pnpm link,pnpm set-script, andpnpm version#2008. Existing comments and key order are preserved.Catalog entries can now use the
file:andlink:protocols #8642. A relative path or bare path in an entry, such as./tarballs/foo.tgz, is measured from the directory holdingpnpm-workspace.yaml.pnpm tasks statuslists running and waiting tasks in each concurrency group, and waiting tasks now take available slots in arrival order with higherprioritytasks going first #15208. If workspaces use different limits for the same group, a later task can take a free slot that earlier tasks cannot use. A package script namedtaskstakes precedence; usepnpm pm tasks statuswhen that script exists.pnpm cache prunedeletes registry metadata cache directories that this version of pnpm can no longer read #15046.pnpm cache prune --dry-runlists what it would delete without removing anything.macosBackup.excludeModulesDirandmacosBackup.excludeStoreDiron macOS can now exclude newly created modules, virtual-store, and package-store directories from Time Machine #6440. Set either totruein global configuration or using thePNPM_CONFIG_MACOS_BACKUP_EXCLUDE_MODULES_DIRandPNPM_CONFIG_MACOS_BACKUP_EXCLUDE_STORE_DIRenvironment variables.pnpm add --tildeis now an alias for--save-prefix=~#12863. The Yarn-Tshorthand is not supported.progresssetting and--no-progressoption now turn off dependency and download progress lines #14065. Warnings, lifecycle output, and the dependency summary are still printed.Patch Changes
Security
POSIX bin shims now take
cygpathandwslpathfrom the system default path on Cygwin, MSYS2, and WSL2 so a dependency cannot redirect another package's shim #14866.pnpm installwarnings no longer carry the text of a package's deprecation notice, naming only the deprecated package and version #15099. A deprecation warning names the newest non-deprecated version when one exists, and control characters and line separators are stripped from package identifiers and warnings.pnpm installand other commands that report configuration warnings now warn when environment variables in project.npmrccredentials are ignored #15051.Installing packages
pnpm install --frozen-lockfilenow succeeds when an optional dependency was unresolvable and skipped by the install that wrote the lockfile #3960.pnpm install --frozen-lockfileno longer installs dependencies of projects removed frompnpm-workspace.yaml#15248. Missing local tarballs used only by those projects no longer fail the install.pnpm cinow emptiesnode_modulesbefore installing in a project that declares acleanscript #15276.pnpm install --forcenow re-imports every package into the virtual store #15030 and removes obsolete dependency links inside virtual-store packages when their dependencies change #15039.preinstallscript for the root project now runs before dependencies are resolved and linked #3760.pnpm installnow runspnpm:devPreinstallwhen the root project usespackage.yaml#15168.pnpm installnow enforces the root project'sengines.noderange whenengineStrictis enabled #3016.pnpm installnow uses the running Node.js whendevEngines.runtimedeclares a range withoutonFail: download#15230.pnpm installno longer hangs when a git dependency is fetched over SSH and ssh prompts for a passphrase or host key confirmation, running ssh in batch mode instead #2227.pnpm installnow installs git-hosted dependencies without preparing them when their builds are explicitly denied byallowBuilds#10522.pnpm installnow reuses an in-flight tarball download when another resolution of the same archive still needs itspackage.json#15037.pnpm install --prodno longer downloads registry packages that only a devDependency reaches #881.pnpm install --no-runtime --frozen-lockfilewithnodeLinker: hoistedno longer fails on repeated runs with a broken lockfile #15212.Resolving and linking dependencies
pnpm installandpnpm updatenow resolve a dependency range to the newest matching version that is not deprecated #15128.pnpm add <pkg>without a version now uses the catalog entry when the workspace already catalogs that package #14865.pnpm installnow links workspace dependencies declared with plain version ranges whenexcludeLinksFromLockfileandlinkWorkspacePackagesare enabled #15133.pnpm installnow resolves local tarball dependencies whose absolutefile:paths contain..consistently and skips reinstallation on repeat installs #15190.pnpm installnow installs dependencies when a custom resolver returns a local or git-hosted tarball without a manifest #15016.pnpm.overridesentries written as a bare path, such as./local-dep, are now measured from the directory holdingpnpm-workspace.yaml#11131.pnpm update --no-saveno longer bypasses version-scoped overrides when a dependency selector specifies a version #14923.pnpm peers checkand strict peer dependency checks no longer reject compatible versions from named registries #15225.pnpm outdatedandpnpm update --interactive --latestnow include named-registry dependencies such aswork:2.1.0and preserve their registry prefix #15226.Workspace projects selected by
hoistPatternorpublicHoistPatternare now hoisted on every install #3642.Workspace packages with SemVer build metadata are no longer skipped when they match the requested range and have the same version precedence as the registry package #2812.
Sped up
pnpm dedupeandpnpm installin projects with many convergence overrides by checking overrides concurrently #15175.minimumReleaseAgeis no longer skipped for packages served by registries returning matching ETags for abbreviated and full package metadata #14925.Running scripts and tasks
pnpm runsignal handling no longer delivers a redundant secondSIGINTto child scripts onCtrl+Cin a terminal, and properly forwards termination signals when running non-interactively without a terminal #7374.pnpm runandpnpm execin workspaces withsharedWorkspaceLockfile: falsenow verify dependencies in the selected projects rather than expecting a root workspace state #15272.pnpm testnow forwards--filterarguments to the test script when the option follows the shortcut #15217.Recursive runs now start scripts matched by a
/pattern/selector in parallel withinworkspaceConcurrency#14933.pnpm deploy,pnpm rebuild,pnpm rb, andpnpm setupnow prefer apackage.jsonscript of the same name #14976.modulesDircustom directory names now support executable lookup and CommonJS plugin resolution acrosspnpm run,pnpm exec,pnpm versionhooks, and lifecycle scripts #3604.pnpm install-testnow accepts--no-baildirectly and in recursive runs #3777.Workspace and project configuration
pnpmcommands run in a project not included in the workspace now act on that project alone #3561.pnpm-workspace.yamledits now preserve scalar YAML anchors and aliases #8245.pnpm-workspace.yamlnow expands environment variable placeholders with fallback syntax in enum-valued settings such asnodeLinker#14914.pnpmfileconfiguration now loads a.jsfile as CommonJS or an ES module, following the nearestpackage.json#15141.updateConfighook settings are now honored bypnpm peers check,why,list,ll,licenses,audit,sbom,fetch,patch,patch-commit,patch-remove,approve-builds, andruntime#15047, #15049.readPackagehook changes or removal now take added dependencies out ofpnpm-lock.yamland update dependencies when an existing lockfile is present #3735, #15136.package.yamlprojects now record their pinned pnpm underpackageManagerDependenciesinpnpm-lock.yaml#15167.packageManagerDependenciespinning@pnpm/exebesidepnpmis no longer rewritten inpnpm-lock.yaml#14926.pnpmnow preserves CRLF line endings when modifying project manifests #3529.loglevelsetting is now honored when configured inpnpm-workspace.yaml, global configuration, orPNPM_CONFIG_LOGLEVEL#3122.storeDirvalues loaded from global configuration orPNPM_CONFIG_STORE_DIRnow expand a leading~/to the user's home directory #6560.--shared-workspace-lockfilenow produces a warning when passed on the command line outside a workspace #1617.Windows
pnpm installon Windows now runs dependency build scripts from long global virtual store paths and normalizes scoped package paths in lifecycle scriptPATHentries #15111.pnpm installacross projects sharing a global virtual store on Windows no longer fails withAccess is denied, file-exists errors, or transient sharing violations #15114, #15176, #15171.pn,pnpx,pnx, andpnpmnow run when Git Bash, MSYS2, or Cygwin launches them through a Windows path #14884.pnpm dlxnow reuses cached packages when Windows creates directory junctions for its cache links #15171.pnpm pipeline --watchnow resolves Windows short paths so multiple path representations share the build cache #15105.CLI commands and output
pnpm removenow runs the project's ownpreuninstall,uninstall, andpostuninstallscripts #3276.pnpm remove -rnow fails before modifying manifests if any requested dependency is absent from all selected projects #2319.pnpm update --peernow updates ranges inpeerDependencies#8081.pnpm updatenow movesdevEngines.runtimeandengines.runtimeversion ranges to the resolved Node.js version #14988.pnpm update -gno longer reinstalls unchanged packages #12002.pnpm add -g,pnpm update -g, andpnpm remove -gnow recover a global package group whosenode_modulesdirectory was deleted #15093.pnpm add -gnow installs local tarballs whenPNPM_HOMEcontains..path segments #15118.pnpm versionnow readstagVersionPrefixfrompnpm-workspace.yaml, global config, orPNPM_CONFIG_TAG_VERSION_PREFIXwhen creating and reading Git tags #15044.pnpm publishnow allows a detached Git HEAD in CI environments #5894.pnpm store prunenow removes unreferenced files and packages from the content-addressable store #3635, as well as expired or supersededpnpm dlxcache data #15171.pnpm cache list-registriesnow prints decoded registry URLs #15046.pnpm deployno longer triggers an install when running scripts in a read-only deployed filesystem #11617.pnpm -r list --jsonnow outputs a single JSON array whensharedWorkspaceLockfileisfalse, and--longand--parseableread each project's own modules directory #15011.pnpm sbomnow validates SPDX identifiers and expressions before emitting them as CycloneDX license IDs or expressions, falling back to a license name for non-SPDX values such asUNLICENSED#14786.pnpm change checknow validates pending change intents in.changeset/#15183.pnpm --filterandpnpm -Fshell completion now suggests workspace package names #15216. Completion candidates containing control or invisible formatting characters are omitted so package and script names cannot inject terminal escape sequences.pnpm runandpnpm run-scriptshell completion now suggests package scripts #15034.pnpm --versionno longer creates a temporary file in the project directory during store detection #15264.pnpm setupnow describes displayed configuration changes as "The following configuration changes were made" #15100.minimumReleaseAgeapproval prompts inpnpm installandpnpm update -gnow count and display each package version once #15083, #15091..npmrcauthentication warnings now report when an empty environment variable removes an auth token and name the affected key #4806.The install summary now names the version each dependency resolved to when
node-linkerishoisted#15161.pnpm installnow re-links a package's global virtual store slot afterallowBuildschanges #15117.Platinum Sponsors
Gold Sponsors
v12.5.1: pnpm 12.5.1Compare Source
Patch Changes
pnpm now reports an unknown task setting in
pnpm-workspace.yamland carries on. It used to refuse to start, so a project could not use a task setting that only the pnpm version itspackageManagerpins reads. The setting is still an error when the running pnpm is that pinned version.Python interpreter installation now retries historical release metadata requests. It caches the release list for up to 24 hours and refreshes it once after a lookup miss. When a release omits the current platform, the search samples at most eight other releases before reporting that the lookup is inconclusive.
Python
registriesentries now route packages by exact names or trailing-prefix patterns inpackages. Registry declaration order no longer affects resolution. A matched package resolves exclusively from its assigned registry, including transitive and build dependencies. Usepackages: ["*"]to declare the default index.pnpm installno longer fails with "Too many levels of symbolic links" when a Cargo configuration file above the workspace is a symlink, such as a~/.cargo/config.tomllinked from a dotfiles repository.pnpm installnow returns "Already up to date" in a workspace wherededupeDirectDepsleft a project without anode_modulesdirectory of its own. Such a project forced a full install on every run.pnpm installno longer refuses the repeat-install fast path just because a changedpnpm-lock.yamlis 16 MiB or larger. Such a lockfile forced a full install on the run after every change.Platinum Sponsors
Gold Sponsors
v12.5.0: pnpm 12.5Compare Source
pnpm 12.5.0 makes Python a first-class ecosystem, accepts Package URLs in
pnpm add, names whole platforms insupportedArchitectures, and gives tasks machine-wide concurrency limits. It also fixes an install that could reuse one package's downloaded tarball for another.Minor Changes
Installing packages
pnpm addaccepts a Package URL in place of a package name.pnpm add pkg:npm/express@4.18.2savesexpresstopackage.json.pnpm add pkg:cargo/serde@1.0.188savesserdetoCargo.toml.pnpm add pkg:pypi/requests@2.31.0savesrequeststopyproject.toml.pkgis now a reserved specifier prefix, whatever case it is written in, so a named registry can no longer be calledpkg.A
registriesentry can now name the ecosystem it serves.ecosystemacceptsnpm,cargoandpypi. An entry that does not name one serves npm, as every entry did before.An ecosystem with several indexes searches them in the order they are declared. The first index that has a package supplies it, so the one declared last answers what none before it had.
A
registriesentry may not carry credentials. pnpm reads them from.npmrc, matched by origin, for a PyPI index as for every other package source.Configuring pnpm
supportedArchitecturesnow accepts a list of platforms, in place of theos,cpuandlibcaxes.An install prepares for the platforms the list names, and for those only. A platform reads as
<os>-<cpu>, with a C library on Linux, as inlinux-x64-muslorlinux-x64-manylinux_2_28. The Rust target triple of the same machine is accepted too, sox86_64-unknown-linux-gnunames the platformlinux-x64names. A Linux platform that names no C library is the glibc platform.currentis the platform the install runs on.The
os,cpuandlibcmapping keeps working and keeps its meaning.Added concurrency groups for tasks. A task in
pnpm-workspace.yamlcan name aconcurrencyGroup. The newconcurrencyGroupssetting gives each group a limit. At most that many tasks of the group run at once on the machine, counted across every pnpm process,pnpm pipelineincluded. A task past the limit waits for a running one to finish. A script that callspnpm runfor a task of the same group runs under the slot its parent holds.toolsnames the programs pnpm downloads, andmirrorsays where each one comes from.node,bunandpythoncan be named. Any other tool is refused.mirroris the base a tool's own layout hangs off.channelssends one release channel elsewhere. A channel neither it nornode-mirror:<channel>names is left tomirror. Onlynodepublishes channels, so naming them for another tool is refused.Set it in the global
config.yamlor inPNPM_CONFIG_TOOLS. Apnpm-workspace.yamlthat names a tool mirror is ignored.pnpm pack-appdownloads the Node.js it embeds throughtools.node.node-mirror:<channel>keeps working and names the same thing as an entry underchannels.Python interpreters and environments
pnpm installnow chooses a Python interpreter for each project instead of installing every project with one interpreter #14945. A project is installed with the first interpreter on the machine that itsrequires-pythonaccepts, so a workspace can hold projects that support different Python versions. pnpm reads.python-versiontoo, and prefers the version it asks for. Setpython.executableinpnpm-workspace.yamlto name one interpreter for every project.pnpm installnow installs a Python interpreter when no interpreter on the machine fits the project #14945. The builds are python-build-standalone's, which uv and rye install too. One interpreter is shared by every project on the machine, and a later install uses it without downloading anything.runtimeOnFaildecides what an install with no interpreter that fits does, the way it does for a Node.js runtime.errorreports the project instead of installing one.warnandignoreinstall with an interpreter the machine has that the project'srequires-pythonrejects.tools.python.mirrornames a mirror.Python environments now live in the store. Each project keeps only its
.venvlink, which points at the project's current environment generation underpython-envsin the store. A repository with many Python projects no longer holds a.pnpm/python-envsdirectory in each of them. The next install relinks a.venvthat an earlier release published. The old.pnpm/python-envsdirectory is left in place, since a running program may still use it, and can be deleted once none does. WithfrozenStoreset, pnpm writes nothing to the store, so environments stay in the project's.pnpm/python-envs#15014.Python environments now use
packageImportMethodto import wheel files from the store. Useclone-or-copyfor copy-on-write clones with a copy fallback, orcopyfor independent files. Hardlinked files share writes with the store and other environments.Isolated Python build environments keep backend writes private with copy-on-write clones or copies.
Python projects and workspaces
pnpm installnow installs a Python project's own package, so the project can be imported and the commands in[project.scripts]run right after an install #14945. The installed package points at the source tree, so an edit to a module takes effect without another install. pnpm installs the package of a project that declares a[build-system].tool.uv.packageoverrides that either way.pnpm installnow installs a Python project in the workspace from its own source. Declare it under[tool.uv.sources], asshared = { workspace = true }orshared = { path = "../shared", editable = true }. pnpm builds the project with the backend it declares. It installs the build editable, so an edit to the project takes effect without another install.Approve the build backend under
allowBuildsinpnpm-workspace.yamlas a Package URL, aspkg:pypi/hatchling: true. An install that has not approved a backend does not build the projects that need it. The message names the key to add.pnpm installnow refuses a requirement that names a project in the workspace when nothing declares where it comes from. It used to take that name from the index.The members of a uv workspace can now share one Python environment. Set
shared-environment = trueunder[tool.pnpm.python]in thepyproject.tomlthat declares[tool.uv.workspace].pnpm installthen resolves every member as one graph into onepylock.tomland one.venvat the workspace root. Two members that require versions of a distribution no release satisfies at once are refused with an error naming both. Each project still gets an environment of its own by default #15015.Python projects can now select extras and dependency groups through
[tool.pnpm.python]inpyproject.toml#14945. Workspacepython.extrasandpython.groupsdefaults now skip names a project does not define.pnpm installnow reads dynamic Python project metadata from the build backend #14945. Projects with only arequirements.txtfile now get a Python environment and lockfile.Python dependencies and lockfiles
pnpm can now resolve
pylock.tomlfor several platforms and Python versions at once.supportedArchitecturesnames the platforms to lock for andpython.versionsthe versions. Every platform is paired with every version. One committed lockfile then serves Linux CI and macOS or Windows contributors #14945.The lockfile pins the wheel each environment takes for a distribution. It marks a package only some environments install.
pnpm installtakes the packages and wheels of the environment its interpreter matches, and refuses an interpreter none of them stand for. pnpm resolves a project that declares environments itself, not through the serverpnprServernames. Naming neither setting locks for the interpreter running the install.python.overridesandpython.constraintspin the versions a Python resolution may pick #14945. pnpm reads uv's own overrides and constraints frompyproject.tomltoo.pnpm installnow supports Python dependencies from Git repositories #14945. Direct wheel URLs are also supported. Sources can be declared in[tool.uv.sources]. Git dependencies requireallowBuildsapproval.pnpm installcan install a Python release that publishes no wheel this interpreter accepts, by building the source distribution the index serves beside it #14945. The archive is pinned inpylock.tomlby name and SHA-256. A later install replays it from the store, offline included. Building a source distribution runs the release's own build backend. Approve it withpkg:pypi/<distribution>: trueunderallowBuilds.A resolution that finds no version of a distribution now says why. It tells apart a distribution no index publishes, one whose releases publish nothing this interpreter can install, and one whose versions the project's requirements exclude.
Patch Changes
Installing packages
pnpm no longer reuses one package's downloaded tarball for another package whose resolution pins a different integrity hash to the same URL #15021.
pnpm installandpnpm addnow report an error whenpackage.json,pnpm-lock.yaml,pyproject.tomlor another file they snapshot before installing is a named pipe or a device. The command used to wait forever for something to write to it.pnpm install --prodandpnpm install --devnow record every dependency group inpnpm-lock.yaml.node_modulesstill holds only the groups the filter selects. They used to write the filter into the lockfile, so a laterpnpm install --frozen-lockfilerejected it.pnpm prune --prod,pnpm prune --dev, andpnpm prune --no-optionalbehave the same way #14912.POSIX bin shims now convert a Windows-form path such as
C:\node_modules\.bin\tsccorrectly. The shim mangled the backslashes in such a path and could not reach the package it runs. Installing again replaces the shims already innode_modules#14867.Two pnpm processes installing one workspace at the same time no longer fail on Windows with "Access is denied" while writing
node_modules/.pnpm-workspace-state-v1.json. The write now retries the transient lock the other process holds, as pnpm's other file writes do.pnpm now reads the manifest from the tarball when a pnpmfile
resolvershook returns a resolution without one. Such a package installed alone, with none of its own dependencies and no warning #15000.pnpm installnow merges Git conflict markers inpnpm-lock.yaml. It parses both sides of the conflict and keeps the versions they locked. A conflict in the config dependencies recorded at the top of the lockfile is merged too #14880.Cargo projects
pnpm installcan now generateCargo.lockfor workspaces with path or Git[patch]and[replace]overrides. Adding, removing, and updating crates also preserve these overrides #14950.Cargo lockfile resolution blocks unsupported Git transport helpers declared by transitive dependencies.
pnpm installnow vendors recursive Git submodules for Cargo dependencies at their pinned commits. Cargo builds can use these sources offline. Set Git'sprotocol.file.allowtoalwaysto fetch local file submodules. pnpm fetches cached Git crates again on the first online install #14951.pnpm installnow generatesCargo.lockfor workspaces with Git dependencies, including a dependency that omits a package version. It also downloads the Rust standard library's dependencies when Cargo configuration enablesbuild-std#14944.pnpm installnow handles weak Cargo features, writtencrate?/feature. Resolution failed when one dependency turned on an optional crate and another asked for a weak feature of it #14960. The generatedCargo.locknow also includes the dependencies weak features reference, which Cargo rejected with--lockedfor crates such asuuid#14978.pnpm installnow generatesCargo.lockwhen a crate version it considers depends on a release the registry carries only as yanked. pnpm rules that version out and resolves the rest of the graph. Resolution failed with an error such asno non-yanked version of napi-build satisfies ^3.0.0-beta#14952.pnpm installnow falls back to an older semver-incompatible version of a crate when the newest one a dependency range allows cannot be resolved. Ranges such as>=1, <3span several of them #14962.Python projects
pnpm installnow honors uv workspace members when discovering Python projects. When no uv workspace declares a project, pnpm skips projects under conventional example, demo, documentation, template,test,tests, and test fixture directories #15058.pnpm install --filter <selector>now installs only the Python projects the selection asks for. A Python project that shares a directory with an npm workspace project is selected with that project. A Python project in a directory of its own is selected by the distribution it declares, by its path, or through the[tool.uv.sources]entries that reach it. Under--fail-if-no-match, a selector that names only a Python project is a match.pnpm add --filter <selector> pypi:<package>writes the requirement to every selected project #14945.pnpm installnow installs wheels whoseRECORDhashes disagree with their contents. The wheel archive's locked SHA-256 hash remains verified. pnpm writes correct hashes to the installedRECORD#15061.pnpm installnow installs a Python wheel whoseWHEELfile lists tags that differ from the ones in its filename. A wheel whose filename tags were changed after the build, such asmysql-connector-python, was rejected #14945.A Python release whose wheel metadata declares a requirement pnpm cannot read no longer fails the install. pnpm now resolves the project against the other releases of that package, and reports the unreadable requirement when none of them works.
pnpm installno longer fails when a Python index lists a file pnpm cannot use, such as a release with no SHA-256 digest or an unreadable wheel filename. That file is left out and the project resolves against the remaining releases.pnpm add pypi:<package>in a directory that has nopyproject.tomlnow names the missing file and says where to run the command. It used to fail with a bareNo such file or directory (os error 2)#14945.Performance
pnpm auditno longer hangs on dependency graphs with many shared dependencies #15005.Sped up
pnpm installin Python workspaces with many projects. Projects now prepare concurrently. Projects with identical registry requirements also share fresh dependency resolutions #14945.Repeat installs through the Node-API bindings now return "Already up to date" when the project manifests still match
pnpm-lock.yaml. Before, every such install reinstalled the whole tree. An install also no longer reinstalls whenpnpm-lock.yamldiffers from the installed dependencies only by packages no project depends on or by top-level keys pnpm does not define.Other commands
pnpm deploynow links commands exposed by workspace dependencies into the deployed project'snode_modules/.bindirectory #14899.pnpm dlxandpnxnow prompt to approve dependency build scripts in interactive terminals #14943. Cached packages with pending builds also prompt for approval. Without an interactive terminal, use--allow-buildto allow the required builds.pnpm add -gandpnpm update -gnow ignore incomplete unrelated global package groups when every command from the replaced group is retained. Operations that could remove a global command still require complete ownership information.pnpm packnow writes tarball entries grouped by file extension and file name, the order npm uses. Packages that ship many same-named files, such as template collections, pack much smaller #14766.pnpm outdated --longfills the Details column with the package homepage again #14886.Platinum Sponsors
Gold Sponsors
v12.4.2: pnpm 12.4.2Compare Source
pnpm 12.4.2 includes security fixes for executable shims and GitHub Actions links, more reliable installs, faster peer dependency checks in workspaces, and Python lockfiles that work across compatible targets.
Patch Changes
Security
Dependency executables can no longer take over another package's POSIX bin shim through its shell helpers. Reinstall dependencies to replace existing shims #14837.
On Cygwin, MSYS2, and WSL, shims still use
PATHfor Windows path conversion, so dependency executables can still redirect them there.GitHub Actions homepage links no longer expose server credentials. GitHub server URLs now require HTTPS, with HTTP allowed only for loopback hosts.
Installing packages
pnpm no longer crashes at startup on FreeBSD and other Unix-like platforms. Platforms other than Windows and macOS use
~/.local/share/pnpm/storeby default #14859.pnpm installon Windows no longer fails withERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIRwhen clearingnode_modulescontaining linked dependencies, such as when changingnodeLinker#14790.pnpm install <pkg>now accepts--prodand--dev, including--prod=false#14868.pnpm installandpnpm updatenow honor--ignore-workspacein nested projects excluded from the surrounding workspace. The flag also skips that workspace's settings during thepackageManagercheck #14809.pnpm installon macOS no longer reuses stale files forfile:tarball or git-hosted tarball dependencies.pnpm installin a single-project directory now detectspackage.jsonedits made while the previous install was finishing #14890.pnpm install --frozen-lockfilenow removes packages no longer reachable from any project inpnpm-lock.yaml. This also prevents repeated lifecycle script execution and unnecessary installs beforepnpm runandpnpm execwithverifyDepsBeforeRun#14891.Resolving and updating dependencies
Node.js runtime resolution now reports network failures from unofficial-builds.nodejs.org. These failures previously omitted musl builds from
pnpm-lock.yaml, making its contents depend on network access #14813.pnpm installnow rejects invalidpeerDependenciesspecifiers withERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION. A value such as"foo": "foo@1.0.0"previously created a broken directory link #14791.pnpm deploynow writes plain registry versions in the deployedpackage.json, without peer dependency suffixes. The lockfile retains peer bindings, and npm aliases retain their target package names #14873.pnpm add <git repository>now names repositories without apackage.jsonas@owner/repo, allowing dependencies on equally named repositories from different owners #14870.Peer dependency resolution now deduplicates packages whose child dependency resolves an optional peer in only some workspace projects, such as
nextwithstyled-jsx's optionalbabel-plugin-macrospeer #14800.pnpm updatenow settles the lockfile in one run when an upgrade removes the package providing an optional peer dependency #14895.pnpm update --no-savenow preserves override-applied specifiers for dependencies it is not updating, preventing subsequent frozen installs from failing withERR_PNPM_OUTDATED_LOCKFILE#14836.pnpm update --no-savenow succeeds underminimumReleaseAgeStrictwhen every resolved version is old enough #14835.Performance
Workspace installs and
pnpm peers checkare faster when projects depend on each other, fixing a slowdown introduced in 12.3.0. Unmet peer dependencies of workspace packages are now reported only under projects that link them directly #14906.Hoisted installs use less memory when packages are cached. Frozen-lockfile hoisted installs on macOS are also faster when reusable package directories are cached.
Python projects
pnpm install --frozen-lockfilenow reusespylock.tomlacross compatible Python targets, including after kernel updates. Reuse requires unchanged requirements, index, andrequires-python, compatible wheels, and a locked dependency graph matching the target's markers #14843.The lockfile's
environmentsmarker now includes only the interpreter version and marker variables used by the dependency graph. Without--frozen-lockfile, pnpm warns and resolves again when the locked graph no longer matches the target.Python resolution no longer fails on malformed
Requires-Pythonvalues, such as the trailing comma inopenpyxl3.0.x. pnpm treats these releases as declaring no interpreter range #14910.pnpm add pypi:...now rejects unsupported--save-prefixvalues before editing the manifest or resolving dependencies.Workspaces and scripts
Scripts listed in
syncInjectedDepsAfterScriptsno longer fail withERR_PNPM_INJECTED_DEPS_SYNC_READ_DIRwhen the lockfile contains an injected package copy that no project depends on.shellEmulatornow expands${VAR},${VAR:-default}, and${VAR:+alternative}in scripts #14814.Cargo and Python project discovery now honors
!exclusions inpnpm-workspace.yamlpackages, skipping both parsing and generated source configuration for excluded projects #14844.pnpm --filter "./packages/{app,lib}"now selects either alternative. Brace alternatives can nest, span path separators, and combine with other wildcards.GitHub Actions updates now stop if an action reference changes during version resolution, and preserve unrelated workflow edits.
CLI and output
pn,pnpx, andpnxnow run the pnpm installed alongside them, even when that directory is absent fromPATHor another pnpm comes first #14803.pnpm --versionnow reports failures to install or record a project's pinned pnpm, then prints the running CLI's version. It also honors--store-dirand--store#14831.pnpm self-updateno longer reinstalls the active version when it was installed by the standalone installation script #14823.pnpm tandpnpm tstwork again as aliases forpnpm test.pnpm sbomnow emits valid repository URLs in CycloneDXexternalReferences[].urland SPDXhomepage. Shorthands such asvercel/msbecomegit+httpsURLs, embedded credentials are removed, and invalid repository values are omitted #14773.Platinum Sponsors
Gold Sponsors
v12.4.1: pnpm 12.4.1Compare Source
pnpm 12.4.1 fixes installs that failed on filesystems refusing hard links or clones, on Android, and under
nodeLinker: hoisted. Repeat installs are faster.Patch Changes
Installing packages
pnpm installno longer fails withOperation not permittedwhen the filesystem refuses a hard link or a copy-on-write clone #14722. UnderpackageImportMethod: autoandclone-or-copy, pnpm copies the file instead. EdenFS checkouts, which have no hard links, and rootless containers, which refuse the clone syscall, both hit this. An explicitpackageImportMethod: hardlinkorclonestill reports the error.pnpm also copies a package file whose store entry has reached the filesystem's limit on names for one file, 1024 on NTFS and 65000 on ext4. Such a file failed the install under
packageImportMethod: hardlink, and underautoit stopped pnpm hard linking for the rest of the install.pnpm installno longer writes a package file through a symlink left at the path it is importing to. Copying such a file overwrote whatever the link pointed at, and created that file when the link pointed nowhere. An executable package file also made the link's target executable.Fixed
pnpm installandpnpm dlxon Android. Registry requests crashed because pnpm found no system CA certificates, so pnpm uses bundled ones there #14777. Imports also failed with "Permission denied" on filesystems that deny hard links and reflinks, and now fall back to copying #14780.pnpm installno longer fails with "Invalid cross-device link" while preserving a package's nestednode_modulesdirectory during a Docker build #14758.pnpm installno longer fails on a package tarball that carries a file at the archive root, such as the._*entries macOStaradds #14701. The file is installed at the root of the package.A
file:tarball packed without the usualpackage/directory is now recorded under the name and version from its ownpackage.json. It was recorded under the alias the dependency was given, at version 0.0.0.Under
nodeLinker: hoisted,pnpm installno longer re-imports packages that are already in place. A repeat install replaced the wholenode_modulestree and reportedPackages: +N. A package is still imported when its directory is missing, when itspackage.jsonno longer carries the installed version, when it is afile:dependency, and when it is patched. Lifecycle scripts no longer run again for a package left in place, andpnpm rebuildand a change toallowBuildsstill reach it.pnpm installnow runs a dependency's build scripts again when its side-effects cache entry has no files to restore #14717. Such builds were skipped and nothing was put in their place, so a script whose whole effect lands outside its own package directory, such as a git hook installer, never took effect. pnpm no longer publishes empty artifacts to the shared side-effects cache either.Resolving and linking dependencies
pnpm install,pnpm add, andpnpm dedupenow applyignoredOptionalDependencies#14729. Matching optional dependencies are left out of the lockfile and are not installed. pnpm 12 installed them whenever it resolved dependencies from scratch.pnpm installno longer links a transitive dependency to a workspace package whenlinkWorkspacePackagesistrueand the dependency is declared with a plain version range #14781. EnablingpreferWorkspacePackagesdoes not change this. SetlinkWorkspacePackages: deepto link them.pnpm installno longer leaves dangling dependency links in workspace packages located above the workspace root #14726.pnpm installandpnpm addno longer leave a dangling symlink innode_moduleswhen a project starts depending directly on a package that the lockfile holds only as a transitive dependency with resolved peer dependencies #14714.pnpm dedupenow keeps a compatible auto-installed peer when another workspace project depends on a newer major #14697. Repeated runs alternated between compatible and incompatible peer versions.pnpm peers checkno longer reports a peer dependency declared asworkspace:^,workspace:~, or a bareworkspace:as unmet #14770. pnpm reported these as unmet whatever version the linked workspace project supplied.Performance
Sped up repeat installs #14540. pnpm checks the store's files only for the packages it links into
node_modules, instead of every package in the lockfile. Creating the command shims innode_modules/.binmakes about 1,500 fewer filesystem calls in a 76 project workspace. Installs that use the global virtual store read their slot paths from the cache directory instead of deriving them every time. Verifying a large lockfile also allocates less memory.Sped up
pnpm installin Cargo workspaces with many member crates. Repeated installs reuse verified Cargo checksum metadata.Installing several packages from the same Git repository and commit now downloads the source once per install #14725. Each package still runs its prepare scripts in its own copy of the checkout.
Running scripts and tasks
pnpm now passes Ctrl+C on to the script or command it started and waits for it to shut down #14723. pnpm exited first, so a script that was still writing landed on the shell prompt.
pnpm run "/pattern/" --no-bailnow lets every matched script finish after one of them fails #14718. The command exits withERR_PNPM_RUN_FAILED, and its message lists the scripts that failed in the order they were selected.pnpm pipelineno longer fails on a project that tracks a symlink, such as aCLAUDE.mdpointing atAGENTS.md#14692. Changing a symlinked input's target invalidates that task's cache, andpnpm pipeline --no-cacheno longer hashes task inputs.Commands
pnpm add -g,pnpm update -g, andpnpm remove -gno longer change global bins or install directories after reading only part of an installed package group #13796. If any declared package manifest is missing, malformed, or unreadable, pnpm now fails before it activates or removes anything and leaves the existing global installation intact.pnpm dedupenow processes every workspace project by default, including workspaces that keep a separate lockfile per project #14732. Workspace filters select which projects it processes, and--fail-if-no-matchexits with an error when no project matches.pnpm update <name>@<version>now keeps the range operator the manifest declares #14745. Runningpnpm update react@19.3.0on"react": "^19.2.8"writes"react": "^19.3.0". Ajsr:entry keeps itsjsr:prefix, and a plainpnpm updatenow moves ajsr:range the way it moves an npm range.pnpm --filterdirectory selectors now support?wildcards and character classes such as[ab]. A*or?wildcard no longer selects a directory whose name starts with a dot, as on pnpm 11.pnpm deploy --legacynow prefers the dependency versions pinned in the source workspace lockfile when they still satisfy the deployed project's range #13857.pnpm sbomnow leaves out a package's author field when the manifest author name is empty or contains only whitespace #14685. In a filtered or split workspace run, only a project with noauthorfield inherits the workspace root's author.pnpm sbom --sbom-format spdxnow writescreationInfo.createdwith whole seconds, such as2026-09-08T10:38:21Z#14684. The fractional seconds it carried were rejected by strict SPDX consumers.Configuration
The
updateConfigpnpmfile hook now receives the resolved configuration, including settings that came from.npmrc, the command line, or a default #14676. Scoped registries are reported underregistriesByScope, and a hook may rewrite that map to change where packages are fetched from. Registry credentials are reported underconfigByUri, as pnpm 11 reports them. An unset setting is left out rather than reported asnull.pnpm audit --fixand theminimumReleaseAgeStrictapproval prompt now keep the comments inminimumReleaseAgeExcludewhen they append an entry to it inpnpm-workspace.yaml. The rest of the list is left as written, and thetrustPolicyExcludePruneandminimumReleaseAgeExcludePrunecleanups keep the comments of the entries they retain.pnpm installandpnpm dedupenow run those cleanups too #14759. Onlypnpm add,pnpm update, andpnpm removepruned the entries that the freshly written lockfile no longer resolves.pnpm config set --global node-download-mirrorsno longer rejects the key #13611. The global config file already acceptednodeDownloadMirrors, but the command refused to write it.NO_PROXYentries that start with a dot, such as.npmjs.org, now bypass the proxy for the domain and its subdomains #14686.pnpm no longer creates a project
pnpm-lock.yamlwhendevEngines.packageManager.onFailisdownloadand lockfile writing is off throughlockfile: falseor--no-lockfile#14728. pnpm still switches to the pinned version.pnpm now writes
node_modules/.package-map.jsononly whennodeExperimentalPackageMapis enabled. Nothing reads the file without that setting, and an install that stops writing the map removes the one a previous install left.Windows
pnpm pipelineno longer fails with intermittent access denied errors when concurrent tasks save their cache entries on Windows.Windows filesystem operations now retry permission errors for up to one second #14682. A permanent permission error delayed the failure by a minute. Sharing and lock violations keep their one minute retry budget.
Messages and output
pnpm now warns when the root
package.jsondeclares a non-emptyworkspacesarray and the project has nopnpm-workspace.yaml#2255. Such an install linked no project and said nothing about why.ERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIRnow names the file or directory innode_modulesthat pnpm could not clean up. It reported only the underlying OS error, such as "Access is denied (os error 5)".pnpm --helpno longer describes pnpm as experimental.Platinum Sponsors
Gold Sponsors
v12.4.0: pnpm 12.4Compare Source
Minor Changes
pnpm can now manage npm, Python, and Cargo dependencies in the same workspace. Enable
python.enabledorcargo.enabledinpnpm-workspace.yaml, then usepnpm installto install them together.pnpm add pypi:<package>. pnpm usespyproject.toml,pylock.toml, and a managed.venv. Frozen and offline installs are supported, andpnpm runandpnpm execmake the environment's executables available #14566.pnpm add crate:<package>. pnpm supports crates.io and custom sparse registries configured withcargo.indexUrl. Registry authentication supports pnpm credentials and, for crates.io,CARGO_REGISTRY_TOKENor$CARGO_HOME/credentials.toml.Both ecosystems support faster dependency resolution through
pnprServer, with local resolution as a fallback when the server does not support it.Added
pnpm pipeline [name]to install frozen dependencies and run workspace tasks declared inpipelines. It selects affected projects, runs their task graph, and continues running tasks after a task fails.Tasks support
inputs,outputs,env, andcachesettings. Cached results restore task outputs and replay logs. Cargo tasks can reuse local build state between worktrees withtasks.<name>.cargoTargetDir. SetincludeWorkspaceRoot: trueto include root tasks.Use
pnpm pipeline --dry-runto preview the task graph without installing configuration dependencies or running workspace hooks.Added support for Android on arm64 and x64, FreeBSD on x64, and Linux on ppc64le, s390x, and RISC-V (riscv64 with glibc) #14431, #14597, #7582.
Added
trustPolicyExcludePruneto automatically remove unused versions and packages fromtrustPolicyExcludewhen runningpnpm add,pnpm update, orpnpm remove. It is disabled by default. Package name patterns such as@scope/*are kept, and cleanup is skipped whensharedWorkspaceLockfileisfalse.Added
pnpm change checkfor CI validation of package versions against theversioning.epicsbands andversioning.fixedgroups inpnpm-workspace.yaml. It reports all violations, including packages that are not part of the current release.Patch Changes
Registry metadata is now kept separate for registries with different URL paths or schemes. This prevents installs from using another registry's package versions or tarball URLs, and keeps metadata fetched over HTTP from being reused for HTTPS #13558.
The first install after upgrading refetches registry metadata. The package store is unchanged.
pnpm cache viewnow shows full registry URLs. Scripts that parse the directory names frompnpm cache list-registriesorpnpm cache listneed updating.Patches that add build scripts or a
binding.gypnow trigger a build, subject to build approval. Unapproved builds appear under "Ignored build scripts" #14648.Build scripts can now be rejected before installing a package with
pnpm add --allow-build=!<pkg>, including global installs.pnpm approve-builds <pkg>andpnpm approve-builds !<pkg>also save decisions when no packages are awaiting approval. They warn if the named package is not awaiting approval #14067.A registry configured in
.npmrcnow takes precedence over registry settings saved bypnpm loginin the globalconfig.yaml. This fixes installs using the wrong registry after login #14614.Large downloads over slow connections no longer time out while data is still arriving.
fetch-timeoutnow limits how long a request can go without making progress #14604.Sped up installs in workspaces with many projects when reusing a warm global virtual store #14540.
pnpm deployis faster in large workspaces and no longer fails withERR_PNPM_LOCKFILE_CONFIG_MISMATCHwhen the project includes a.pnpmfile.mjs#14539, #14671.pnpm add --workspace <pkg>works again. It saves the dependency with theworkspace:protocol and links it from the workspace. The command fails if no workspace project provides the package #14602.pnpm addandpnpm installnow accept protocol-prefixed selectors such asjsr:@scope/pkg,npm:pkg@^1.0.0, andworkspace:pkg@*#14590. Installs with JSR dependencies in the lockfile also no longer fail withERR_PNPM_META_FETCH_FAIL#14649.Boolean flags now accept explicit inline values. For example,
pnpm install --prod=falseinstalls devDependencies, while--prod=trueskips them #14553.pnpm install <pkg>now accepts--offlineand--prefer-offline, aspnpm add <pkg>already did #14194.Fixed
pnpm install --frozen-lockfilerejecting a freshly generated lockfile when overrides use relativefile:orlink:paths in a workspace #14555.Fixed installs with config dependencies failing on symlinked lockfiles, such as those used by Bazel and Nix, when the config dependencies have not changed. Updates that would write through a symlink remain disallowed. Updating config dependencies also preserves lockfiles that start with a byte order mark #14372.
Fixed package manager version pins being written to the wrong lockfile when
lockfileDiris set. The pins also remain consistent across commands when version switching is disabled, avoiding unnecessary lockfile changes #14633, #14575.pnpm importnow respectslockfileDirand branch lockfiles without modifying other lockfiles. Failed imports restore the destination lockfile #14563.pnpm patch-commitnow produces valid patches when files are added or deleted.pnpm installalso accepts patches that delete files without listing their contents, and patch files with CRLF line endings #14559, #14557.Fixed version ranges with partial upper bounds. For example,
<=16now includes all 16.x versions, and>=0.11 <=3correctly accepts 3.0.1 #14419.Workspace package patterns now support
.and..segments and repeated slashes. Patterns such as./packages/*and exclusions such as!./packages/foonow match correctly #14571.packageConfigssettings now apply to the specified projects whensharedWorkspaceLockfileisfalse, includingoverrides,hoist,modulesDir,saveExact, andsavePrefix. Workspaces with a shared lockfile report which entries were ignored #14556.pnpm runandpnpm execno longer report a changed workspace structure after a successful install whensharedWorkspaceLockfileisfalseandverifyDepsBeforeRunis enabled #14588.Commands run from a project's subdirectory now find the nearest ancestor with a manifest. This fixes commands such as
pnpm binreturning paths under the wrong directory.pnpm initstill creates its manifest in the current directory, andpnpm execstill runs there #14622.Relative
scriptShellpaths inpnpm-workspace.yamlnow resolve from the workspace root, including when scripts run in nested packages. Bare command names such asbashstill usePATH#14422.Fixed installing the pnpm version pinned in
packageManagerwhennodeLinkerishoisted. Managed Node.js, Deno, and Bun installations also work when the global config usesnodeLinker: hoisted#14595.The JavaScript pnpm can again switch to a project's pinned pnpm version on platforms without a native binary for that version, such as Alpine Linux with pnpm 10 or Intel Macs with pnpm 11. If a native pnpm version does not support the platform, the error now names the missing target #13622.
Provisioning Yarn 6 now uses
GH_TOKENorGITHUB_TOKENwhen available to avoid GitHub's anonymous API rate limit in CI. Tokens are only sent whenstrict-sslis enabled.Fixed concurrent installs sharing a global virtual store on macOS failing with "failed to import ... No such file or directory" #14560.
Fixed
pnpm setupfailing withERR_PNPM_DIRECTORY_FETCHER_PATH_ESCAPEon Windows. Localfile:dependencies whose directories are symlinks or junctions are now packed correctly #14618.On Windows, installs now retry replacing command shims temporarily locked by another process #14549.
Fixed argument forwarding on Windows with
shellEmulatorenabled. Paths ending in a backslash, line breaks, and literal shell expressions are preserved #14548.Windows store paths now consistently use backslashes in
pnpm store pathoutput and in thestoreDirandvirtualStoreDirfields ofnode_modules/.modules.yaml.Invalid certificates in
caorcafileno longer cause anInvalid CA certificateerror. Valid certificates still apply, and blankcertorkeyvalues are treated as unset #14646.Installs now respect the archive extraction concurrency limit even after a download is abandoned #14585.
pnpm auditsummaries now exclude advisories ignored throughauditConfig.ignoreGhsasand report them separately. When all advisories are ignored, the summary says so #14535.pnpm pack --jsonnow reports errors as JSON. Lifecycle script output appears before the final JSON output.pnpm outdated -rnow wraps theDependentscolumn, keeping the table readable when many workspace projects use the same dependency #14591.Shell completions now support the
pnalias in bash, fish, pwsh, and zsh #11955.pnpm versionnow accepts-mas a short alias for--message#14567.Platinum Sponsors
Gold Sponsors
v12.3.4: pnpm 12.3.4Compare Source
Patch Changes
Sped up dependency resolution in large workspaces #14352.
pnpm 12 now accepts the boolean settings as command-line flags on every command that takes them in pnpm 11, for example
pnpm install --unsafe-perm,pnpm add foo --offline, andpnpm install --dangerously-allow-all-builds. pnpm 12 rejected them withunexpected argument, which failed every install on Vercel, whose build runspnpm install --unsafe-perm#14346.pnpm removenow accepts--unsafe-perm, the same flagpnpm install,pnpm add, andpnpm updatetake.Platinum Sponsors
Gold Sponsors
v12.3.3: pnpm 12.3.3Compare Source
Patch Changes
Fixed concurrent installs sharing a store occasionally failing with an ENOENT error while importing a package file #14353.
Sped up writing the lockfile in large workspaces #14352.
Sped up dependency resolution in large workspaces #14352.
pnpm now runs through Node.js when it was installed by a tool that skips build scripts, such as Vercel's
packageManagerprovisioning, Bun, Deno, ornpm install --ignore-scripts. Those installs previously failed withsyntax error near unexpected token ')'. They still cannot run pnpm on Windows. On macOS only a shell can start it #14346.Platinum Sponsors
Gold Sponsors
v12.3.2: pnpm 12.3.2Compare Source
Patch Changes
pnpm audit --fix updateno longer aborts when a vulnerable package has no safe version inside its declared range #14508. The run updates every package it can and lists the rest as remaining.pnpm installno longer reruns root lifecycle scripts when the global virtual store contains an unfinished-build marker in a package slot that the current lockfile does not use pnpm/pnpm#14485.Sped up installs that have no lockfile. pnpm now links packages whose dependency subtree has no peer dependencies into the virtual store while resolution is still running.
pnpm runandpnpm execnow start without reinstalling on filesystems that keep sub-millisecond mtimes, such as NTFS. Previously, every run on those filesystems reinstalled first pnpm/pnpm#14486.pnpm importnow keeps the versions recorded inpackage-lock.json,npm-shrinkwrap.json, oryarn.lockwhen it generatespnpm-lock.yaml. A range inpackage.json, a catalog, or an override still decides which versions are eligible, and the recorded version is preferred among them. The generated lockfile previously could pin newer versions than the source lockfile #14476.pnpm importin a workspace now imports every workspace project into the shared lockfile. It previously imported only the project in the current directory.pnpm importnow fails withERR_PNPM_LOCKFILE_NOT_FOUNDwhen none of the three source lockfiles is present. It also fails withERR_PNPM_YARN_LOCKFILE_PARSE_FAILEDwhen it cannot parseyarn.lock. It previously generated a lockfile from scratch in both cases.pnpm importalways resolves locally. It warns when--pnpr-serveror thepnpr-serversetting is given and does not use the server.Sped up installs in large workspaces. Discovering the workspace projects no longer enumerates every matched directory to learn which manifest files it holds #14352.
Sped up installs in large workspaces. The resolver and the peer pass allocate less for every dependency edge #14352.
pnpm self-update,pnpm with, and automatic package-manager version switching no longer wait through registry retry delays when a configured registry has no signatures andregistry.npmjs.orgis unavailable #14483.Sped up installs in large workspaces. Saving the lockfile is faster, and the install finishes without waiting for memory cleanup #14352.
pnpm installnow relinks workspace packages whenpublishConfig.linkDirectorychanges. Frozen installs report an outdated lockfile until it is regenerated pnpm/pnpm#14488.The pnpm npm wrapper keeps its placeholder shebang-less so pnpm 11 can install pnpm 12 through the version store. Wrapper installs must allow lifecycle scripts to install the native binary #14502.
Sped up dependency resolution when there is no lockfile, and for the dependencies a lockfile does not cover.
Sped up installs in large workspaces. Workspace
link:targets and importer ids are now derived from the paths' suffixes under the workspace root #14352.pnpm installnow reports "Already up to date" when local tarball dependencies have not changed #14495.pnpm updatenow accepts--ignore-scriptsand skips lifecycle scripts during the update pnpm/pnpm#14512.Sped up installs that restore a deleted
node_modulesfrom a warm global virtual store. pnpm no longer re-links packages that are already fully present in the global virtual store #14510.Platinum Sponsors
Gold Sponsors
v12.3.1: pnpm 12.3.1Compare Source
Patch Changes
Sped up installs in large workspaces: the anchor for re-rendering workspace
link:targets is now derived once per project instead of once per dependency edge, and project ordering hashes paths by their raw bytes #14352.After a self-update from pnpm 12.2 to 12.3, global commands such as
node,npm, andyarnfailed withunexpected argument '--shim' found. Global commands now launch normally, and their first launch migrates the global bin directory to native shims. When self-update downgrades to pnpm 12.2 or older, it keeps the newer native shims so those commands continue to work.Sped up installs in large workspaces. The check that verifies each project against the lockfile now runs the projects in parallel #14352.
Platinum Sponsors
Gold Sponsors
v12.3.0: pnpm 12.3Compare Source
Minor Changes
Every context-aware global command (
node,deno,bun, and the shims created withpnpm shim add) is now a native executable on every platform, so environment variables whose names are not valid shell identifiers reach these commands. On Windows,<name>.exereplaces the.cmdand.ps1shims for them. Shims written by earlier pnpm 12 releases are migrated on the next global install or self-update.pnpm removeandpnpm updatenow accept--trust-lockfile,--no-trust-lockfile,--trust-policy,--trust-policy-excludeand--trust-policy-ignore-after, the same flagspnpm installandpnpm addtake, so the supply-chain settings can be overridden for a single run.pnpm removeverifies the lockfile against the active policies the waypnpm installdoes, and--trust-lockfileskips that pass for every entry, not only the package being removed.pnpmnow also honors--config.trust-lockfile=<value>, and accepts the bare--trust-lockfile/--no-trust-lockfilespelling on the commands that previously took the setting from the config file alone.Patch Changes
pnpm add <local directory>,pnpm add <local tarball>,pnpm add file:<path>andpnpm add <tarball URL>work again. A specifier given without a<name>@prefix is no longer read as a registry package name and rejected withERR_PNPM_PACKAGE_MANAGER_ADD_RESOLVE_LATEST#14437.Fixed
pnpm deploy --legacyignoringallowUnusedPatchessupplied through--config.allow-unused-patchesor thePNPM_CONFIG_ALLOW_UNUSED_PATCHESenvironment variable pnpm/pnpm#14450.Fixed
pnpm install --lockfile-onlywriting a lockfile that referenced a missing peer-suffixed snapshot when an npm-aliased dependency took part in a cyclic peer dependency graph. The followingpnpm install --frozen-lockfilefailed withERR_PNPM_LOCKFILE_MISSING_DEPENDENCY#14449.pnpm confignow accepts-g/--global,--location, and--jsonbefore its subcommand pnpm/pnpm#14421.pnpm dedupenow converges in one pass when it re-resolves a lockfile created by pnpm 11, so a second run no longer changes the lockfile #14455.Fixed detached child processes being terminated on Windows when another program launches
pnpmdirectly, without a shell, asnrfrom@antfu/nidoes #14447.Fixed
pnpm docs <package>@<version>ignoring the requested version. It now opens the selected version's homepage and reports a missing version instead of opening the package-level homepage pnpm/pnpm#14428.Sped up installs in large workspaces.
pnpm-lock.yamlis now read while the workspace projects are being discovered #14352.Fixed filtered and recursive
pnpm runandpnpm exechanging when a script reads from the terminal. Interactive prompts work again in a script that pnpm never runs alongside another one, such as a single--filtered project,--workspace-concurrency=1, a dependency chain, or a task declaringconcurrency: 1#14397.Fixed false unmet peer errors for auto-installed peers in linked workspace packages.
Fixed npm global installs on Windows so the PowerShell shims invoke
pnpm.exe.Fixed
pnpm with current <command>when global options precede it, such aspnpm --workspace-root with current --versionpnpm/pnpm#14413.A short-option cluster that mixes a global flag with an option owned by the command, such as
pnpm -ro dist pack-app, is now parsed like the same options written after the command.An option written before the command name is now reported as an unknown option unless that command accepts it, instead of being taken for the command to run.
pnpm -P exec echoandpnpm -z exec echofail the waypnpm --tag next exec echodoes.Apply pure insertions in zero-context patches at the correct line instead of one line early.
Improved peer dependency resolution performance when many packages reuse the same peer ranges.
pnpm outdatedandpnpm updatenow follow local actions and reusable workflows referenced with GitHub's self-repository syntax (uses: $/.github/actions/setup) when looking for outdated GitHub Actions, the same way they follow./references.The
pnpm install --helpdescriptions of--prodand--devno longer claim that the flags take precedence overNODE_ENV. pnpm does not readNODE_ENVwhen selecting which dependency groups to install #14445.Sped up installs in large workspaces. The check that decides whether the lockfile needs updating no longer compares every project against every lockfile entry #14352.
Sped up dependency resolution in large workspaces that use
link:dependencies #14352.On Linux, pnpm now resolves registry hostnames through the system resolver (
getaddrinfo), as it already does on macOS and Windows and as pnpm 11 did. Previously, an/etc/resolv.confcontaining an option the bundled pure-Rust resolver did not recognize, such asoptions no_tld_query, made pnpm ignore the configured nameservers and silently query Google's public DNS instead #14469.Sped up dependency resolution in large workspaces. The resolver builds fewer lookup keys for each dependency #14352.
catalogModeand--save-catalogno longer move a local path, tarball, orworkspace:<path>specifier into a catalog. Such a specifier is resolved against the project that declares it, so one catalog entry cannot mean the same directory for every project that references it #14437.Sped up installs in large workspaces. The workspace dependency graph is now built once per run instead of twice #14352.
Sped up writing
pnpm-lock.yamlin large workspaces #14352.Fixed non-frozen installs through a pnpr server failing instead of regenerating a conflicted lockfile.
pnpm update --interactiverenders its checklist the way pnpm 11 does. Group headings and column headers are separators the cursor skips instead of checkboxes that select nothing. The columns of one group line up with the next.atoggles all andiinverts the selection. The confirmed selection is echoed as a list of package names #14423.Fixed
pnpm configcommands targeting global configuration to skip project package manager version switching, allowing registry authentication to be configured before pnpm downloads a project-pinned version pnpm/pnpm#14463.Fixed pnpm retaining the surrounding quotes in
.npmrcvalues, including auth tokens expanded from environment variables. This restores authentication with registries configured using:_authToken="${TOKEN}"pnpm/pnpm#14427.Fetch and tarball errors no longer print the secrets of the URL they name. Inline
user:pass@credentials and the query string or fragment of a signed URL are hidden, so a failed install orpnpm add <url>cannot leak them into terminal scrollback or CI logs.When
dist-tags.latestnames a version whose manifest pnpm cannot read, the error now names that version and the field it could not decode, instead of reporting the tag as empty.Retry transient Windows file-lock errors, including sharing violations, while linking dependencies with the default (isolated)
nodeLinker. This fixes pnpm/pnpm#14407.pnpm run,pnpm exec,pnpm rebuild, and the script shortcuts such aspnpm testnow load the pnpmfile, soupdateConfighook settings such asextraEnvandextraBinPathsreach the scripts they spawn #14433.The
pnpmexecutable of the npm package now works when the package was installed without running its install scripts, as under--ignore-scriptsor the default build-script block of pnpm and Bun #14346. In that case it runs through Node.js and, in a terminal, says how to switch to the native binary.Sped up installs in large workspaces. The resolver no longer copies the whole lockfile before resolving #14352.
minimumReleaseAgeStrictnow defaults totruewhenminimumReleaseAgeis explicitly configured, whether inpnpm-workspace.yaml, the globalconfig.yaml, aPNPM_CONFIG_*variable, or a CLI flag. The built-in 1440-minute default stays non-strict. Previously an explicit cutoff was treated as non-strict, so immature versions were silently added tominimumReleaseAgeExcludeinstead of being gated with a prompt #14409.Preserve environment variables whose names are not valid shell identifiers when launching Node.js installed by
pnpm runtime set node --globalon Unix pnpm/pnpm#14417.Fixed
pnpm repoandpnpm docsfailing to open the Windows browser from WSL pnpm/pnpm#14467.pnpm link,pnpm outdated, andpnpm importnow apply pnpmfileupdateConfighooks before resolving dependencies.Fixed standalone installations to preserve the bundled
node-gypfiles used to build native dependencies.Fixed resolution against registries whose version manifests carry
_npmUser,dist.attestations,dist.unpackedSize,dist.fileCount, orpeerDependenciesMetain a shape npm does not use. Such a version was skipped as though it had never been published, sopnpm addcould fail with "no version found for the latest tag" even though the registry served it.pnpm unpublishnow completes the two-factor authentication a registry asks for instead of failing withERR_PNPM_UNAUTHORIZEDwhile logged in. A 401 that is an OTP challenge starts the web-based authentication flow, or prompts for a classic one-time password. The obtained password is reused by every request of the run #14464.On Windows, pnpm now resolves host names through the system resolver instead of its own DNS client. The built-in client bound a UDP socket for every lookup, which made Windows Defender Firewall ask to allow
pnpm.exeagain after everypnpm self-update#14405.Platinum Sponsors
Gold Sponsors
v12.2.1: pnpm 12.2.1Compare Source
Patch Changes
pnpmexecutable target without a file extension so pnpm 12.1 and earlier can upgrade to newer pnpm 12 releases on POSIX systems.Platinum Sponsors
Gold Sponsors
v12.2.0: pnpm 12.2Compare Source
Minor Changes
workspace:protocol.Patch Changes
Fixed
pnpm audit --fixfailing withERR_PNPM_INVALID_FIX_OPTIONwhen used without a value, including when another flag follows it, as inpnpm audit --fix --json#13261. Fixedpnpm audit --fix=overrideignoring thesaveExactandsavePrefixsettings when writing vulnerability overrides #11523.Authenticate Node.js runtime downloads from
nodeDownloadMirrorswith URL-scoped npm registry credentials, including bearer tokens, basic auth, andtokenHelperpnpm/pnpm#14334.Fixed detached child processes being terminated after successful commands on Windows.
Sped up installs in large workspaces by resolving each named
workspace:dependency (workspace:*,workspace:^,workspace:1.2.3) once and reusing it across every project that declares it, instead of re-resolving it per project.Fixed
pnpm install --fix-lockfileto derive its repair and filtered-merge views from one lockfile snapshot.Load pnpmfile
updateConfighooks before packing so hook-provided catalogs resolve inpnpm pack,pnpm publish, andpnpm stage publishpnpm/pnpm#14377.pnpm deployno longer requiresinjectWorkspacePackagesto be enabled. A linked workspace dependency is rewritten to afile:dependency in the dedicated deploy lockfile, and the peer dependencies it declares are bound to the deployed graph's own resolution.When a peer resolves to more than one version in that graph the binding is ambiguous, and choosing between the candidates is exactly what injecting the package would have decided, so the deploy still fails — now with
ERR_PNPM_DEPLOY_AMBIGUOUS_PEER, which names the package, the peer, and the competing versions, instead of refusing every non-injected workspace up front, and suggests pinning the peer to one version with anoverridesentry as the way to keep deploying without injection #9386.Fixed global virtual store hashes for dependency cycles. Every package that transitively depends on an allowed build now includes the engine in its store path, independent of traversal order pnpm/pnpm#14341.
Fixed
ERR_PNPM_CMD_SHIM_CHMODwhen several installs run at once against a shared global virtual store. One install could remove a command shim while another was making it executable (pnpm/pnpm#14353).Fixed the PowerShell shim generated by
npm install -g pnpmon Windows so it invokes the nativepnpm.exebinary pnpm/pnpm#14362.Fixed context-aware global shims on WSL2 so native Linux installations dispatch through the project runtime.
pnpm installno longer writes globalminimumReleaseAgeExcludeentries to the project'spnpm-workspace.yamlpnpm/pnpm#14347.Fixed
catalog:ranges in workspace package peer dependencies being reported as unmet pnpm/pnpm#14361.globalDirandglobalBinDirare honored wherever they are set, sopnpm add -gno longer fails withERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATHafterpnpm config set -g global-bin-dir#14336. The globalconfig.yamlis read again,PNPM_CONFIG_GLOBAL_DIR/PNPM_CONFIG_GLOBAL_BIN_DIRreach the directories derived from them, and a leading~/is expanded before that derivation. A project'spnpm-workspace.yamlstill cannot set either key.Fixed the install progress line reporting
added 0undernodeLinker: hoisted, even when packages were linked intonode_modules#14348.An auto-installed optional peer is now resolved to a version its declared peer range accepts, even when the workspace root depends on that package at a version outside the range. Previously the root's version was used and then reported as an unmet optional peer #13867.
Fixed
pnpm run "/pattern/"running matching scripts one at a time in a single project. Matching scripts now run concurrently up toworkspaceConcurrency, and their output is prefixed so concurrent lines remain distinguishable pnpm discussion 14357.Fixed a slowdown at the end of a resolving install in a large workspace. The peer-dependency report now inspects only the projects the resolution flagged, rather than every project in the lockfile (pnpm/pnpm#14359).
Speed up workspace discovery for literal directories and conventional trailing-star patterns.
Workspace patterns now follow the same dot-directory rule as pnpm 11: a wildcard no longer matches a dot-prefixed directory, so
packages/*and**skippackages/.cacheand.git. A pattern that names a dot-prefixed directory still matches it, aspackages/.cacheandpackages/.*do.pnpm auditnow ends its output with a trailing newline, including the--json,--fix, and--ignoreoutput.Retry transient Windows file-lock errors while replacing hoisted packages during installation.
This fixes pnpm/pnpm#14349.
Fixed command-line
--side-effects-cacheoverrides being ignored whenpnpm-workspace.yamluses the object form ofsideEffectsCachepnpm/pnpm#14338.Speed up workspace project discovery in large monorepos: workspace patterns are now probed concurrently and the discovered projects'
package.jsonfiles are read in parallel #14352.Fixed repeated
pnpm deduperuns alternating between peer resolutions when a peer is provided through an npm alias.Fixed
pnpm repo <package>andpnpm docs <package>resolving bare package names through thelatesttag, and prevented malformed package ranges from crashing registry selection.Fixed non-ASCII characters in configuration values being mangled during environment-variable substitution. Paths such as
storeDir: ./café-storeare now preserved #14383.Platinum Sponsors
Gold Sponsors
v12.1.0: pnpm 12.1Compare Source
Minor Changes
pnpm loginandpnpm addusernow record the granted token in the globalconfig.yaml, under the_authsetting, with--scope's scope routed to that registry underregistries.pnpm logoutremoves it from there, and still from anauth.inian earlier version wrote. Tokens already inauth.inikeep working.A
scopeset in a project'spnpm-workspace.yamlis now ignored, with a warning naming where to set it instead.pnpm loginrecords the scope as a@scope:registryroute in the machine-globalauth.ini, which outranks~/.npmrcin every project — so a repository-committed file could redirect a scope such as@acmefor all of a user's other projects after one routine login. Use--scope, thePNPM_CONFIG_SCOPEenvironment variable, or the global config file instead #13557.Verified remote build artifacts are persisted in the shared store with their signed origin metadata. Later installs reverify the artifact against current trust, policy, platform, and source before reuse, while invalid remote variants are quarantined per channel (pnpm/pnpm#13771).
Persist completed recursive tasks so
--resume-fromskips exactly the work that passed during a matching interrupted or failedpnpm -r run/pnpm -r execinvocation. When no compatible state exists, pnpm retains its graph-based resume behavior.Workspace install, rebuild, pack, publish, stage, and lifecycle work now starts as soon as its dependencies finish instead of waiting for an unrelated topological group.
Added per-task concurrency limits to workspace task orchestration. Set
tasks.<name>.concurrencyinpnpm-workspace.yamlto limit how many instances of that task may run across workspace projects at once:sideEffectsCachenow declares the whole of how a package's build output is reused — whether one is restored, whether one is saved, and the remote tier that shares it between machines:sideEffectsCache: true,sideEffectsCacheReadonly,remoteSideEffectsCache, and itsorganizationfield all keep working. Where a field is set under both spellings the one above wins; where it is set under only one, it is kept.Two behaviors change, both bringing this CLI in line with what the Rust one already did:
sideEffectsCacheReadonly: truenow blocks writing to the cache, and setting it alongsidesideEffectsCache: falsegives a read-only view rather than switching the cache off entirely. A cache can also be declared write-only now, to populate one the run does not read.Workspace task orchestration (pnpm/rfcs#23).
pnpm -r runandpnpm -r execnow schedule per task instead of in topological chunks: a task starts as soon as the tasks it depends on have finished, so a project no longer waits for unrelated projects that happen to share its chunk.A new
taskssection inpnpm-workspace.yamldeclares what a task depends on, using the^convention:^namemeans the named task in each of the project's workspace dependencies; a barenamemeans the task in the same project; an entry with nodependsOndeclares an empty dependency list. A task with no entry behaves asdependsOn: ['^<its own name>'], which is exactly what the previous chunked ordering implied — an unconfigured workspace gets the scheduler improvement and nothing else changes meaning. A project without the script is reported skipped and passes its edges through to its own dependencies, so a scriptless package does not sever a chain.Also part of this change:
ERR_PNPM_TASK_CYCLE) instead of silently running in an arbitrary order. SettingignoreWorkspaceCycles: truedowngrades the error to a warning: the cycle's tasks run in an arbitrary order relative to each other.--resume-fromnow skips exactly the transitive dependencies of the anchor package; work unrelated to the anchor still runs.--no-bail, tasks whose dependencies failed are reported as skipped, not failed, and do not add to the exit code.--bail(the default), the first failure still ends the run at once and nothing new is dispatched — including scripts already queued behind the concurrency limit.pnpm -r run --dry-run <script>prints the task graph that would execute without running anything (including skipping theverifyDepsBeforeRuncheck);--jsonemits the tasks and their resolved dependency edges.--workspace-concurrency=1, or the graph forces the scripts to run one after another).Added macOS and Windows x64 and arm64 support to remote shared build artifacts pnpm/pnpm#13771.
Generalized the experimental shared-artifact protocol so candidates and signed payloads identify a discriminated subject. Dependency side effects use package and source-integrity subjects, while workspace tasks use project and task subjects.
This changes shared-artifact request bodies and signed payloads. A pnpr server and its clients have to be on matching versions.
Patch Changes
An
_authentry in the global config file no longer decides which registry packages come from when something else says. Aregistryorregistriesdeclared inpnpm-workspace.yamlor the global config now wins over the route inferred from a stored credential, which still applies where nothing else declares one. Thepnpm_config__authenvironment variable is unchanged: it stays the way to point a CI runner at a mandated proxy, and still overrides what a repository declares.Fixed
pnpm deploy --legacyto exclude dependencies that are only reachable from unselected workspace projects afterpnpm fetch.Fixed dependency-verification install logs corrupting
pnpm execoutput and ignoring--silentpnpm/pnpm#14197.pnpm clean/pnpm purgerun from a workspace subdirectory now remove each project's ownnode_modulesinstead of emptying the workspace root's for every project #14239. A custommodulesDiris resolved against each project directory too.pnpm dlx <pkg>@catalog:now resolves the specifier through the calling workspace's catalogs instead of failing withERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC#14294.Fixed
pnpm doctorreporting a version that does not matchpnpm --versionpnpm/pnpm#14225.Pacquet now strips exactly one leading path component from
./-prefixed tarball entries, matching pnpm and npm's tar extraction semantics and keeping shared store keys consistent.Installs whose lockfile carries platform or engine constraints are up to ~150 ms faster when resolution runs: the
node --versionprobe behind the installability checks now starts before the lockfile is parsed and finishes while dependencies resolve, instead of running afterwards.Treat empty scripts selected by a regular expression as missing before running dependent tasks.
Filter hidden scripts matched by a regular expression during recursive runs when a visible script also matches.
Fixed
.mjspnpmfile hooks failing to load on Windows, including hooks supplied by config dependencies pnpm/pnpm#14301.Fixed automatically switched pnpm versions forcing all descendant pnpm processes to use the same version pnpm/pnpm#14309.
Fixed
pnpm deploy --prodfailing when an excluded dev dependency was also declared as an optional peer dependency pnpm/pnpm#14302.Fixed
pnpm packto respect thefilesfield when deciding whether to include root-level changelog, history, and notice files.pnpm update -gno longer downgrades a global package.--latestresolves thelatestdist-tag, which can point at an older release than the one installed — afterpnpm add -g <pkg>@next, for instance #14270.pnpm update -galso no longer changes the pnpm version. pnpm's own global install belongs topnpm self-update#14270.When multiple versions of the same package expose the same binary, pnpm now links the binary from the highest version #14249.
pnpm updateno longer replaces the specifier a project declares for a dependency that is also listed inoverrides. Acatalog:reference stays acatalog:reference, and a declared range stays as written, instead of being rewritten to the version the override resolved to #12115.pnpm updateno longer moves the range a project declares for a dependency thatoverridesalso lists, even when the override repeats that range verbatim. Previously the updatedpackage.jsondisagreed with the lockfile, so the nextpnpm install --frozen-lockfilefailed with a specifier mismatch #14224.Allowed pnpm's shared-artifact client to connect to an artifact-only pnpr tier.
Rebuilding
node_modulesfrom an up-to-date lockfile is up to ~200 ms faster: thenode --versionprobe that installability checks and store keying need now runs concurrently with the store's warm-cache reads instead of before them.Remove the duplicate colon from the one-time password prompt.
Print errors as JSON on stdout when
--jsonis passed topnpm viewor its aliases (info,show, andv).Installs complete faster on workspaces with many projects: each project's
node_modulesis now linked concurrently.Fixed
patchedDependenciesmatching for git-hosted dependencies during fresh and frozen installs pnpm/pnpm#14273.pnpm pm <command>works again: thepmprefix, which forces pnpm's built-in command over apackage.jsonscript of the same name, is recognized instead of failing withERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL/Command "pm" not found.pnpm pm cleanandpnpm pm purgenow removenode_moduleseven when the project (or the workspace root) declares aclean/purgescript #14226.The settings that pnpm accepts as command-line flags are recognized again:
--package-import-method,--hoist-pattern,--public-hoist-pattern,--no-hoist,--global-dir,--virtual-store-dir,--modules-dir,--child-concurrency,--no-lockfile,--strict-peer-dependencies,--side-effects-cache,--side-effects-cache-readonly,--trust-policy,--trust-policy-exclude,--trust-policy-ignore-after, and--optimistic-repeat-install. Each is accepted anywhere on the command line, spelled either--setting=valueor--setting value, and overrides the same setting read frompnpm-workspace.yamlor.npmrc#14281.pnpm add,pnpm update, andpnpm removenow savepackage.jsonbefore failing withERR_PNPM_IGNORED_BUILDS. The dependency they were asked to change is already materialized by that point, so the manifest has to record it — otherwise the next install removes the packages again.The progress output no longer overwrites the lines above it once it grows taller than the terminal window #14270.
Restoring a dependency's build from the remote side-effects cache no longer downloads files the store already holds.
Recognize
pnpm install --fix-lockfile, including filtered installs, and regenerate broken lockfile metadata while preserving compatible locked versions pnpm/pnpm#14250.Fixed intermittent
Access is deniedfailures when concurrent global commands hand off the global bin lock on Windows.Fixed the
--shamefully-hoistCLI option being rejected pnpm/pnpm#14235.The environment variables for the remote side-effects cache are named for the setting they configure:
PNPM_SIDE_EFFECTS_CACHE_REMOTE_KEY_ID,..._BUILDER_ID,..._IMAGE_DIGEST,..._ARCHITECTURE_BASELINE,..._PRIVATE_KEY,..._BUILD_ENV,..._TRUSTED_KEYSand..._PUBLISH. ThePNPM_REMOTE_SIDE_EFFECTS_CACHE_*names keep working, and the new one wins when both are set.Installs that run no build scripts finish faster, especially in workspaces with many projects.
A
devEngines.packageManagerrange pin on pnpm is now recorded inpnpm-lock.yaml'spackageManagerDependencieswhen the running pnpm already satisfies it, using the running version and keeping the range as the recorded specifier. Previously only an exact pin — or a range resolved on the way through a version switch — reached the lockfile, so a range pin written by hand (or by any tool other thanpnpm add/pnpm self-update) left the project without the shared resolution the pin exists to provide.Workspace installs are substantially faster (~0.7 s on a 60-project workspace): after hoisting, pnpm now shims only the bins of publicly hoisted workspace packages instead of re-walking every project's
node_modulesto rediscover bins that were already linked.Fixed a large install-time regression on macOS for installs that rebuild
node_modulesfrom a warm store #14231. APFS serializes file-cloning and hard-linking syscalls volume-wide, so importing packages one file at a time from many threads was bounded by a per-volume ceiling and got slower the more CPU cores the machine had. On macOS,pnpm installnow materializes each package once into the store'slinksdirectory (the same canonical slotsenableGlobalVirtualStoreuses) and copies it intonode_modules/.pnpmwith a single copy-on-write directory clone per package, replacing tens of thousands of per-file syscalls with one per package. Applies with the defaultnodeLinker: isolatedwhenenableGlobalVirtualStoreis off andpackageImportMethodisauto,clone, orclone-or-copy; hoisted, global-virtual-store, and explicithardlink/copyinstalls are unchanged.Stop in-flight recursive
runandexeccommands when bailing after the first failure.Warm installs that rebuild
node_moduleson macOS are about 10% faster: creating each package's virtual-store directory now issues fewer filesystem calls.An
_authcredential in an.npmrcnow authenticates even when its base64 is written without the trailing=padding (or with extra padding, or with whitespace inside it), instead of failing with a 401. An_auththat is not valid base64, or that carries no:between the username and the password, now fails withERR_PNPM_AUTH_INVALID_BASE64/ERR_PNPM_AUTH_MISSING_SEPARATOR#14257.Colored output is no longer printed as raw escape sequences in the Windows Command Prompt #14292. Commands such as
pnpm listnow style their output there.Platinum Sponsors
Gold Sponsors
v12.0.0: pnpm 12Compare Source
Major Changes
Git dependencies on known hosts (GitHub, GitLab, Bitbucket) are now treated as identities rather than transport choices. Every representation of the same repository —
github:owner/repo,owner/repo,git+https://…,git+ssh://git@…— resolves through the host's canonical HTTPS URL, and the lockfile never records an SSH URL for them. Repositories whose archive endpoint is anonymously reachable resolve to the host's archive (fast tarball download); all others resolve to agitclone of the canonical HTTPS URL, which every machine with access to the repository can fetch.To reach a private hosted repository over SSH, configure the machine (not the project) with git's own URL rewriting, for example:
pnpm shells out to
git, so the rewrite applies to all of pnpm's git operations automatically. URLs of unknown hosts (self-hosted servers) are unaffected and keep their exact URL, including SSH. URLs with embedded credentials are also kept verbatim and never resolve to a host archive.This removes the network probing that previously decided between HTTPS and SSH at resolution time, which could record a transport that only worked on the machine that happened to run the resolution (e.g. an SSH URL that broke CI runners without SSH keys).
A project's
pnpm-workspace.yamlmay no longer carry a setting pnpm does not recognize. Such a setting used to be ignored in silence — a misspelledminimumReleaseAgedropped the policy it was meant to set, and nothing said so. Now it is reported, suggesting the closest real setting name when the key looks like a typo, and it fails the command withERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGSwhen the project pins a pnpm version the running pnpm satisfies: with the pin honored, the setting cannot be meant for a different pnpm version, so it is a mistake to fix rather than a key to ignore. Everywhere else it is a warning, so a project that has yet to be cleaned up keeps working.The
pnpm configsubcommands never fail on such a setting, so a broken file can still be inspected and repaired, andpnpm config get <key>prints the value with no warnings at all. Keys the global config file cannot set are likewise split between workspace-only settings (still directed topnpm-workspace.yaml) and settings unknown to this version.Dependency cycles are now broken canonically during peer resolution: the members of each cycle are ordered by package id, and the edges that close a cycle are always cut at the same place, no matter where the installation walks into the cycle from. Previously the cut depended on the walk path, so installing the same dependencies could produce different lockfiles depending on importer order or resolution order #13846, and a peer-resolution verdict computed for one occurrence of a cyclic package could be wrongly reused at another #13865.
With canonical cycle breaking the lockfile is a pure function of the dependency graph: repeated installs, reordered importers, and reordered dependencies all produce byte-identical lockfiles. Peer dependencies of packages inside a cycle keep nearest-wins resolution along the canonical order, and a dependency edge that closes a cycle references an occurrence of its target resolved at the importer level. On large cycle-heavy workspaces peer resolution is 2–3× faster, uses about 25% less memory, and produces a substantially smaller lockfile (fewer redundant peer variants).
Existing lockfiles keep working: headless (
--frozen-lockfile) installs consume them unchanged, and installs that skip resolution leave them untouched. The first install that actually re-resolves (for example after a dependency change) re-keys walk-order-dependent peer variants of cyclic packages once.packageImportMethod: autonow tries hardlinks before cloning on Linux. A reflink materializes a new inode and copies extent bookkeeping inside the filesystem's metadata trees, where a hardlink is one directory entry — on btrfs this roughly halves the time an install spends materializingnode_modulesfrom a warm store. ext4 installs are unchanged (cloning was never supported there, soautoalready hardlinked), and macOS keeps clone-first, where APFSclonefileis the platform's cheap primitive. Cloning remains the fallback when the store refuses hardlinks, and remains available explicitly viapackageImportMethod: clone.Under
engineStrict, an install fails when an incompatible package is reached through a regulardependenciesedge of an installable package, even when that whole subtree hangs off anoptionalDependenciesentry. pnpm v11 installs the package and emits an install-check warning instead. Packages reachable only through optional edges, or through a package that was itself skipped, are still skipped in both versions #13286.pnpm install --frozen-lockfile falseis no longer supported. Usepnpm install --no-frozen-lockfileto disable frozen-lockfile mode. To enable it, usepnpm install --frozen-lockfilewithout a separatetrueargument #14741.Minor Changes
Globally installed bins can now follow the project you run them in. The new
globalShimssetting is a record of package names to policies that selects which globally installed packages get project-aware shims; it defaults to{ node: true, deno: true, bun: true }and merges key-wise, soglobalShims: { bun: false }switches one default off andglobalShims: { typescript: true }adds another package. With the default, a project that pins Node.js throughdevEngines.runtimeorengines.runtimegets the pinned stable release — authenticated against the Node.js release-team signatures — downloaded on first use and run whenever you typenodeinside the project, with no shell hooks. Candidates that are not signature-verified (Deno, Bun, Node.js prereleases, and ordinary package bins you enable) ask "Do you trust this project?" once per candidate and remember the answer machine-locally; the record values name the policy per package:"auto"(or its shorthandtrue) defers to artifact authentication,"always"switches without ever asking (useful in CI), and"prompt"always asks, even for authenticated candidates. SetglobalShims: falseto disable the feature, orPNPM_SHIM_BYPASS=1to bypass it for one invocation. On Windows, programs can keep spawning the globalnode.exedirectly, without a shell.pnpm installs the other package managers now, not just itself: npm, Yarn Classic, Yarn Berry, Yarn 6 (
yarnpkg/zpm), and Bun. Each is resolved and fetched through the trusted package-manager registries, and an npm-published one is verified against npm's signature for its exact version before it is executed.Three things use it:
packageManager/devEngines.packageManagerpin is honored, and ayarn.lockwritten by Yarn Classic no longer gets installed by Yarn Berry. pnpm provides that package manager when the dependency pinned a version, or when the host cannot satisfy what the dependency needs — so a repository built with Yarn now installs on a machine that has only pnpm, while a host that already has a suitable one keeps using its own.pnpm dlx(pnx) runs one of them for a single command:pnx yarn@4 install,pnx npm@11 ci,pnx bun@1.3.0 install. Naming a package manager, or a runtime (node,deno,bun), there now provisions the real thing instead of installing the npm package that shares its name — unless the specifier locates a package rather than asking for a released version (pnx yarn@npm:yarn@1.22.22,pnx yarn@yarnpkg/berry), which installs what it names —pnx yarn@4was previously a missing version, since Yarn 4 is published as@yarnpkg/cli-dist, andpnx node@22now runs that Node.js release rather than a wrapper that downloads one.--packagenaming a package manager picks which of its commands to run, sopnx --package npm@11 npx create-somethingruns that npm'snpx.pnpm shim add yarnlinks ayarncommand that runs whatever version the current project pins, andpnpm shim rm/pnpm shim lsmanage those shims. It works for any package, not only package managers. Shims are never created as a side effect ofpnpm setupor an install — a shim shadows the rest of yourPATH, so pnpm only writes one when asked.Installing a package manager globally (
pnpm add -g yarn) now makes it follow a project's pin too, the way a globally installed Node.js already followsdevEngines.runtime: the pinned version runs where a project pins one, and the globally installed copy is the fallback everywhere else. An explicitglobalShimsentry, includingfalse, is left as you set it.pnpm addfollows the same rule about what a name means.pnpm add -g yarn@4installs Yarn Berry — it used to fail, because npm'syarnpackage stops at Classic — andpnpm add -g node@22/pnpm add -g deno@2install that Node.js or Deno release rather than a wrapper package that downloads one. In a project, naming a package manager records which one the project uses instead of installing it as a dependency, and naming a runtime records it underengines.runtimeasnode@runtime:22already did.The declaration goes where the package manager reads it. Yarn is started from a project pin by corepack, which reads only
packageManagerand only accepts an exact version there, sopnpm add yarn@4resolves the line and writes"packageManager": "yarn@4.18.0"— the same thingcorepack use yarn@4writes, down to the+sha512.…integrity for the Yarn Classic line that corepack pins its tarball with. Every other package manager is recorded indevEngines.packageManager, which holds a range. Only one of the two fields is ever left behind: they declare the same thing, and corepack refuses to run a project whose declarations disagree.A JavaScript package manager on a machine without Node.js gets a managed LTS runtime to run on.
What changes for a project coming from v11:
pnpm add yarnrecords the project's package manager instead of installing the npm package that shares the name (that package is still reachable aspnpm add yarn@npm:yarn@1.22.22),pnpm add -g yarninstalls the current Yarn line rather than Classic,pnpm add -g node/pnpm add -g denoandpnx node/pnx denoinstall a Node.js or Deno release rather than a wrapper package, and a globally installed package manager defers to a project's pin where there is one.Added an opt-in proof of concept that lets installs reuse a dependency's build output across machines, by publishing and restoring signed, organization-scoped artifacts through pnpr instead of running the lifecycle scripts locally.
Configure it with the new
remoteSideEffectsCachesetting. A workspace names the eligibleorganizationandpackages; everything describing the act of signing —publish,keyId,builderId,trustedKeys,privateKeyand the provenance fields — is refused inpnpm-workspace.yamland read from the global config file or the environment instead.Added the
audit.ignorePrunesetting. When set totrue,pnpm audit --fixremoves ignored GHSA entries that no longer appear in the audit report.pnpm initnow pins the latest pnpm version, instead of the version of pnpm that ran the command. A project scaffolded by an outdated pnpm therefore no longer inherits that staleness through its owndevEngines.packageManager/packageManagerpin #7490.The version is read from the
latesttag on the package-manager registries. When that lookup cannot answer — no network, an unreachable or slow registry,offline, or alatestthat theminimumReleaseAge/trustPolicysettings reject —pnpm initpins the running version as before, and never fails or hangs on the lookup. Alatestthat is older than the running pnpm is never pinned either.Allowed
pnpm update --patchesto refresh registry revisions through a configured pnpr server while retaining locked package versions.Added explicit registry revision selection with
<version>+rNandpnpm update --patchesfor refreshing revision artifacts without changing package versions. Registry-backed lockfile policy checks recognize historical revisions, and pnpr now preserves safe revision histories from upstream registries.Added support for registry replacement tarballs using standard integrity values, explicit revision fields, registry routing from the
registriessetting, non-redirecting integrity-addressed URLs, canonical safe-integer revision numbers, and pnpr proxying for immutable upstream revision artifacts.Running
pnpm setup,pnpm self-update, or a command that modifies the global installation (such aspnpm add --global) throughsudonow fails withERR_PNPM_SUDO_NOT_SUPPORTEDinstead of silently operating on the root user's home directory. pnpm keeps global packages and configuration in the invoking user's home directory, so these commands never need root permissions. Read-only global commands (such aspnpm bin --global) still work under sudo.pnpm stage approvenow approves several staged packages at once. Run it without a stage id to pick from the staged versions interactively, or pass a list of stage ids. The whole batch is approved with a single one-time password, and pnpm asks for a new one only once the registry stops accepting it. Inside a workspace, the selected packages are approved in dependency order, and a package whose workspace dependency could not be approved is skipped instead of being published against a dependency that never reached the registry.Patch Changes
Deprecated the pnpmfile
filterLoghook in pnpm v12. The Rust CLI ignores it and emits a warning.The built-in compatibility database no longer adds dependencies that were detected by static analysis of published packages. Those entries named packages that are only imported for their types, so installing them was at best unnecessary and at worst broke the dependent:
@typescript-eslint/typesgained atypescriptdependency resolved to the newest release, which put TypeScript 7 under older@typescript-eslintversions and made ESLint fail with "Cannot read properties of undefined (reading 'Intrinsic')". The database keeps its@yarnpkg/extensionsentries and pnpm's own curated ones.When no directory above the project accepts a hard link — inside an AI agent sandbox that only grants write access to the project, or a container with just the project mounted writable — the default store is now created at
<project>/node_modules/.pnpm-storeinstead of in the pnpm home directory. In those environments the home store is either read-only or on another volume, which forces every package to be copied instead of hard linked #13525.Platinum Sponsors
Gold Sponsors
v11.28.5: pnpm 11.28.5Compare Source
This release reads cached registry metadata faster and makes
pnpm config get --globalignore project settings. It also carries several security fixes for package archives, git dependencies, and config dependencies.Patch Changes
Security
pnpm now verifies locked config dependencies against their registry before installing them. Config dependencies must come from an npm registry. The lockfile can no longer replace the integrity of a config dependency pinned with
version+integrity.Lockfile verification now checks the tarballs inside a
variationsresolution against the registry. Aname@versionlockfile entry with an emptyvariationsresolution is now rejected.pnpm audit signaturesnow verifies signatures against the integrity recorded in the lockfile. Packages without a recorded integrity cannot pass signature verification.pnpm now rejects a git dependency whose lockfile repository is empty, begins with
-, or contains a null byte. Git can no longer read such a value as a command-line option pnpm/tasks#84.A git dependency with a
#path:subpath can no longer reach files outside the repository through a symlink in the subpath.pnpm pack,pnpm publish, and installs of git and local directory dependencies now leave out files that a directory symlink or abundleDependenciesentry points to outside the package directory pnpm/tasks#83 pnpm/tasks#93.pnpm deploywithdeployAllFilesnow rejects symlinks that point outside the package directory. Local package installs with this setting apply the same check.pnpm no longer hangs on a package archive with a negative PAX record length or an entry of 4 GiB or more pnpm/tasks#78 pnpm/tasks#79.
Large package downloads and large files inside gzip and bzip2 package archives now use bounded memory during installation. Package manifests and archive metadata larger than 64 MiB are rejected.
pnpm publishalso rejects manifests and README files larger than 64 MiB in pre-built tarballs before reading them into memory.Two URL or local path dependencies no longer share a virtual store directory when one URL has
+,#,:, or?where the other has/. Such dependencies, including git dependencies pinned with#, now get a hash suffix on their directory name.pnpm licensesnow removes terminal control characters from package metadata in table output.The warnings about ignored project
.npmrcregistry and auth settings no longer print the username and password of a URL-scoped key such as//user:password@registry.example.com/:_authToken.Installing and resolving dependencies
Dependency resolution reads cached registry metadata faster. The metadata cache moved to
<cache-dir>/v12/, so the first install after upgrading downloads registry metadata again. A damaged cache entry is downloaded again, or reported as an error when--offlineis set #13512.pnpm installnow fails withERR_PNPM_UNSUPPORTED_PROTOCOLwhen a dependency uses a specifier with a protocol pnpm does not support, such as Yarn'spatch:. pnpm linked such a dependency to a directory that does not exist #16590.When a dependency moves an exact dependency of its own to an older version, a peer dependency that pnpm installed automatically now moves with it. Before,
pnpm installandpnpm dedupekept the newer locked version of the peer, so the lockfile held two copies of it, for example two copies ofvuepnpm/tasks#61.pnpm addandpnpm installnow keep the peer dependencies thatpnpm-lock.yamlrecords for a package they did not update. A registry whose metadata disagrees with the package'spackage.json, for example by omittingpeerDependenciesMeta, madepnpm addandpnpm dedupewrite different lockfiles, sopnpm dedupe --checkfailed afterpnpm add#16615.Configuration
pnpm config getandpnpm config listwith--globalor--location=globalnow show only the global configuration. Both flags included the project's.npmrcbefore.--location=globalalso included the project'spnpm-workspace.yaml.pnpm config get --globalfailed when the global bin directory was not in PATH #16598.pnpm now prints config warnings, such as an unset environment variable in
.npmrc, when loading the config fails.pnpm now fails when
httpProxyorhttpsProxyinpnpm-workspace.yamlor the global configuration is not a string.pnpm dlxnow uses thereleaseentry ofnodeDownloadMirrorsfrom the workspace configuration when downloading Node.js runtimes #11281. Mirrors for other channels, such asrcandnightly, still apply only from the global configuration.Commands
pnpm runandpnpm execnow forward--config.*command-line flags to the install started byverifyDepsBeforeRunpnpm/tasks#60.pnpm dlxwith--packagebut no command now fails with'pnpm dlx' requires a command to run. Before, it installed the package and then crashed trying to run an empty command.pnpm unpublish <pkg>@<version>now deletes the tarball under the registry's path when the registry is served under one, such as Gitea's npm registry. It used to send the delete to the host root and report success without removing the version #16568. It also no longer mistakes a sibling path such as/npm-mirror/for the registry path/npm/pnpm/tasks#94.The interactive
pnpm audit --fixpicker now shows each patched version with thesaveExactandsavePrefixstyle that the override is written with #13209.pnpm listnow reports the correct package paths whennodeLinkerishoisted#9593.pnpm setupnow puts$PNPM_HOME/binfirst onPATHin login shells that inherited it further down, such as the VS Code terminal on macOS. Before, anothernodetook precedence over the one installed bypnpm runtime set node -g. Runpnpm setupagain to update the block in your shell config #16635.pnpm setupnow names the shell config file even if it is already up to date #16608.Output and messages
A warning about a project's
devEnginesorpackageManagerpin is now printed to stderr. A command such aspnpm cache pathorpnpm list --jsonkeeps only its own output on stdout #16584.The warning for a non-root
resolutionsfield now points at theoverridesfield inpnpm-workspace.yaml#11757.Platinum Sponsors
Gold Sponsors
v11.28.4: pnpm 11.28.4Compare Source
pnpm 11.28.4 fixes two ways credentials could leak, makes
pnpm install --frozen-lockfileaccept several lockfiles it rejected, warns when an optional dependency cannot be fetched, and stopspnpm self-updatefrom installing a second pnpm next to a Homebrew one.Patch Changes
pnpm loginno longer forwards credentials in its request body to another origin during redirects.The error for a tarball that fails its integrity check no longer prints credentials, query strings, or fragments from the tarball URL.
Installing packages
pnpm install --frozen-lockfilenow succeeds in a project with no dependencies whenpnpm-lock.yamlrecords only the pinned pnpm version. Other commands write such a lockfile when they run before the first install. A lockfile missing the---line after that section is accepted too #16477.pnpm install --frozen-lockfileagain succeeds when a workspace project recorded inpnpm-lock.yamlhas no directory, such as a project left out of a Docker build context. It still fails if the project's directory exists without apackage.json#16453.pnpm install --frozen-lockfileno longer fails withERR_PNPM_OUTDATED_LOCKFILEfor a workspace project that declaresdependenciesMetaand whose dependencies are all workspace links. pnpm now records that project'sdependenciesMetainpnpm-lock.yaml#16457.Fixed frozen installs replacing a hoisted dependency with a workspace package of the same name. A later
pnpm dedupethen removed the hoisted link #16485.With
enableGlobalVirtualStoreon, scripts can run entry points that a CommonJS require hook loads again, such asts-node index.ts. They failed withERR_UNKNOWN_FILE_EXTENSIONon Node.js versions without built-in TypeScript support #16436.Optional dependencies
pnpm installnow prints a warning with the error when an optional dependency cannot be fetched and is skipped. The skipped package is no longer linked intonode_modulesas a broken symlink or listed among the added dependencies. Thepnpm:skipped-optional-dependencylog reports the skip with thefetch_failurereason #16514.When an optional dependency fails to build, pnpm now removes its link from
node_modules. A repeatpnpm installthen reports "Already up to date" and no longer reruns the failing build #16468.Fixed frozen installs creating symlinks to the working directory for skipped optional dependencies and unresolved peer dependencies #16454.
Hoisted node_modules
With
nodeLinker: hoisted, a filtered install now keeps the packages of the workspace projects an earlier install put innode_modules. This also covers the install thatpnpm --filter <selector> runandpnpm --filter <selector> execstart before the command. Before, these installs removed every package that only the unselected projects needed #16483.A filtered install of a workspace project also no longer fails with
ERR_PNPM_LOCKFILE_MISSING_DEPENDENCYafter a filtered install of another project.pnpm installwithnodeLinker: hoistednow refreshes directories supplied by custom fetchers when reinstalling.Speed and network
pnpm now revalidates cached registry metadata with a conditional request, so the registry can answer
304 Not Modified. Before, pnpm downloaded the whole document again for registries whose responses forbid caching, such asCache-Control: no-store#16528, and for packages published withinminimumReleaseAge#16506.A fetch timeout while other downloads from the same host are still running now lowers concurrency for that host to one connection. Retries of that request, and later downloads from that host, use the lower concurrency. Other hosts keep the configured concurrency #12791.
Running scripts
Scripts run without a terminal no longer start a second
sheach. One watchdog per pnpm command now ends every script's process group if pnpm is killed, sopnpm -r runacross many projects starts half as many processes #16489.pnpm runandpnpm execnow warn and run the command when the install thatverifyDepsBeforeRunstarts fails. This lets scripts run in sandboxes where pnpm cannot install, such as containers with a read-only store or no network #15173.A filtered
pnpm runorpnpm execnow finds dependencies out of date when a workspace dependency of a selected project has nonode_modulesdirectory, as after a filtered install. WithverifyDepsBeforeRun: install, pnpm installs that dependency before running the command pnpm/tasks#45.pnpm rebuildandpnpm approve-buildsrefresh command launchers when a build changes a command's interpreter or replaces it with a native executable. Dependent packages' build scripts use the refreshed launchers.Updating pnpm
pnpm self-updatenow fails for Homebrew-installed pnpm and prints thebrew upgradecommand for the installed formula, such asbrew upgrade pnpmorbrew upgrade pnpm@11. It used to install a second copy of pnpm that the Homebrew one kept shadowing #16547.On Windows,
pnpm self-updatenow replaces apnpm.exeleft inPNPM_HOMEor inPNPM_HOME\bin. InPNPM_HOME, that executable kept running the old version after a successful update. InPNPM_HOME\bin, the update failed withEPERM. If the executable was inPNPM_HOME,self-updatenow asks you to runpnpm setup#9094.pnpm can now switch to a
packageManagerversion below 11 on x64 musl Linux, such as Alpine #16467.A
devEngines.packageManagerrange now records the running pnpm inpnpm-lock.yamlonly if it meetsminimumReleaseAge. Otherwise pnpm records the newest version in the range that meets it. If no version in the range does, pnpm still records the running pnpm #16431.Filtering, settings, and other commands
The
[<since>]filter selector works again with Git 2.24 through 2.27 #16561. With Git older than 2.24, the selector now fails with an error that names the required Git version.Package-name filters now support
?to match one character #2817.pnpm -r pkg getnow reports every selected project when several share a package name. Projects with the same name are keyed by their directory relative to the workspace root. Before, only one of them appeared in the output.pnpm now reports an
INVALID_SETTINGerror whenallowUnusedPatchesinpnpm-workspace.yamlis not a boolean, or whenignoredOptionalDependenciesorrequiredScriptsis not an array of strings. A quotedallowUnusedPatchesvalue such as"false"was treated astrue.pnpm store path,pnpm store status, and other commands that look up the default store no longer fail when the current directory is not writable. pnpm now uses the store in the pnpm home directory in that case #16554.Platinum Sponsors
Gold Sponsors
v11.28.3: pnpm 11.28.3Compare Source
pnpm 11.28.3 updates
undicito clear a security advisory, fixes "database disk image is malformed" errors when several pnpm processes share a store, and makes packages, catalogs, projects, and commands named likeconstructorwork.Patch Changes
Installing packages
pnpm now ships
undici7.29.1, so security scans of pnpm no longer report GHSA-3wwx-pv8p-q78v.pnpm no longer fails with "database disk image is malformed" or reads stale store entries while another pnpm process writes to the same store.
Names that match built-in JavaScript object properties, such as
constructor,toString, or__proto__, now work like any other name. pnpm crashed, wrote a wrong lockfile, or silently skipped such names in:pnpm add,pnpm install, andpnpm import, for dependencies, peer dependencies, andfile:dependencies that point to a directory namedconstructor.toStringwas not written.$toString.pnpm list, andpnpm why.pnpm constructorruns theconstructorscript like any other unknown command, andpnpm help constructorno longer crashes.constructor.pnpm installno longer re-resolves an up-to-date lockfile on every run when a patched package is a peer in a peer cycle #16418.POSIX bin shims and the
pnpm,pn,pnpx, andpnxlaunchers now run inside a Nix build, where the system default path holds none of the utilities they call. Installing again replaces the shims already innode_modules#16377.In a project that pins another pnpm version, pnpm now passes a command with an option it does not know to the pinned version. Before,
pnpm install --auto-dedupefailed with "Unknown option" even though the pinned pnpm supports it #16353.pnpm now fails with
ERR_PNPM_INVALID_ALLOW_BUILDSwhenallowBuildsis not an object or one of its values is nottrue,false, or a string. Such values used to be ignored silently.Removing a dependency whose bins are declared through
directories.binno longer leaves broken shims innode_modules/.bin.A custom resolver's
shouldRefreshResolutionhook that rejects no longer crashes pnpm with an unhandled rejection when another hook has already asked for a refresh.Updating dependencies
When
minimumReleaseAgehides the version thatlatestpoints to, pnpm now falls back to a prerelease of the same major before a stable version of an older major. A stable version of the same major is still preferred. For example, while a new1.0.0is too new, pnpm picks1.0.0-beta.4rather than an old0.0.1#16388.pnpm --filter <project> update <pkg>now fails withERR_PNPM_NO_PACKAGE_IN_DEPENDENCIESwhen the selected projects do not depend on<pkg>, also in a workspace with a shared lockfile and a root project. It used to exit successfully.pnpm audit --fixnow updates vulnerable packages in a single project that setsupdateConfig.ignoreDependencies. It used to leave them on the vulnerable version.pnpm update --globalnow removes hard-linked executables fromPNPM_HOMEwhen migrating packages from the old global layout #16420.Updating a pinned GitHub Action now rewrites the version in its
# vX.Y.Zcomment even when the action name contains the same version text. The action name used to change while the comment kept the old version.Workspaces and deploy
pnpm deployno longer fails withERR_PNPM_DEPLOY_AMBIGUOUS_PEERin a workspace withinjectWorkspacePackages: truewhen a workspace package also lists its peer dependency as a dev dependency #16375.pnpm deployno longer copies the workspace root'spackageManageranddevEngines.packageManagerfields into the deployedpackage.json#16403.--filterfixes:...pkg...selector combined with another dependents selector, such as--filter ...a --filter ...b..., no longer adds the dependencies of the other selector's dependents.--filter "[<since>]"now detects changes in projects whose directory names contain non-ASCII characters. The change used to be credited to the parent project.Running scripts
After relaying a signal to a script, pnpm keeps waiting for a process in the script's process group whose main thread has exited while its other threads still run. Linux reports such a process as a zombie, so the wait used to end before those threads finished pnpm/tasks#56.
A lifecycle script run with
unsafePerm: falsenow fails with an error when pnpm cannot createnode_modules/.tmp. It used to hang.pnpm runwithverifyDepsBeforeRunno longer crashes with an unhandled rejection when a lockfile it did not need to compare fails to load.pnpm run -rnow closes the collapsible CI log section of a project whose script fails, so the output of later projects is no longer nested inside it.pnpm run --resume-fromno longer crashes when a saved run state file containsnull.Store
pnpm store prunenow removes the packages that only expiredpnpm dlxcache entries used, as long as the store still has another registered project. They used to stay until the nextpnpm store prune#16383.pnpm store prunenow stops with an error when it cannot read a project directory for a reason other than the directory missing, such as a permission error. It used to skip the directory.Publishing and registry output
pnpm publishnow includes bareREADMEfiles and README files with Markdown extensions such asreadme.markdownin registry metadata #12704.pnpm pack-appnow accepts an entry file or output directory inside the project whose name starts with two dots, such as..build/entry.cjs. It used to fail withERR_PNPM_PACK_APP_ENTRY_OUTSIDE_PROJECT.Registry error messages now always say "(response body truncated)" when pnpm cut the response body short. The marker was missing when the body was cut at exactly 64 KiB.
Platinum Sponsors
Gold Sponsors
v11.28.2: pnpm 11.28.2Compare Source
pnpm 11.28.2 fixes
pnpm installskipping every workspace project whose common ancestor is the filesystem root, and stopspnpm runfrom reinstalling or installing when nothing needs it.Patch Changes
pnpm installreported success without installing anything when the workspace projects' common ancestor was the filesystem root, such as/or a drive root likeC:\. It now installs these projects #16328.verifyDepsBeforeRunno longer reports dependencies as outdated after a filtered install just becausepnpm-lock.yamlhas a newer modification time. It checks the lockfile against the packages that install put in place. Before,pnpm runreinstalled the whole workspace with lifecycle scripts on, for example after a DockerCOPYbrought in a lockfile with a newer mtime #16322.After a filtered install,
verifyDepsBeforeRunnow also checks that the install put the selected projects' dependencies in place. Anode_modulesdirectory alone no longer counts as proof.pnpm runandpnpm execno longer install a project that has never been installed and has nothing to install. Such a project declares no dependencies, no peer dependencies thatautoInstallPeerswould fetch, and no install lifecycle scripts. The command now runs without writingnode_modulesorpnpm-lock.yaml#16313.Platinum Sponsors
Gold Sponsors
v11.28.1: pnpm 11.28.1Compare Source
pnpm 11.28.1 makes
pnpm installwork in StackBlitz WebContainers, repairs lockfiles whose patch hashes disagree withpatchedDependencies, and fixes many bugs in Windows command shims, injected workspace dependencies, andpnpm deploy.Patch Changes
Installing packages
pnpm installnow works in StackBlitz WebContainers. On projects without a lockfile, it used to fail withENOENT ... pnpm-lock.yaml, because pnpm did not recognize the errors that WebContainers return from asynchronous file system calls. It also failed with "this.db.exec is not a function", becausenode:sqlitethere lacksDatabaseSync.exec. Whennode:sqlitecannot prepare statements either, pnpm stores the index inindex.fallback#15649.pnpm installnow completes after downloading a Node.js runtime specified bydevEngines.runtimewhen pnpm runs on Node.js 24.4.x #14667.pnpm installno longer fails when a package from the registry declares afile:dependency on a directory inside itself, such as"@types/css-tree": "file:./typings/css-tree". pnpm links that dependency to the directory inside the package, as npm and Yarn do. The lockfile records it aslink:<root>/typings/css-tree#9141.Installing a git-hosted dependency that has to be built no longer fails when that dependency's own dependencies have build scripts nobody approved. pnpm skips those builds while preparing the dependency, as it does without
strictDepBuilds#9764.pnpm now uses pnpm to prepare a git-hosted dependency that is a pnpm workspace without a committed lockfile. It used npm before, which could skip the dependency's build #14011.
When installing a git dependency over SSH fails with
Permission denied (publickey), pnpm suggests checking the loaded keys withssh-add -l. Resolving an SSH URL that refuses the key also shows a local HTTPS rewrite that leaves the recorded URL alone #13743.pnpm install --devandpnpm fetch --devnow install the optional dependencies of devDependencies, such as the platform binaries of Biome and oxlint. The project's ownoptionalDependenciesare still skipped #9678.pnpm install --frozen-lockfilenow works on a detached HEAD whengitBranchLockfileis enabled. The install now reads the lockfiles of the local and remote-tracking branches that contain the checked-out commit. It still writes the sharedpnpm-lock.yaml#7672.pnpm installon CI now fails on an outdated lockfile whenpreferFrozenLockfileis explicitly set totrue. Setting it totrueused to let CI update the lockfile #9072.pnpm installnow fails withERR_PNPM_IGNORED_BUILDSon a repeat install whenstrictDepBuildsis on and a dependency's build is still undecided. A repeat install against an existingnode_modulesreported success where a fresh install failed #10450.pnpm installnow removes an optional dependency fromnode_modulesif its install script fails. Code that checks whether the package is installed no longer finds a package that cannot load #8756.pnpm install --offlineandpnpm add --offlinenow resolve a version range to the newest matching version whose tarball is already in the store. They used to pick the newest version in the cached metadata and fail withERR_PNPM_NO_OFFLINE_TARBALLwhen its tarball was missing #10715.If an offline install fails because the registry metadata cache uses the layout from before pnpm 11.27 and 12.4, the error now names the older mirror on disk and explains that one online install repopulates the cache #15656.
pnpm installnow fails right away when writing package files fails because the store is full. It no longer retries the tarball download first #8581.With
nodeLinker: hoisted,pnpm installnow restores a workspace project'snode_modulesafter it was deleted. Before, the install printed "Already up to date" and left the project without the dependencies nested under it.Under
nodeLinker: hoisted,pnpm installnow clears orphaned package directories that an interrupted or failed install leaves in a project'snode_modules. A directory recorded by the previous install is removed, while an unrecorded directory is moved tonode_modules/.ignored. A copy already in.ignoredis never overwritten #13676.Packages in an external
virtualStoreDircan resolve the project's direct dependencies selected byhoistPattern. Runpnpm install --forceto repair an existing installation #5652.A repeat install now keeps the fast path when a declared local file dependency is replaced by an override #12892.
Store, build cache, and global virtual store
Files imported from the store now follow the umask of the install that writes them. Installing with a umask of
077no longer leaves imported files readable by the group and others #3807.With the global virtual store,
pnpm rebuildno longer modifies packages shared with projects that have not approved their build scripts #12302.The side-effects cache now restores the symlinks that a build script creates inside a package. A warm install used to replace each of them with a copy of its target #12859.
After upgrading, every package with a build script is built once more.
pnpm installnow restores cached build artifacts when reinstalling a workspace that uses separate lockfiles #12942.The global virtual store and the side-effects cache now key built packages by the Node.js version that the root project's
devEngines.runtimeorengines.runtimepins. That is the Node.js their build scripts run with. A dependency that declares its ownengines.runtimeno longer changes the key for every other package.Concurrent installs that share a global virtual store now run a package's build in its shared slot one at a time. A failed build leaves the slot in place and marks it for the next install to rebuild #15568.
Concurrent installs no longer fail when they replace the same stale hoisted dependency link. Virtual store cleanup now keeps the temporary lockfiles that concurrent installs are still writing.
pnpm installkeeps the owner, group, and mode of files already in a shared store, includingindex.db. New store files and directories inherit the store directory's group-write bit. When that directory is setgid, new files inherit its group. pnpm does not change a file's owner or group #12765.When
pnpm installrepairs a store file that was modified through a hard link innode_modules, the repair now keeps the file's inode on Linux and macOS. Hard-linked copies in other projects are healed at the same time. Before, only the project running the install received the restored content. On Windows the repair still replaces the file, so other projects are healed on their next install #3445.A tarball whose integrity pnpm computed during download is now found in the store on the next install. Before, that install downloaded the tarball again once the lockfile recorded the integrity #12562.
pnpm now warns when it cannot hard link packages from an existing store in the pnpm home directory and uses a store on the project's filesystem. This can happen when the project is on another filesystem, such as a bind-mounted workspace in a container. The warning names both stores and suggests setting
storeDir#14505.Resolving and linking dependencies
A peer dependency no longer resolves to two different versions for one package. This happened when the package peer-depends on another package and on one of that package's peers, and it is installed deeper than a direct dependency of the package that provides them #12098.
An optional peer dependency is no longer resolved from another workspace project's package when the project provides one of that package's own peers at a version it rejects. This avoids bogus unmet peer errors #13989.
pnpm no longer reports unmet peer dependency warnings for aliased
npm:peer ranges that a tarball dependency satisfies #11126.pnpm installnow links the executables of auto-installed peer dependencies into the workspace root'snode_modules/.bin, including after a frozen-lockfile reinstall #8511.With
resolutionMode: time-basedandminimumReleaseAgeboth set,pnpm installno longer reports a subdependency as too new when only the time-based cutoff excludes it. Such subdependencies used to fail a strict install withERR_PNPM_NO_MATURE_MATCHING_VERSION, or were added tominimumReleaseAgeExclude#13569.A transitive dependency that has no matching version published before the time-based cutoff now resolves to the lowest matching version allowed by
minimumReleaseAge. pnpm picks a version younger thanminimumReleaseAgeonly if no older version matches #16298.With
minimumReleaseAgeset, re-resolving the lockfile no longer rewrites thepeerDependenciesrecorded for a package whose version did not change. This happened when the registry metadata of a package differed from thepackage.jsonin its tarball #13988.pnpm no longer revalidates cached registry metadata when the registry sends
Cache-Control: max-age=0,no-cache, orno-store. It downloads the metadata again, so a version newly published to such a registry is visible on the next install #13487.pnpm's built-in package compatibility database no longer applies to a project's own manifest. A project named like a published package, such as
vue-loader, no longer gains dependencies onpnpm installorpnpm update. User-configuredpackageExtensionsstill apply to project manifests #11700.Relative local tarball paths in
pnpm.overrideswithout an explicitfile:prefix are now rebased correctly for workspace packages #11131.pnpm no longer reports
pnpm-lock.yamlas broken when a project depends on a package namedconstructor. A__proto__key in the lockfile is now kept as a plain entry when pnpm reads or writes the lockfile. It no longer replaces the prototype of the objects pnpm builds from it #11028.With
nodeLinker: pnp, a workspace package can now require another workspace package it depends on #3567. On Windows, workspace dependency paths in the generated.pnp.cjsnow use forward slashes.Performance
pnpm now uses less memory when installing a package whose archive is larger than 64 MiB unpacked, and when installing a runtime from a zip archive, such as Node.js on Windows, Deno, or Bun #14164.
A fresh install reusing a warm global virtual store skips reimporting packages whose target directory is already complete #11112.
A warm
pnpm installreuses on-disk package metadata for five minutes when the registry does not send an ETag. Registries that send an ETag, including the public npm registry, still revalidate with a conditional request.pnpm updatestill fetches current metadata #13976.Patched dependencies
pnpm installnow repairs apnpm-lock.yamlwhose(patch_hash=<hash>)dependency paths disagree with itspatchedDependenciesmap, including paths that lack the hash their patch calls for. pnpm previously accepted such a lockfile as up to date and kept the old patched files.pnpm install --frozen-lockfilenow fails on such a lockfile withERR_PNPM_INCONSISTENT_PATCH_HASH. It fails withERR_PNPM_UNCHECKABLE_PATCH_HASHwhen a patch hash in the lockfile is malformed, or when the lockfile lacks the package version or patch entry that the check needs #15336.pnpm installnow fails withERR_PNPM_PATCH_NOT_FOUNDwhen a patch file listed inpatchedDependenciesdoes not exist. It used to fail with a rawENOENTerror and a stack trace #5268.pnpm now fails with
ERR_PNPM_INVALID_PATCHED_DEPENDENCYwhenpatchedDependencieshas an invalid shape or contains a non-string value.engineStrictnow checks the patchedpackage.jsonwhen apatchedDependenciesentry changesengines. A patch that relaxesengines.nodeno longer fails the install against the published range #9603.pnpm patchnow applies the existing patch file to the edit directory of a git-hosted dependency, as it already does for packages from the registry #9699.pnpm patch-commitnow fails with an error whengitcannot be found inPATH. It previously reported that no changes were found #8666.Workspaces and filtering
pnpm installrefreshes injected copies of workspace packages when source projects are rebuilt. Injected copies previously stayed stale untilpnpm install --force#4407.Scripts listed in
syncInjectedDepsAfterScriptsnow update injected dependencies while they run. A watcher on the injected package, such as a dev server, sees each change before the script exits #4410.pnpm installno longer fails for an injected workspace dependency whose package publishes from apublishConfig.directorythat its ownpreparescript builds. The injected copy now picks up that directory oncepreparefinishes building it.pnpm install --frozen-lockfileno longer reports the dependency as outdated while the directory has not been built yet #7811.With
sharedWorkspaceLockfile: false, an injected workspace package that has lifecycle scripts is now hard linked into the projects that depend on it. Before, pnpm left a plain copy, so later edits to the package did not reach those projects #9828.injectWorkspacePackagesnow hard links a workspace dependency declared with a relative path, such asworkspace:../foo, the same way it already does forworkspace:*#10446.pnpm installno longer creates anode_modulessymlink inside thepublishConfig.directoryof a workspace package linked withlinkDirectory. A build tool that cleaned its output directory through that symlink deleted the files of the package's dependencies.pnpm installalso removes a symlink that an earlier install left there #16226.When
verifyDepsBeforeRuntriggers an install before a filteredpnpm runorpnpm exec, pnpm now installs only the selected projects and their dependencies. A later filtered command also installs a selected project that an earlier filtered install skipped #11865.pnpm importin a workspace now keeps the versions pinned by ayarn.lockinside a workspace project #4385.Installing with
pnprServerset now records the pnpmfile checksum in the lockfile, so a laterpnpm install --frozen-lockfileaccepts that lockfile. A frozen install through the pnpr server now fails if the pnpmfile changed. If the pnpmfile defines areadPackage,afterAllResolvedorpreResolutionhook or custom resolvers, pnpm resolves dependencies locally and prints a warning that the pnpr server was not used #14460.Installing through a pnpr server now links a workspace project at the directory its
publishConfig.directorynames. An install that resolves through a server which does not forward the setting fails withERR_PNPM_PNPR_PUBLISH_DIRECTORY_MISMATCH. The server rejects apublishConfig.directorythat points outside its project.Adding, updating, and removing dependencies
With
autoInstallPeers,pnpm addandpnpm removein a workspace project keep the locked version of a peer dependency the project declares. In a workspace where another project depended on a different version of that package, the peer could switch to that version #11225.Adding a dependency now keeps unrelated transitive dependencies on their locked versions #11456.
pnpm update --recursive <pkg>no longer changes the version of a peer dependency that another workspace project installs automatically. Such a peer could move to a version outside the range the project declares, for example to React 19 in a project that declaresreact: ^18.3.1#14928.pnpm update package@betaon a dependency declared ascatalog:now updates the catalog entry and keepscatalog:inpackage.json. Before, pnpm wrote the resolved specifier topackage.json#13399.pnpm updatenow applies an override that references a catalog with the catalog's new value when the update bumps that catalog entry. Before, the packages the override targets kept the old version in the lockfile #12159.pnpm add <dir>now warns when the added directory declares peer dependencies, aspnpm linkdoes. The directory is saved as alink:dependency, and its peers are not resolved from the project that adds it. Use thefile:protocol to have them resolved #5523.Running scripts and commands
pnpm runandpnpm execno longer install dependencies automatically when the rootpackage.jsonstill keepsoverrides,packageExtensions,patchedDependencies, orignoredOptionalDependenciesin itspnpmfield. pnpm no longer reads that field, so the install rewrote the lockfile without those settings. The command now fails and asks to move the settings topnpm-workspace.yaml#16278.pnpm runno longer reinstalls dependencies when anode_modulesdirectory installed outside CI is used withCI=true, or the other way around #12337.pnpm no longer hangs after a lifecycle script exits while a process it started in the background keeps the script's output open. pnpm stops reading that output one second after the script exits #5730.
A signal sent to pnpm, such as
SIGTERM, now reaches the pnpm that pnpm switches to because ofpackageManagerordevEngines.packageManager, and the one thatpnpm withruns. The signal used to be dropped, so scripts running under that pnpm never got to shut down #9948.pnpm runexits with the code of a script that handles Ctrl+C and shuts down. A script that finished cleanly is not reported as a lifecycle failure. The commands after it in the same script still run #9945.pnpm runand lifecycle scripts use the configuredscriptShell, including Git Bash on Windows, whenshellEmulatoris also enabled.shellEmulatorstill runs scripts whenscriptShellis not set. Extra arguments passed topnpm runare quoted for the shell that runs the script, so a Windows path stays intact #14719.pnpm -r run /regexp/now honors thetasksdependsOndeclared for each script the selector matches, like running the script by name does. Matched scripts that depend on each other run in order. Each matched script runs once #15596.Commands run from a POSIX shell through a dependency's own
node_modules/.bin, such asnode_modules/vite/node_modules/.bin/esbuild, no longer fail withMODULE_NOT_FOUND#10189.pnpm rebuildwithnodeLinker: hoistedno longer puts one package's parentnode_modules/.bindirectories on thePATHof the packages it builds after it.Publishing, packing, and deploying
pnpm publishnow waits at least 5 minutes for the registry to answer a publish request, like npm. This fixes "409 Conflict - Failed to save packument" errors when the registry is slow to answer #11454.pnpm deploywith a shared lockfile now copies workspace dependencies into the deploy directory, even whenpackageImportMethodis set tohardlink. Previously, their files were hard-linked to the workspace sources, so editing a source file also changed the deployed copy #12176.pnpm deploy --prodno longer fails withERR_PNPM_OUTDATED_LOCKFILEwhen the deployed project declares adevEngines.runtimewithonFail: download. The runtime stays out of the deployednode_moduleswith the rest of the dev dependencies #15703.pnpm deploy --legacyno longer leaves broken links to nested local dependencies of workspace packages #9575.pnpm deployno longer creates extra directories inside the deploy target and workspace projects when using a relative deploy path #10981.Manifests and configuration files
Settings given on the command line, such as
--registryand--store-dir, now take precedence over the values a pnpmfileupdateConfighook sets #14063.An
updateConfighook that returnsregistriesByScopewithout thedefaultor@jsrentry no longer crashes the install withInvalid URL. A missingdefaultkeeps the configuredregistry, and a missing@jsrfalls back to the built-in JSR registry #15619.The hook's
registryand thedefaultentry of itsregistriesByScopenow set one default registry, which installs,pnpm publish, andpnpm loginall use. If a hook changes both,registrywins. A route that is not a string fails withERR_PNPM_INVALID_UPDATE_CONFIG_RESULT.An
updateConfighook in.pnpmfile.cjshas to useconfig.registriesByScope,config.registriesByPrefix, andconfig.registryOptionsByUrl. These registry lookups were renamed in pnpm 11.23.0 fromconfig.registries,config.namedRegistries, andconfig.registryOptions#15620.An async
updateConfighook that resolves toundefinednow fails withERR_PNPM_CONFIG_IS_UNDEFINED, as a synchronous hook that returnsundefinedalready did.pnpm config set --location=projectandpnpm config delete --location=project, run from a package inside a workspace, now write settings that belong inpnpm-workspace.yamlto the workspace root'spnpm-workspace.yaml. Before, they created a newpnpm-workspace.yamlin the current package, which made that package the workspace root. Settings stored in.npmrcare still written to the current directory #13757.pnpm now reads the workspace directory override from
PNPM_CONFIG_WORKSPACE_DIR, like other settings.NPM_CONFIG_WORKSPACE_DIRstill works as a fallback #16275.A
${VAR}placeholder in.npmrcorpnpm-workspace.yamlwhose name matches a built-in object property, such as${toString}, is now treated as an unset variable. It used to be replaced with the source text of a JavaScript function.pnpm now rejects a falsy non-array
packagesfield inpnpm-workspace.yaml, such aspackages: false, with an error. It used to treat the field as omitted.Global packages, pnpm versions, and runtimes
pnpm update --globalnow reinstalls the global packages that pnpm 10 installed into the previous global directory,<global-dir>/5, so their commands are linked into the pnpm homebindirectory again andpnpm list --globallists them. Once every package is migrated, pnpm deletes the previous directory and the commands pnpm 10 linked into the pnpm home #11528.pnpm env remove --globaldeletes Node.js versions that pnpm installed into its own store, including when another tool installed pnpm #8357.pnpm self-updateno longer suggests a downgrade whenminimumReleaseAgeholds back the registry'slatestrelease. It now says that release is still within the cutoff #12006.@pnpm/exeno longer ships a binary for arm64 musl Linux, such as Alpine on ARM. The published binary crashed with a segmentation fault at startup. Installing@pnpm/exeon that platform now fails with an error that suggestsnpm install -g pnpmor pnpm 12 #10443.The macOS and Linux release archives no longer include Windows-only files, such as
node-gyp.cmdand thefastlistexecutables #11352.Windows and WSL
On Windows,
pnpm runnow passes the arguments after the script name to the script as typed. Before,cmdexpanded%VAR%in them and backslashes arrived doubled. Line breaks still arrive as the two characters\n, becausecmdcannot pass them. The command line pnpm prints for the script quotes the arguments the same way on every platform #16257.On Windows, command shims now run tools whose paths contain non-ASCII characters #6999. The PowerShell shims do so in Windows PowerShell 5.1 too #16217.
On Windows, the
.cmdcommand shims innode_modules/.binnow keep a%in the project path. Before, cmd.exe expanded it as a variable reference, so the command received a mangledNODE_PATH#15716.Bin shims in
node_modules/.binrun from Cygwin on Windows again. The shims passed a/cygdrive/c/...path to the Windowsnodefound onPATH, so Node.js failed withCannot find module 'C:\cygdrive\c\...'#12845.pnpm setupno longer writes thepn.ps1,pnpx.ps1, andpnx.ps1PowerShell wrappers. It also removes the ones an earlier setup wrote. PowerShell now runspn,pnpx, andpnxthrough their.cmdwrappers, likepnpmitself. Before, these aliases failed with a "not digitally signed" error wherever the execution policy blocks unsigned scripts #8444.On Windows, globally installed
@pnpm/execommands now run in the invoking PowerShell console and return their exit status #6503.On Windows, installing
@pnpm/exewith npm inside a project now writesnode_modules/.binshims that run the standalone executable #15688.On Windows,
pnpm env use -gandpnpm add -g node@runtime:<version>now replace anode.exein the global bin directory that is a broken symlink. Previously they failed withENOENT#5411.On Windows, pnpm expands nested
%VAR%references inPNPM_HOMEand the other directory environment variables it uses for its home, store, cache, state, and config directories. pnpm fails with an error when a%VAR%reference remains after expansion #13236.On Windows, if the global bin directory is not in
PATHand aPATHentry still contains an unexpanded variable such as%PNPM_HOME%, the error now names that entry. A variable referenced from the userPathmust be set to a full path and stored as a plain string (REG_SZ) for the entry to expand #5283.On Windows,
pnpm setupno longer garbles non-ASCII characters in existingPathentries #6346.On Windows,
pnpm setuprepairs thePNPM_HOMEregistry type left by older pnpm versions, even when the configured directory has not changed.On Windows, the
ERR_PNPM_BAD_ENV_FOUNDerror ofpnpm setupnow shows the valuePNPM_HOMEis currently set to. It used to show the directory pnpm wanted to set.On Windows,
pnpm installno longer skips a dependency's build script on a later install when the script changes nothing inside the package directory #15667.On Windows, pnpm now retries writing the workspace state file while another process, such as an antivirus scanner, briefly holds it open #14550.
Inspecting dependencies
pnpm licenses listnow reports the actual on-disk package locations when usingnodeLinker: hoistedorshamefully-hoist: true#8589.pnpm licenses list --jsonnow includes every installed copy of a package in itspathsarray, including hoisted copies and isolated installations with different peer dependencies. Its paths also exist on disk when the isolated linker uses a custommodulesDir.pnpm listnow shows the correct path of alink:dependency that points to a directory on another drive on Windows. The path used to be appended to the project directory, such asC:\project\D:\lib, andpnpm list --longcould not show the package's details #10362.pnpm rootnow prints the configuredmodulesDir. It used to printnode_modulesregardless of the setting. A project's ownmodulesDirfrompackageConfigsis printed too #9113.pnpm auditandpnpm audit signaturesnow fail with an error when the lockfile contains unresolvable dependency references #13638.Output and messages
With the default and append-only reporters, installs with
--loglevel warnor--loglevel errornow print the full output of a failed install script. The output of successful scripts, including the root project's own install hooks, stays hidden. With--loglevel warn, pnpm also prints ignored build script warnings.The lockfile verification error now suggests relaxing the policy that flagged an entry only if a fresh resolution still fails and you trust the affected packages. Errors from checks that no policy controls, such as a missing tarball integrity, no longer suggest relaxing a policy #14411.
pnpm run --recursivenow prints GitLab CI collapsible sections that GitLab recognizes. The section markers used to appear as raw text in the job log.Local tarball dependencies using the file protocol are no longer counted as downloaded in the progress banner #1103.
Platinum Sponsors
Gold Sponsors
v11.28.0: pnpm 11.28Compare Source
pnpm 11.28.0 adds the
forceIgnoresPlatformsetting andpnpm update --peer, and fixes many bugs inpnpm deploy,--filter,nodeLinker: hoisted, and custommodulesDirsetups. This release also carries security fixes for shell completion, bin shims on Nix, lifecycle scripts inside a custommodulesDir, anduserAgentplaceholders inpnpm-workspace.yaml.Minor Changes
forceIgnoresPlatformsetting. When it isfalse,pnpm install --forceskips optional dependencies whoseos,cpuorlibcdo not match the host instead of installing all of them. The default staystrue#6133.Patch Changes
Security
pnpm no longer expands environment variables in a
userAgentset in a project'spnpm-workspace.yaml. AuserAgentwith a placeholder in that file is now ignored. Before this fix, pnpm sent the variable's value to the configured registry #15415.pnpm no longer treats packages inside a custom
modulesDiras workspace projects, including one thatpackageConfigssets for a project. Before, with amodulesDirsuch asvendorand apackagespattern such as**, a repeat install ran the lifecycle scripts of dependencies thatallowBuildshad not approved #15412.On Nix, a dependency's bin named like a system utility such as
sedcan no longer redirect a POSIX bin shim or thepnpm,pn,pnpx, andpnxlaunchers. The shims and launchers now ignorenode_modulesand relativePATHentries while they locate their own files. Installing again replaces the shims already innode_modules#14883.Shell completion now omits candidates containing control or invisible formatting characters, and fish completion omits names containing backslashes. Package and script names can no longer inject extra completion records or terminal escape sequences.
Commands that run pnpm again, such as
pnpm runtime setandpnpm env use, no longer re-run a script that only looks like pnpm. A script namedpnpmorpnthat another package installed was run as though it were pnpm.pnpm store prunenow leaves adlxcache root that is a symlink or Windows junction untouched. Cleanup no longer removes directories through that link.Installing packages
pnpm installnow fails at once when a registry or tarball server presents a TLS certificate that fails verification, such as a self-signed or expired one. The error names the certificate problem. Such requests were retried for more than a minute #9134.pnpm installno longer appears to hang when a git dependency is fetched over SSH and ssh asks for a key passphrase or a host key confirmation. pnpm now runs ssh in batch mode, so the install fails right away with the ssh error, and a key that needs a passphrase has to be loaded into an SSH agent first. An ssh command selected throughGIT_SSH_COMMAND,GIT_SSH, or thecore.sshCommandgit setting is kept as is #2227.pnpm no longer crashes on startup when the temporary directory set by
TMPDIR,TEMP, orTMPdoes not exist #4960.pnpm install --silentno longer fails when the install is delegated to pacquet. pnpm also stops passing-s,--logleveland the other reporting flags to pacquet #11936.Fixed
pnpm installfailing withEEXISTwhen a concurrent install cleared the file or directory that was occupying a symlink path. On Windows, a symlink another process is still holding is no longer moved aside and recreated.pnpm installno longer fails when writing the workspace state file encounters an error. Failures to update the state file now emit a warning instead of aborting the install #14550.Installing or adding dependencies no longer fails when a previously installed local tarball file was deleted from disk #8367.
pnpm installnow reads the same local tarball it installs when a dependency's absolutefile:path contains... Such a path could install a different tarball than the one it read, failing withERR_PNPM_TARBALL_INTEGRITY, or fail to resolve at all.pnpm install --frozen-lockfilenow rejects changed local tarballs, even when the previous archive contents are in the store #1889.pnpm addandpnpm installnow support installing bzip2 compressed tarballs #6761.pnpm installnow fetches committed submodules of git dependencies #1470.Interrupting
pnpm installwith Ctrl+C or SIGTERM no longer leaves a temporary lockfile (.pnpm-lock.yaml.*.tmp) behind in the project #1418.pnpm installandpnpm runnow reinstall a single project that was moved or renamed together with itsnode_modules. Before, they reported "Already up to date" while links such as Windows junctions still pointed at the old location #9512.pnpm installnow relinks a direct dependency whose link innode_modulespoints to a missing target. Before, it reported "Already up to date" and left the broken link #9758.pnpm installandpnpm addno longer skip optional dependencies that the Node.js version resolved for adevEngines.runtimerange supports, when the range usesonFail: download. An explicitly setnodeVersionstill takes priority #14628.pnpm installnow uses the running Node.js whendevEngines.runtimedeclares a range withoutonFail: download. Optional dependencies supported by the active Node.js are no longer skipped #15230.pnpm install --engine-strictnow respectsenginesrelaxed byreadPackagehooks in.pnpmfile.cjs#15482.pnpm installnow applies changes to or removal of a globalreadPackagehook when an existing lockfile is present #15136.The project's
.pnpmfile.mjsor.pnpmfile.cjsnow runs after the pnpmfiles of config dependency plugins #9891.A
readPackagehook that sets a dependency range to a value other than a string, such asundefined, now fails the install with an error that names the dependency, the package, and the pnpmfile. Delete the property to remove a dependency #5517.pnpm install --prodand other installs that skipdevDependenciesno longer run thepnpm:devPreinstallscript #7065. They skippreparelifecycle scripts too, as doespnpm installgiven package arguments.pnpm deploydoes not run thepreparescripts of the deployed project #7282.The root project's
preinstallscript now runs before dependencies are resolved and linked. A guard such asnpx only-allow yarncan stop the install before pnpm populatesnode_modules#3760.pnpm install --prod,pnpm fetch --prodandpnpm deploy --prodno longer install a devDependency that is only there to satisfy an optional peer dependency of a production dependency.pnpm list,pnpm why,pnpm licenses,pnpm sbomandpnpm auditleave it out of--prodresults too. The same applies to--dev. A peer that is not optional is still installed and audited #15344.pnpm prune --prodand production installs now prune excluded development dependencies even when lockfile generation is disabled.pnpm fetchnow also installs the pnpm version thatpnpm-lock.yamlpins, when it differs from the running pnpm. A laterpnpm install --offlinethat switches to the pinned version no longer fails because that version is missing from the store #11808.A dependency that ships a
binding.gypand setsgypfile: falseno longer gets thenode-gyp rebuildinstall script pnpm synthesizes for it. Such a dependency needs noallowBuildsentry and is no longer listed under "Ignored build scripts".pnpm installno longer addsallowBuildsplaceholder entries topnpm-workspace.yamlwhen it runs in CI or without a terminal. Interactive installs still add them #11574.Installing through a pnpr server now installs a project's peer dependencies when
autoInstallPeersis enabled. A project that declared only peer dependencies failed withERR_PNPM_OUTDATED_LOCKFILEor skipped its peers #14833.Resolving and linking dependencies
pnpm now installs a dependency that a package also declares as an optional peer dependency, for example
lightningcssin some vite builds. The dependency was missing fromnode_modules, so the package failed to import it #8912.Removal overrides such as
"parent>peer": "-"now prevent optional peers from being installed from another workspace package #15008.Removing an entry from
overridesnow re-resolves the packages it targeted. A version the override had locked is no longer kept just because the declared range still accepts it #4587.packageExtensionsandoverridesentries with a ranged selector (such as@<Xor@*) no longer match a dependency that has nopackage.json, such as a local directory dependency #15007.Trim leading and trailing whitespace from dependency override selectors in
pnpm.overrides#6356.With
trustPolicy: no-downgrade, pnpm now resolves the newest matching version that is not a trust downgrade. Previously a dependency failed withERR_PNPM_TRUST_DOWNGRADEeven when an older version satisfied its range.pnpm self-updatepicks its target version the same way. A request for an exact version still fails #14176.pnpm installandpnpm updatenow resolve a dependency range to the newest matching version that is not deprecated. A version already recorded in the lockfile is still used #15128.pnpm addandpnpm removeno longer move unrelated transitive dependencies to other versions. Adding a package and then removing it now leavespnpm-lock.yamlunchanged. Before, the dependencies of auto-installed peers andnpm:aliased subdependencies could move to a newer version that was already in the lockfile #11859.pnpm dedupenow moves transitive dependencies to the version acatalog:dependency pins, as it already did for versions written directly inpackage.json.pnpm install --ignore-pnpmfileno longer removespnpmfileChecksumfrom an up-to-datepnpm-lock.yaml.pnpm install --frozen-lockfile --ignore-pnpmfileno longer fails withERR_PNPM_LOCKFILE_CONFIG_MISMATCHwhen the lockfile records apnpmfileChecksum. A command that resolves dependencies with the pnpmfile ignored still writes the lockfile without it #10944.pnpm install --frozen-lockfilenow succeeds when an optional dependency was unresolvable and skipped by the install that wrote the lockfile. Previously, frozen installs failed withERR_PNPM_OUTDATED_LOCKFILE. The notice states that the dependency could not be resolved and names the requested range #3960.pnpm no longer rewrites
packageManagerDependenciesinpnpm-lock.yamlwhen that block pins@pnpm/exebesidepnpm. The rewrite ran on every command, sopnpm listleft a clean working tree dirty, andpnpm versionthen refused to run #14926.pnpm importand fresh resolutions now recordintegrityfor git-hosted tarballs, such ascodeload.github.comURLs, even when the tarball is already in the store #13338.Merging lockfiles now preserves recorded configuration fields such as
overrides,neverBuiltDependencies,patchedDependencies,packageExtensionsChecksum,settings, andcatalogs#8366.A lockfile entry whose resolution is unchanged now keeps its recorded
deprecatedmessage #5772.pnpm no longer writes a package's legacy array-form
engines, such as["node >= 0.8"], to the lockfile. It was recorded as an object keyed by index, such as{'0': node >= 0.8}#4518.With
nodeLinker: hoisted,hoistWorkspacePackagesnow links each workspace project thathoistPatternorpublicHoistPatternselects into the rootnode_modules, unless a hoisted package or a root dependency already uses its name. The project's bins are linked into the rootnode_modules/.bin#7553.Workspace projects that
hoistPatternorpublicHoistPatternselects are now hoisted on every install. A project added to the workspace was not hoisted untilnode_moduleswas deleted and reinstalled. A workspace that installs nothing from a registry hoisted none of its projects at all #3642.With
nodeLinker: hoisted,pnpm installnow removes the commands of the packages it removes fromnode_modules/.bin, such as a nested copy deduped into the rootnode_modules#7568.pnpm installnow links a dependency's bin even when the bin's file does not exist yet, such as a workspace package's bin that a build script creates after install. Previously pnpm printed aFailed to create binwarning. The command then stayed missing untilnode_moduleswas removed #10007, #10216.Executable linking now makes a bin executable for every user. A target with only some execute permission bits set was left non-executable for other users #3699. Bin linking leaves workspace and linked dependency files outside
node_modulesunchanged.Dependencies and executable binaries are now correctly linked and accessible for workspace packages using
publishConfig.directoryandpublishConfig.linkDirectory#8338.Workspaces and filtering
Fixed
pnpm installfor workspace projects reached through a symlink, such as apackagesdirectory that links to a folder outside the workspace. pnpm now installs their dependencies, and the links in theirnode_modulesresolve #1044.A dependency declared with
catalog:now counts as a workspace dependency when its catalog entry points at a workspace project, for exampleworkspace:*#15587. WithlinkWorkspacePackagesenabled, so does annpm:alias of a workspace project, such as"math-alias": "npm:math@^1.0.0".pnpm -r runruns that project first.--filter <pkg>...selects it.A
workspace:dependency now resolves to a workspace project whose version is not valid semver, such as1or1.0.workspace:*,workspace:^, andworkspace:~match it. A range identical to the version also matches it #4567.A
workspace:dependency with an exact version now resolves to a workspace project whose version carries SemVer build metadata. For example,workspace:0.5.6-next.3matches a project at0.5.6-next.3+f60facc#6483. A workspace project with build metadata is no longer skipped when it matches the requested range and has the same version precedence as the registry package #2812.Secondary dependencies now prefer the version resolved by the local project's direct dependencies over versions from sibling workspace projects #7191.
pnpm installnow re-resolves a workspace project's auto-installed peer dependency when another workspace project changes its specifier for that package to one that excludes the locked version but still overlaps the peer range. The peer then resolves to the version a fresh install would pick #11800.pnpm install --frozen-lockfilenow fails withERR_PNPM_OUTDATED_LOCKFILEwhenpnpm-lock.yamllists a workspace project whose directory or manifest file is missing. The install used to report success without installing that project's dependencies #7667.pnpm install --frozen-lockfilenow fails when a workspace package's version no longer satisfies the range that a dependent workspace project declares for it. This includes injected workspace dependencies #7823.pnpm install -rnow installs every workspace project whenrecursiveInstallis set tofalseinpnpm-workspace.yaml#7504.pnpm installwith--filternow installs only the dependencies of the selected projects when usingnodeLinker: hoisted#8882.pnpm installnow updates an injected workspace dependency after that package's own dependencies change, whenshared-workspace-lockfileisfalse#7209.syncInjectedDepsAfterScriptsnow copies files into injected dependencies whennode_modulesis on another filesystem than the package sources. The sync previously failed with a cross-device link error and made the script run exit with an error #14703.Fixed injected workspace dependency synchronization failing with
EPERMon Windows when removing nested directories.Workspace discovery now returns one project per directory when multiple manifest formats are present. It selects
package.json, thenpackage.json5, thenpackage.yaml#3027.Wildcards in negated
packagespatterns ofpnpm-workspace.yamlnow match directories whose names start with a dot. For example,!packages/**now also excludespackages/.dev/toolwhen another pattern includes.devexplicitly.pnpm now warns when a workspace install covers a project that has its own
pnpm-workspace.yaml. The nested file's settings, such aspatchedDependencies, do not apply when the outer workspace installs that project. pnpm reads settings only from thepnpm-workspace.yamlat the workspace root #11724.pnpm now warns when
shared-workspace-lockfileis passed on the command line outside a workspace #1617.The
[<since>]filter selector now compares against the commit where the current branch forked from<since>. Projects changed only by newer commits on<since>are no longer selected. Uncommitted changes are still included. In a shallow clone without that commit, pnpm compares against<since>directly, as before #9907.--filter "[<since>]"now selects workspace packages when dependency versions change in a catalog inpnpm-workspace.yaml#8718. It also selects projects that files were moved out of when git detects the move as a rename #15481.--filternow evaluates selectors in order, so later inclusion filters can re-include packages that an earlier exclusion filter excluded #9354.Directory filters such as
--filter=./packages/*now select projects when the current directory was entered with a lowercase drive letter on Windows, likec:\repo#5500.Custom modulesDir
pnpm run,pnpm exec,pnpm versionhooks, and the lifecycle scripts a project runs during install now find commands in a custommodulesDir, and bin shims resolve dependency paths there #3604. Project.hooksscripts are read from the configured modules directory. Installs resolved by pnpr now preserve configured modules and executable directories.pnpm binnow reports the configured executable directory. In a workspace whose projects keep their own lockfiles, apackageConfigsentry that gives one project its ownmodulesDiris followed too.Tools installed in a custom
modulesDircan load CommonJS plugins installed there, the same way they would fromnode_modules. When executables are symlinks, as withpreferSymlinkedExecutablesor the hoisted linker, this works forpnpm run,pnpm exec,pnpm versionhooks, and the lifecycle scripts a project runs during install. A symlinked tool started directly from a shell does not get it. Paths containing the platform path-list separator do not receive this fallback.extendNodePath: falsedisables this fallback #3604.A repeat
pnpm installin a workspace with a custommodulesDirnow takes the up-to-date fast path. Before, pnpm looked for each workspace project's dependencies innode_modulesand ran a full install every time.Adding, updating, and removing dependencies
pnpm addnow keeps the specifier areadPackagehook provides when the hook rewrites the requested one. When the hook removes the dependency instead, the add skips it and reports why. Previously the add wrote the request either way, and the hook undid it on the next read, sopnpm install --frozen-lockfilefailed #15156.pnpm addnow saves changes topackage.jsonbefore running lifecycle scripts, so a postinstall script failure leaves the added dependency inpackage.json#8627.pnpm addnow saves the requested exact version when adding a dependency, even when the manifest already contains a version range #6040.pnpm addandpnpm installkeep an emptypeerDependencies,dependencies,devDependencies, oroptionalDependenciesfield that was already inpackage.json. pnpm still drops such a field when it removes the last entry itself, aspnpm removedoes #5096.Added a
--peerflag topnpm updateto update ranges inpeerDependencies#8081.pnpm updatenow keeps a version range whose shape has no save prefix, such as<= 3.0.0or>=1.0.0 <2.0.0, when the updated version still satisfies it. Before,<= 3.0.0became^3.0.0#6714.pnpm update <name>now updates a peer dependency that pnpm installed automatically #10486.pnpm update <pkg>now moves a package off a locked version the registry no longer serves, such as an unpublished release. The lockfile check for supply-chain policies such asminimumReleaseAgeused to reject that version before the update could replace it #9953.pnpm update --prodno longer installs devDependencies when run in a project installed with--prod#8038.pnpm update --interactive --workspacenow allows external dependencies to be updated.pnpm outdatedandpnpm updatenow applyminimumReleaseAgeto GitHub Actions.minimumReleaseAgeExcludeentries match action names such asactions/checkout#13923.pnpm removenow runs the project's ownpreuninstall,uninstall, andpostuninstallscripts.preuninstallanduninstallrun before dependencies are unlinked. A failure in either stage aborts the removal.postuninstallruns after unlinking completes. TheignoreScriptssetting and--lockfile-onlyskip all three stages #3276.pnpm remove -rnow fails if any requested dependency is absent from all selected workspace projects. Validation respects--save-prod,--save-dev, and--save-optionaland completes before modifying project manifests #2319.pnpm removenow accepts--trust-lockfileand--no-trust-lockfileto control supply-chain policy checks while removing a package #14406.pnpm unlinknow removes thelink:dependency thatpnpm link <dir>added topackage.json. The linked package is removed fromnode_modulesand the lockfile. Alink:dependency to another directory is kept #4219.minimumReleaseAgeExcludePruneandtrustPolicyExcludePrunenow work in workspaces withshared-workspace-lockfile=false. Once every project has been installed, pnpm drops an entry only if no project lockfile records it. UndecidedallowBuildsentries are pruned the same way #14612.pnpm importnow converts dependencies that use Yarn'spatch:protocol. The dependency keeps the version it patches, and the patch file is added topatchedDependenciesinpnpm-workspace.yaml. If the patch file is missing, pnpm prints a warning and imports the dependency without the patch #10278.pnpm importin a workspace now keeps the versions pinned by the rootyarn.lock,package-lock.json, ornpm-shrinkwrap.jsonwhen another workspace project's range allows a newer version. Before, the root project got the newest version in its range #4385.pnpm patch-commitnow resolves default patch directory locations when passed a package name or package specifier (such aspnpm patch-commit <pkg>orpnpm patch-commit <pkg>@<version>).pnpm patch-commitnow updates the lockfile snapshot and prunes removed dependencies when the patch modifiespackage.json#6866.pnpm patch-commitnow falls back to copying package files when hard linking fails.make-dedicated-lockfileno longer removes fields such asmainandtypesfrom thepublishConfigof the project'spackage.json. It now restorespackage.jsonwhen it cannot move the originalnode_modulesback to its place. The error then names.tmp_node_modules, where the originalnode_moduleswas left. The command refuses to run while that directory exists, so a retry cannot overwrite it.Running scripts and commands
A script that pnpm runs without a terminal now ends when pnpm itself is killed. Killing pnpm's process group, as Playwright's
webServerdoes to stop the command it started, used to leave the script running and holding the caller's output pipes open #15555.pnpm --filter <project> <command>andpnpm -r <command>now run a command installed in the selected projects' dependencies when none of them has a script by that name. This matchespnpm <command>in a single project.pnpm runwith--filteror-rstill reports the missing script #10151.pnpm execandpnpm dlxnow setnpm_execpath,INIT_CWD,npm_node_execpath, andNODEfor child processes #7037. Scripts thatpnpxandpnxrun now get pnpm itself asnpm_execpath. A script that ran$npm_execpath installthere ranpnpm dlx install.pnpm execnow sets thePWDenvironment variable to the directory the command runs in. Shells and tools that readPWDnow report the logical path of a workspace package reached through a symlink #1550.A script that runs
pnpm runno longer adds duplicatenode_modules/.binandnode-gyp-binentries toPATH#5352.Concurrent
pnpm runandpnpm execcommands now serialize their dependency installs #14551.pnpm run --recursiveno longer reports interrupted scripts as lifecycle failures afterCtrl+C.pnpm restartnow runs the "stop" and "start" scripts when the package has no "restart" script. Previously it ran "stop" and then failed with "Missing script: restart" #4750.pnpm install-testnow accepts--no-bailwhen executed directly and in recursive runs #3777.pnpm dlxnow keeps a separate cache entry for each Node.js major version. A package built under one Node.js major version, such as a native addon, is no longer reused under another #8611.A
runtime:version range that contains||or a space, such as adevEngines.runtimeversion of^22.18.0 || ^24.0.0, now installs the requested runtime. pnpm used to install the npm package with the same name, such asnode#14817.When the configured
scriptShelldoes not exist, running a script now fails with an error that names the shell. Previously pnpm printed only an exit code or the package directory #7562.If a script is killed by a signal that pnpm survives, such as SIGPIPE, the error now names the signal:
Command failed with signal SIGPIPE.#9821.Publishing, packing, and deploying
pnpm publishnow resolvesworkspace:dependencies from workspace manifests whennode_modulesis not installed. Previously, publishing withoutnode_modulesfailed withERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL#6567.pnpm publishnow honorspublishConfig["@scope:registry"]for a package in that scope. It takes precedence over the registry set for the same scope in.npmrcand overpublishConfig.registry#12071.pnpm packandpnpm publishnow include bundled dependencies when using the isolated linker. This covers workspace packages and the dependencies of each bundled package. Bundled dependencies are also included whenpublishConfig.directoryselects a build directory #1643.pnpm pack,pnpm deploy, and installs of local directory dependencies now keep symlinks that point to files or directories included in the package.pnpm packleaves out symlinks that point outside the package #8208.pnpm packnow honors thefilesfield ofpackage.yamlandpackage.json5manifests. Git-hosted and injected local dependencies that use these manifests now honor it too #7906. The archive includes exactly onepackage.jsonwhen the project uses an alternative manifest format, even when.npmignoreorfilesexcludes the source file.pnpm packnow preserves file executable permissions in the packed tarball when source files are executable on disk.pnpm publishandpnpm packnow report a missingversionornamefield on a workspace dependency. Previously, pnpm reported that the dependency was not installed #4164.pnpm publishandpnpm packnow report an error when a bin script has a shebang line ending with CRLF #7311.pnpm deploynow copies thepackageManageranddevEngines.packageManagerfields of the workspace rootpackage.jsoninto the deployedpackage.json, unless the deployed project pins a package manager itself #9079.pnpm deploynow puts the virtual store atvirtualStoreDir, resolved against the deploy directory. A shared-lockfile deploy recordsvirtualStoreDirin the deployedpnpm-workspace.yaml. With the global virtual store enabled or an absolutevirtualStoreDir, the deploy still usesnode_modules/.pnpm#8787.pnpm deploynow respects--package-import-methodpassed on the command line and reports the package import method correctly #7593.pnpm deployno longer triggers an install when running scripts in a read-only deployed filesystem #11617.A legacy
pnpm deploywithnode-linker=hoistednow puts the deployed project's direct dependencies at the top of the deployednode_modules#9671.pnpm deploy --legacyno longer rewritesnode_modules/.pnpm-workspace-state-v1.jsonin the source workspace. The nextverifyDepsBeforeRuncheck there reported the workspace as out of date #15352.Manifests and configuration files
Fixed
pnpm versionfailing on projects using apackage.yamlmanifest.Fixed
pnpm initcreating an extrapackage.jsonwhenpackage.yamlis already present.pnpm now preserves CRLF line endings when it modifies project manifests.
pnpm versionnow applies pending bumps to private workspace packages. A private package's changelog is written to its committedCHANGELOG.md, also whenversioning.changelog.storageisregistry#13736, #13519.pnpm change checknow validates the pending change intents in.changeset/. It fails when an intent names a package that is not in the workspace or cannot be released..npmrcfiles now support npm's${VAR?}placeholder. It expands to the value ofVAR, or to an empty string without a warning whenVARis unset #14404.pnpm now expands environment variables in
_auth.authTokenvalues loaded from globalconfig.yamlandpnpm_config__auth#12828.pnpm now warns when an empty environment variable removes an
.npmrcauthentication token. Authentication environment warnings now name the affected key #4806.pnpm now keeps the configured default registry when
_authholds credentials for several registries and some of those registries serve package scopes.Lockfile verification checks a tarball hosted on a scoped registry against that registry's metadata, unless the package's own scope has a registry assigned #15530.
pnpm now treats a missing global
config.yaml,auth.ini, or other optional config file as absent in Node.js-compatible runtimes such as StackBlitz WebContainers. Commands such aspnpm --versionfailed there withENOENT#14030.pnpm loginnow logs back in to an existing user on registries without web login, such as verdaccio. The classic login request sends the username and password as basic auth, asnpm logindoes #12055.pnpm doctornow checks the configured default registry and sends its credentials. It used to always pinghttps://registry.npmjs.org/#15618.Global packages, pnpm versions, and runtimes
Global commands such as
pnpm add --global,pnpm list --global, andpnpm bin --globalnow run with the pnpm you invoked, even in a project that pins another pnpm version. Previously, a pin withonFail: "download"switched them to the pinned pnpm, and a pinned pnpm 10 or older failed because its global bin directory was not inPATH#14531.pnpm add -g,pnpm update -g, andpnpm remove -gnow recover a global package group whose entirenode_modulesdirectory was deleted.pnpm remove -gleaves such a group's command shims in the global bin directory #15093. These commands no longer fail withERR_PNPM_PACKAGE_MANIFEST_IO_ERRORwhen another global package's link into the store dangles, for example after the store was pruned. The pnpm install script failed the same way on such a machine.pnpm update --globalnow skips a global package installed from afile:path that no longer exists, prints a warning, and updates the remaining global packages. Previously the whole update failed withERR_PNPM_LINKED_PKG_DIR_NOT_FOUND#12533.pnpm update -gno longer asks more than once for approval of the same immaturename@versionwhenminimumReleaseAgeStrictis enabled #15091.pnpm self-updaterun in a project that pins pnpm throughpackageManagerordevEngines.packageManagernow also updates the global pnpm, as it does outside a project #14747.pnpm self-updateno longer leaves the previous pnpm in the global packages when it was installed as@pnpm/exe.pnpm ls -gnow lists a single pnpm #14709.pnpm setupno longer deletes aliases and other lines that sit between a# pnpmcomment and the pnpm block in a shell startup file #7067.pnpm env removenow cleans up dangling Node.js executables and symlinks. Surviving global commands remain intact.Node.js runtime resolution now supports Windows ARM64. Node.js 20 and newer resolve native
win-arm64builds, and older versions fall back towin-x64under emulation #7123.Windows and WSL
pn,pnpx,pnx, andpnpmnow run when Git Bash, MSYS2, or Cygwin launches them through a Windows path such asC:\Users\me\node_modules\pnpm\pn. The aliases used to fail to find the pnpm installed beside them, or hand the call to an unrelated one #14884.Fallback
.cmdand.ps1Windows wrappers in@pnpm/exenow propagate the exit status of the invokedpnpmcommand #14826.On Windows, bin shims run from Git Bash, MSYS2, or Cygwin now pass
NODE_PATHto Node.js as Windows paths. A project installed from cmd or PowerShell gave its bins aNODE_PATHunder the Git install directory when they ran from Git Bash. Installing again replaces the shims already innode_modules#3360.pnpm installin WSL now waits out Windows file locks on a Windows drive such as/mnt/c, as it already does on Windows. Before, an antivirus or indexer scan holding a file open could fail the install withEACCES#6155.On Windows, pnpm now retries saving
pnpm-lock.yamlfor up to a minute while another process holds the file open. The save used to fail at once withEPERM,EBUSY, or "Access is denied" #9461.On Windows, pnpm now fails within about a second when it cannot move a
node_modulesdirectory installed by another package manager because a file in it is in use. The error names the directory and suggests stopping the process that uses it. pnpm used to retry for a minute and then print a rawEPERMstack trace #7505.pnpm now escapes trailing dots and spaces in
node_modules/.pnpmdirectory names. Windows strips these characters, so a dependency such as"parent-pkg": "file:../"created a directory that could not be deleted or failed to install #8101.pnpm installnow resolves local tarballs specified with bare UNC paths on Windows #1669.pnpm now recognizes local paths with forward slashes on Windows.
On Windows,
pnpm addandpnpm updatenow write relativefile:andlink:specifiers with forward slashes topackage.jsonand the lockfile. They used to write backslashes, so the same project produced different files on Windows and on other systems #7497, #9687.Fixed scripts failing with errors such as
'an-compile' is not recognizedwhenscriptShellis set tocmd.exeon Windows #7181.Inspecting dependencies
pnpm auditandpnpm audit signaturesnow check only the dependencies of the projects selected by--filter,--filter-prod, or--workspace-root. The filter used to be ignored, so a filtered audit reported the whole workspace #10982.pnpm auditnow lists at least one dependency path from every workspace project that depends on a vulnerable package. Before, a project whose dependency was reached through more than 100 paths filled the path list, and other projects that depend on the same package were left out #12200.pnpm audit --fix=updatenow fixes vulnerabilities in dependencies declared through an npm alias. A specifier such as"foo": "npm:vulnerable-pkg@1.0.0"moves to the patched version and keeps the alias. Versions pinned with a leading=are fixed as well #15155.pnpm audit --fixnow prunes redundant overrides when one vulnerable range is a subset of another for the same package #8577.Running
pnpm listinside a workspace package without--recursiveor a filter now lists only the current package #14494.pnpm licenses listdoes the same. Use--recursiveor--filterto list the licenses of other workspace projects #5689.pnpm list --only-projectsnow prints every project selected with--filteror--recursive, including a project that has no workspace dependencies #9770. It also lists the workspace projects whensharedWorkspaceLockfileisfalse#7151, and a project that setspublishConfig.directory#10635. It no longer reports packages innode_modulesthat are missing from the lockfile #9528.pnpm licenses listfailed or reported nothing in a workspace withsharedWorkspaceLockfile: false. It now reads the lockfile of each selected project #10140.With
nodeLinker: hoisted,pnpm licenses listreported paths undernode_modules/.pnpmthat do not exist. It now reports the directory where the hoisted linker placed each package #8589.pnpm outdatedandpnpm -r outdatednow fail withERR_PNPM_NO_PACKAGE_IN_DEPENDENCIESwhen a requested package selector does not match any dependency in the inspected projects #2319.pnpm -r outdated --jsonnow includes every outdated workspace dependency when multiple projects depend on different versions or dependency types of the same package. Such a package is keyed by its current version and dependency type, for examplevue@2.7.14 (dev)#7693.pnpm sbomfiltered to a single workspace project no longer replaces the project's ownlicenseorbugsfield with the workspace root's value when the project's value is blank. The same applies to anauthor,description,license,repository, orbugsfield set tonull#14882.pnpm store statusno longer reports packages with build or postinstall scripts as modified in the store. When packages were mutated, it now lists only those packages and no longer suggests runningpnpm install --force#919.pnpm peers checkand theERR_PNPM_PEER_DEP_ISSUESerror now group peer dependency issues under the workspace project they were found in #15351.The
pnpm:peer-dependency-issueslog event, which--reporter ndjsonprints, no longer lists peers silenced bypeerDependencyRules.ignoreMissingunderconflictsorintersections#8295.Output and messages
The error for an incompatible pnpm-lock.yaml now reports the lockfileVersion the file was generated with and the lockfileVersion the current pnpm supports. The error also warns that recreating the lockfile with
--forcemay break the application and suggests installing the pnpm version that generated the lockfile #848.When the registry stops sending data for longer than
fetchTimeout, pnpm now reports that the metadata or tarball request timed out. Previously the error did not mention the timeout #3646.Resolution no longer logs an error when a package metadata request fails and resolution succeeds via cached metadata #2522.
The ignored build scripts warning and the update notice are printed as plain lines when output is not a terminal, in CI, or with
--reporter append-only. They were drawn inside a box that broke apart in CI logs #9421.pnpm runwith--loglevelset towarn,error, orsilent(or the sameloglevelsetting) no longer prints the$ <command>line before a script, nor the summary of the install thatverifyDepsBeforeRunruns first. Both are info-level output #8944.pnpm addnow warns when replacing an existing dependency with a specifier pointing to a different source #14869.pnpm updateno longer warns "Skip adding ... to the default catalog" for a dependency that already usescatalog:#13715.pnpm remove --helpno longer shows a[@<version>]suffix in its usage line. The command accepts package names only #7751.Bash completion now completes script names that contain a colon, such as
pnpm run test:utopnpm run test:unit#5482.Platinum Sponsors
Gold Sponsors
v11.27.1: pnpm 11.27.1Compare Source
Patch Changes
pn,pnpx, andpnxnow run the pnpm installed alongside them. They used to look pnpm up onPATH. That failed when the directory holding them was not onPATH, and it silently handed the call to an unrelated pnpm when one came first there #14803.The
@zkochan/cmd-shimpackage is now available as@pnpm/bins.cmd-shim.pnpm cache list-registriesnow prints the registry URL, matchingpnpm cache view. It printedhttps%3A+registry.npmjs.orgbefore and printshttps://registry.npmjs.org/now #15046.pnpm deployno longer installs the dependencies of the workspace root project into the deploy directory #6437.Install warnings no longer carry the text of a package's deprecation notice. The warning names the deprecated package and version, and the
pnpm:deprecationevent no longer carries the notice either.pnpm viewstill shows it on request.A deprecation warning now names the newest version of the package that is not deprecated, and says when reaching it means widening the range you declared:
pnpm works this out from the metadata it already fetched, so it costs no extra request. An install that reuses the lockfile without fetching metadata names no version.
pnpm strips control characters from the package name and version in a deprecation warning, and from the notice
pnpm outdated --longprints.The text sanitizer now also strips the Unicode line and paragraph separators U+2028 and U+2029.
pnpm exec <command>andpnpm <command>run from a subdirectory of a project now find the executables installed in the project'snode_modules/.bin. The command still runs in the subdirectory.PNPM_PACKAGE_NAMEnames the project #5068.pnpm execandpnpm dlxnow wait for the command to finish shutting down afterCtrl+C. A signal sent to pnpm alone now reaches the command, the way it does withpnpm run. pnpm used to exit on the interrupt and terminate the command while it was still shutting down #7374.Warnings about ignored environment variables in project
.npmrccredentials now link to the npmrc documentation pnpm/pnpm#15051.pnpm audit --interactive --fix=updateno longer opens a second prompt for selecting dependencies to update #14927.Fixed
pnpm deduperequiring a second pass after bumping a direct dependency inpackage.jsonpnpm/pnpm#14987.pnpm deploynow writes plain versions for registry dependencies with peer dependencies in the deployedpackage.json. The deployed lockfile retains the resolved peer bindings. npm aliases keep their target package names #14873.pnpm publishnow allows a detached Git HEAD in CI, including checkouts of release tags. The working tree must still be clean. Branch and remote-history checks still apply when HEAD is attached pnpm/pnpm#5894.pnpm dlxandpnxnow prompt to approve dependency build scripts in interactive terminals. Cached packages with pending builds also prompt for approval. Without an interactive terminal, use--allow-buildto allow the required builds. Fixes pnpm/pnpm#14943.pnpm install --forcenow removes obsolete dependency links inside virtual-store packages when their dependencies change. Invalid dependency names are ignored during obsolete-link cleanup #15039.pnpm add -gandpnpm update -gnow ignore incomplete unrelated global package groups when every command from the replaced group is retained. Operations that could remove a global command still require complete ownership information.Resolving a Node.js runtime now fails when unofficial-builds.nodejs.org cannot be reached. pnpm used to ignore that failure and leave the musl builds out of
pnpm-lock.yaml.pnpm updatethen wrote a different lockfile on a machine whose network blocks the mirror pnpm/pnpm#14813.pnpm now deduplicates a package whose child dependency resolved an optional peer in one workspace project but not in another. Two copies of
nextcould appear when only some projects could reachstyled-jsx's optionalbabel-plugin-macrospeer #14800.Fixed shell completion of package scripts for
pnpm runandpnpm run-scriptpnpm/pnpm#15034.Bash completion now preserves literal script names containing glob characters and shell punctuation in pnpm v11 and v12.
pnpm sbomnow publishes a valid URL in the CycloneDXexternalReferences[].urland the SPDXhomepage. An npm shorthand such asvercel/msorgitlab:group/subgroup/projectis expanded to thegit+httpsURL npm derives for it. An scp-style remote such asgit@github.com:vercel/ms.gitis expanded the same way. Any other URL is published in its normalized form, without embedded credentials. A value that names no repository, an email address for example, is left out. pnpm used to publish the raw value, so a shorthand produced a URL that consumers such as Dependency-Track reject pnpm/pnpm#14773.pnpm setupnow describes the displayed configuration changes as "the following configuration changes."pnpm --versionnow reports why the pnpm version a project pins cannot be installed or recorded, then prints the version of the running CLI. It used to fail, which made the command unusable where the filesystem is read-only.pnpm --versionalso honors--store-dirand its--storealias now #14831.pnpm install --forcenow reinstalls dependencies when the manifest and lockfile are unchanged. It previously reported "Already up to date" without reinstalling. Files changed innode_modulesare restored when the store content is intact. Combining--forcewith--frozen-storenow reports a configuration conflict on repeat installs #919.pnpm installnow installs git-hosted dependencies without preparing them when their builds are explicitly denied byallowBuilds. Dependencies that require preparation still need an explicit allow or deny decision pnpm/pnpm#10522.pnpm runtime setandpnpm env usenow use the pnpm version that started the command. They could run a different installed pnpm when the command was started through Corepack or another wrapper.The install summary now names the version each dependency resolved to when
node-linkerishoisted. It also lists what an install restores afternode_modulesis deleted, and both sides of a version change. The summary showed the range recorded inpackage.json, or nothing at all #15161.The
@pnpm/npm-lifecyclepackage is now available as@pnpm/exec.npm-lifecycle.Fixed
minimumReleaseAgemaking pnpm download a package's full metadata again on every install. The cached copy carried a validator the registry could not match, so pnpm could never revalidate it pnpm/pnpm#15103.pnpm now measures a
pnpm.overridesentry written as a bare path, such as./local-dep, from the directory holdingpnpm-workspace.yaml. It used to be measured from each package the override rewrote, so the dependency linked to a directory that does not exist #11131.pnpm now preserves scalar YAML anchors and aliases when editing
pnpm-workspace.yaml. Removing the entry that defines an anchor keeps surviving aliases valid. Entries updated to different values are written separately #8245.pnpm now preserves comments and existing key order when updating
package.yaml. New keys are appended to their mapping pnpm/pnpm#2008.pnpm install --prodno longer downloads the registry packages that only a devDependency reaches #881.pnpm update --globalno longer reinstalls a global package when its dependency graph resolves to what is already installed. It reportsAlready up to datepnpm/pnpm#12002.The
minimumReleaseAgeapproval prompt now counts and displays each package version once pnpm/pnpm#15083.pnpm runno longer sends a script a secondSIGINTwhenCtrl+Cis pressed in a terminal. A script that shuts down on the firstSIGINTand exits at once on a second used to die before its shutdown finished #7374.pnpm now reads a
pnpm-workspace.yamlwhosetaskssection uses a setting only pnpm 12 acts on, such asconcurrencyGroup. A task's unrecognized fields are ignored, unless the field only differs in case fromconcurrencyordependsOn, which pnpm reports as a typo.The warning about unrecognized top-level settings now names
cargo,concurrencyGroups, andpipelinesas pnpm 12 settings.pnpm -r list --jsonnow prints one JSON array. It printed a separate array for each project whensharedWorkspaceLockfilewasfalse, so the output could not be parsed.pnpm -r listnow reads each project's own modules directory when the projects keep their own lockfiles, so--longand--parseablereport the packages that project installed #15011.A signal sent to pnpm while it runs without a terminal, as a container runtime or a service manager does, now reaches the script even when the shell running it stays the script's parent. pnpm then waits for the script to finish shutting down. Such a signal used to end the shell at once or stay with it, and the script was never told to stop #7374.
Fixed
minimumReleaseAgebeing skipped for packages served by a registry that returns the same ETag for abbreviated and full package metadata pnpm/pnpm#14925.pnpm installnow returns "Already up to date" in a workspace wherededupeDirectDepsleft a project without anode_modulesdirectory of its own. Such a project forced a full install on every run.Installs in different projects that share a global virtual store no longer fail on Windows with
Access is deniedwhile repairing the same slot #15114.pnpm sbomnow emits a license value as a CycloneDX expression only when it is a valid SPDX license expression. Anything else is emitted as a CycloneDX license name pnpm/pnpm#14786.A dependency's own bins can no longer take over another package's bin shim. The POSIX shims pnpm generates used to look up their shell helpers on
PATH, where a dependency's bins come first #14837. Reinstalling replaces the shims already in yournode_modules. On Cygwin, MSYS2, and WSL the shims still take their Windows path conversion fromPATH, so a dependency can still redirect them there.POSIX bin shims now convert a Windows-form path such as
C:\node_modules\.bin\tsccorrectly. The shim mangled the backslashes in such a path and could not reach the package it runs. Installing again replaces the shims already innode_modules#14867.pnpm packnow writes tarball entries grouped by file extension and file name, the order npm uses. Packages that ship many same-named files, such as template collections, pack much smaller #14766.A command run in a project that the workspace does not include now acts on that project alone. A project is outside the workspace when it has a manifest of its own and no pattern in the
packagessetting selects it, or when a!pattern excludes it. A directory with no manifest of its own, such as a package's source directory, still belongs to the workspace.pnpm installin an excluded project used to install every project in the workspace #3561.POSIX bin shims now take
cygpathandwslpathfrom the system default path on Cygwin, MSYS2, and WSL2. The shims looked both helpers up onPATH, where a dependency's own bins come first, so a dependency could redirect another package's shim. Installing again replaces the shims already innode_modules#14866.pnpm updateandpnpm audit --fix=updateno longer copy dependencies added bypackageExtensions, areadPackagehook, or an override intopackage.json. Those dependencies keep the specifier the hook or override gives them.pnpm update --latestno longer resolves past that specifier.pnpm audit --fix=updatenow warns when one of them pins a vulnerable version. The warning points atpnpm audit --fix#14928.Platinum Sponsors
Gold Sponsors
v11.27.0: pnpm 11.27Compare Source
Minor Changes
nodeDownloadMirrorscan now be set in the global config file (config.yaml) and through thePNPM_CONFIG_NODE_DOWNLOAD_MIRRORSenvironment variable, so a Node.js download mirror can be configured once for a machine instead of in every workspace #12124, #13611.Added a new setting
trustPolicyExcludePrune(default:false). When enabled,pnpm add,pnpm update, andpnpm removeprune the entries oftrustPolicyExcludeinpnpm-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.Patch Changes
pnpm now reads the
packageManager,devEngines.packageManagerand runtime pins from the workspace root'spackage.jsonwhenlockfileDiris set. A project that moved its lockfile lost the pins it declared there #14633.Fixed
pnpm add -g,pnpm update -g, andpnpm remove -gmutating global bins or install directories after only partially reading an installed package group. If any declared package manifest is missing, malformed, or unreadable, pnpm now fails before activation or removal and leaves the existing global installation intact pnpm/pnpm#13796.fetch-timeoutnow limits how long a request may make no progress. The timer restarts on every chunk that arrives. A large download over a slow connection is no longer aborted while data is still coming in. A connection that stops delivering data still fails afterfetch-timeout#14604.pnpm peers checkno longer reports a peer dependency declared asworkspace:^,workspace:~, or a bareworkspace:as unmet. pnpm reported these as unmet whatever version the linked workspace project supplied #14770.A
readPackagehook that edits its argument in place no longer changes what a later install in the same command resolves. Adeprecatednotice read from the lockfile no longer carries over to another install either #13988.pnpm installnow auto-installs missing transitive peers when workspace projects share a dependency at different depths. This also removes incomplete duplicate peer contexts from the lockfile. Fixes pnpm/pnpm#14840.GitHub Actions updates now stop if an action reference changes while its versions are being resolved. Unrelated workflow edits are preserved.
GitHub Actions homepage links no longer expose server credentials. GitHub server URLs now require HTTPS, with HTTP allowed only for loopback hosts.
pnpm licenses listnow reports the runtime downloaded throughdevEngines.runtimewithonFail: "download". The command previously failed withERR_PNPM_UNSUPPORTED_PACKAGE_TYPE#14172.pnpm no longer creates a project
pnpm-lock.yamlwhendevEngines.packageManager.onFailisdownloadand lockfile writing is turned off withlockfile: falseor--no-lockfile. pnpm still switches to the pinned version #14728.A
registryor@scope:registryset in an.npmrcnow wins over the registry apnpm logincredential stored in the globalconfig.yamlpoints at. Previously, after logging in to one registry, installs in a project whose.npmrcnamed a private registry went to the logged-in registry instead. They now go to the registry the.npmrcnames #14614.A patch that gives a dependency a
preinstall,install, orpostinstallscript, or abinding.gyp, now runs that build. pnpm asks for build approval first, so the package is listed under "Ignored build scripts" until it is allowed to build. pnpm 12 ran nothing, and pnpm 11 ran it without asking #14648.Registries that share a host but differ by URL path — one JFrog Artifactory, Nexus, AWS CodeArtifact or GitLab Packages instance serving several repositories — now get a metadata cache directory each. Previously they shared one, so resolving a package from one of them could answer with another's versions, integrity hashes and tarball URLs and fail with
ERR_PNPM_TARBALL_URL_MISMATCH#13558.The URL scheme is part of the cache directory name too, so an
httpregistry can no longer hand its metadata — which can be rewritten in transit — to a resolution configured forhttpsat the same host.The first install after upgrading refetches registry metadata once. The package store is untouched.
pnpm cache viewnow labels each entry with the full registry URL. It printedregistry.npmjs.orgbefore and printshttps://registry.npmjs.org/now.pnpm cache list-registriesandpnpm cache listprint the new directory names. Scripts that parse either command need updating.Updated the embedded Node.js release keys to the current canonical
nodejs/release-keyslist.pnpm sbomnow omits package author fields when the manifest author name is empty or contains only whitespace pnpm/pnpm#14685. In a filtered or split workspace run, only a project with noauthorfield inherits the workspace root's author.pnpm sbom --sbom-format spdxnow writescreationInfo.createdwith whole seconds, such as2026-09-08T10:38:21Z. The timestamp carried fractional seconds, which strict SPDX consumers rejected #14684.Windows filesystem operations now retry permission errors for up to one second. Permanent permission errors previously delayed failure by a minute. Sharing and lock violations retain their one-minute retry budget pnpm/pnpm#14682.
pnpm now writes
node_modules/.package-map.jsononly whennodeExperimentalPackageMapis enabled. Nothing reads the file without that setting. An install that stops writing the map removes the one a previous install left.pnpm now unpacks a downloaded runtime archive into a randomly named directory inside the store. It previously used a predictable path, where another user of a shared store could plant a symlink and redirect the write outside the store (GHSA-vwc7-r8mq-g2x9).
Platinum Sponsors
Gold Sponsors
v11.26.0: pnpm 11.26Compare Source
Minor Changes
Catalogs can now resolve workspace dependencies through the
workspace:protocol.pnpm removeandpnpm updatenow accept--trust-lockfile,--no-trust-lockfile,--trust-policy,--trust-policy-exclude, and--trust-policy-ignore-after.pnpm removechecks the whole lockfile against the active policies unless--trust-lockfileis set.Added
pnpm change checkfor CI validation of package versions against theversioning.epicsbands andversioning.fixedgroups inpnpm-workspace.yaml.Patch Changes
Fetch and tarball errors and retry logs now hide URL credentials, query strings, and fragments that could expose secrets.
Fixed a race during config dependency updates that could redirect a lockfile write through a symlink #14322.
pnpm add --allow-build=!<pkg>now correctly denies builds, including in global installs.pnpm approve-builds <pkg>andpnpm approve-builds !<pkg>now save decisions even when the package is not awaiting approval, with a warning #14067.Fixed
pnpm audit --fixfailing without a value or when followed by another flag.pnpm audit --fix=overridenow respectssaveExactandsavePrefixwhen writing overrides #13261, #11523.pnpm auditnow excludes ignored advisories from vulnerability totals and severity counts, and reports them separately #14535.pnpm deployno longer requiresinjectWorkspacePackages. If a workspace dependency's peer has multiple possible versions, deployment reportsERR_PNPM_DEPLOY_AMBIGUOUS_PEERwith the conflicting versions. Pin the peer withoverridesto deploy without injection #9386.Fixed concurrent installs sharing a store occasionally failing with an
ENOENTerror while importing a package file #14353.Fixed installation failures when a linked local dependency provides a peer dependency also provided by an ancestor, including with
pnpm deploy --legacy.pnpm install --node-linker=hoistedno longer downloads skipped optional dependencies whennode_modulesalready exists #14139.Fixed
pnpm installrejecting a symlinked lockfile when config dependencies are unchanged. Updates to config dependencies also preserve lockfiles with a byte order mark. Writes through symlinked lockfiles remain blocked #14372.pnpm installnow relinks workspace packages whenpublishConfig.linkDirectorychanges. Frozen installs require the lockfile to be regenerated #14488.Auto-installed optional peers now satisfy their declared range even when the workspace root uses a version outside that range #13867.
Fixed global virtual store paths for dependency cycles to consistently account for the runtime engine when dependencies have allowed builds #14341.
Standalone installations now preserve the bundled
node-gypfiles needed to build native dependencies.Downloaded runtimes are now available to dependency lifecycle scripts during installation.
Node.js downloads from
nodeDownloadMirrorsnow use URL-scoped npm credentials, including bearer tokens, basic auth, andtokenHelper#14334.Fixed
globalDirandglobalBinDirhandling in global configuration and environment variables, including~/expansion. This fixespnpm add -gfailing afterpnpm config set -g global-bin-dir#14336.The JavaScript pnpm can again switch to the project's pinned pnpm version on hosts without a matching native binary. If the requested version requires an unavailable native binary, the error now identifies the unsupported host #13622.
Global
pnpm configcommands now skip project package manager version switching, allowing authentication to be configured before downloading the pinned version #14463.pnpm self-update,pnpm with, and automatic version switching no longer wait through registry retries when a configured registry has no signatures andregistry.npmjs.orgis unavailable #14483.Fixed argument forwarding on Windows with
shellEmulatorenabled. Trailing backslashes, line breaks, and literal shell expressions are preserved #14548.Relative
scriptShellpaths now resolve from the workspace root. Bare command names such asbashstill usePATH#14422.pnpm importnow preserves the project-local lockfile whenlockfileDirpoints elsewhere and restores the destination lockfile on failure. Branch lockfile imports leave the shared lockfile unchanged #14563.catalogModeand--save-catalogno longer move local paths, tarballs, orworkspace:<path>specifiers into catalogs #14437.--side-effects-cache,--no-side-effects-cache, andPNPM_CONFIG_SIDE_EFFECTS_CACHEnow toggle only the local cache, preserving any remote cache configured insideEffectsCache.pnpm unpublishnow handles registry two-factor authentication challenges through web authentication or a one-time password prompt #14464.pnpm outdatedandpnpm updatenow follow GitHub Actions references using self-repository syntax, such asuses: $/.github/actions/setup.pnpm removenow accepts--unsafe-perm.Platinum Sponsors
Gold Sponsors
v11.25.0: pnpm 11.25Compare Source
Minor Changes
Added an opt-in proof of concept that lets installs reuse a dependency's build output across machines, by publishing and restoring signed, organization-scoped artifacts through pnpr instead of running the lifecycle scripts locally.
Configure it with the new
remoteSideEffectsCachesetting. A workspace names the eligibleorganizationandpackages; everything describing the act of signing —publish,keyId,builderId,trustedKeys,privateKeyand the provenance fields — is refused inpnpm-workspace.yamland read from the global config file or the environment instead.Added macOS and Windows x64 and arm64 support to remote shared build artifacts pnpm/pnpm#13771.
Added the
audit.ignorePrunesetting. When set totrue,pnpm audit --fixremoves ignored GHSA entries that no longer appear in the audit report.Generalized the experimental shared-artifact protocol so candidates and signed payloads identify a discriminated subject. Dependency side effects use package and source-integrity subjects, while workspace tasks use project and task subjects.
This changes shared-artifact request bodies and signed payloads. A pnpr server and its clients have to be on matching versions.
pnpm initnow pins the latest pnpm version, instead of the version of pnpm that ran the command. A project scaffolded by an outdated pnpm therefore no longer inherits that staleness through its owndevEngines.packageManager/packageManagerpin #7490.The version is read from the
latesttag on the package-manager registries. When that lookup cannot answer — no network, an unreachable or slow registry,offline, or alatestthat theminimumReleaseAge/trustPolicysettings reject —pnpm initpins the running version as before, and never fails or hangs on the lookup. Alatestthat is older than the running pnpm is never pinned either.A
scopeset in a project'spnpm-workspace.yamlis now ignored, with a warning naming where to set it instead.pnpm loginrecords the scope as a@scope:registryroute in the machine-globalauth.ini, which outranks~/.npmrcin every project — so a repository-committed file could redirect a scope such as@acmefor all of a user's other projects after one routine login. Use--scope, thePNPM_CONFIG_SCOPEenvironment variable, or the global config file instead #13557.Verified remote build artifacts are persisted in the shared store with their signed origin metadata. Later installs reverify the artifact against current trust, policy, platform, and source before reuse, while invalid remote variants are quarantined per channel (pnpm/pnpm#13771).
Persist completed recursive tasks so
--resume-fromskips exactly the work that passed during a matching interrupted or failedpnpm -r run/pnpm -r execinvocation. When no compatible state exists, pnpm retains its graph-based resume behavior.Allowed
pnpm update --patchesto refresh registry revisions through a configured pnpr server while retaining locked package versions.Added explicit registry revision selection with
<version>+rNandpnpm update --patchesfor refreshing revision artifacts without changing package versions. Registry-backed lockfile policy checks recognize historical revisions, and pnpr now preserves safe revision histories from upstream registries.Workspace install, rebuild, pack, publish, stage, and lifecycle work now starts as soon as its dependencies finish instead of waiting for an unrelated topological group.
pnpm stage approvenow approves several staged packages at once. Run it without a stage id to pick from the staged versions interactively, or pass a list of stage ids. The whole batch is approved with a single one-time password, and pnpm asks for a new one only once the registry stops accepting it. Inside a workspace, the selected packages are approved in dependency order, and a package whose workspace dependency could not be approved is skipped instead of being published against a dependency that never reached the registry.Added per-task concurrency limits to workspace task orchestration. Set
tasks.<name>.concurrencyinpnpm-workspace.yamlto limit how many instances of that task may run across workspace projects at once:Added support for registry replacement tarballs using standard integrity values, explicit revision fields, registry routing from the
registriessetting, non-redirecting integrity-addressed URLs, canonical safe-integer revision numbers, and pnpr proxying for immutable upstream revision artifacts.sideEffectsCachenow declares the whole of how a package's build output is reused — whether one is restored, whether one is saved, and the remote tier that shares it between machines:sideEffectsCache: true,sideEffectsCacheReadonly,remoteSideEffectsCache, and itsorganizationfield all keep working. Where a field is set under both spellings the one above wins; where it is set under only one, it is kept.Two behaviors change, both bringing this CLI in line with what the Rust one already did:
sideEffectsCacheReadonly: truenow blocks writing to the cache, and setting it alongsidesideEffectsCache: falsegives a read-only view rather than switching the cache off entirely. A cache can also be declared write-only now, to populate one the run does not read.Workspace task orchestration (pnpm/rfcs#23).
pnpm -r runandpnpm -r execnow schedule per task instead of in topological chunks: a task starts as soon as the tasks it depends on have finished, so a project no longer waits for unrelated projects that happen to share its chunk.A new
taskssection inpnpm-workspace.yamldeclares what a task depends on, using the^convention:^namemeans the named task in each of the project's workspace dependencies; a barenamemeans the task in the same project; an entry with nodependsOndeclares an empty dependency list. A task with no entry behaves asdependsOn: ['^<its own name>'], which is exactly what the previous chunked ordering implied — an unconfigured workspace gets the scheduler improvement and nothing else changes meaning. A project without the script is reported skipped and passes its edges through to its own dependencies, so a scriptless package does not sever a chain.Also part of this change:
ERR_PNPM_TASK_CYCLE) instead of silently running in an arbitrary order. SettingignoreWorkspaceCycles: truedowngrades the error to a warning: the cycle's tasks run in an arbitrary order relative to each other.--resume-fromnow skips exactly the transitive dependencies of the anchor package; work unrelated to the anchor still runs.--no-bail, tasks whose dependencies failed are reported as skipped, not failed, and do not add to the exit code.--bail(the default), the first failure still ends the run at once and nothing new is dispatched — including scripts already queued behind the concurrency limit.pnpm -r run --dry-run <script>prints the task graph that would execute without running anything (including skipping theverifyDepsBeforeRuncheck);--jsonemits the tasks and their resolved dependency edges.--workspace-concurrency=1, or the graph forces the scripts to run one after another).Patch Changes
An
_authentry in the global config file no longer decides which registry packages come from when something else says. Aregistryorregistriesdeclared inpnpm-workspace.yamlor the global config now wins over the route inferred from a stored credential, which still applies where nothing else declares one. Thepnpm_config__authenvironment variable is unchanged: it stays the way to point a CI runner at a mandated proxy, and still overrides what a repository declares.Prevent installs through a symlinked
node_modulesdirectory from rewriting the target checkout pnpm/pnpm#14286.Treat empty scripts selected by a regular expression as missing before running dependent tasks.
The options type of the
fetchcommand now declaresallowBuilds, a setting its handler already forwarded to the installer. Type-level only — whatpnpm fetchdoes is unchanged.Filter hidden scripts matched by a regular expression during recursive runs when a visible script also matches.
Fixed automatically switched pnpm versions forcing all descendant pnpm processes to use the same version pnpm/pnpm#14309.
Fixed
ERR_PNPM_UNUSED_PATCHvalidation during incremental installs pnpm/pnpm#13692.Fixed
pnpm deploy --prodfailing when an excluded dev dependency was also declared as an optional peer dependency pnpm/pnpm#14302.pnpm update -gno longer downgrades a global package.--latestresolves thelatestdist-tag, which can point at an older release than the one installed — afterpnpm add -g <pkg>@next, for instance #14270.pnpm update -galso no longer changes the pnpm version. pnpm's own global install belongs topnpm self-update#14270.Copying a built package to its other hoisted locations no longer replaces the destination directory. With
nodeLinker: hoisted, that replacement deleted the dependencies nested inside the destination'snode_modules, and made concurrent copies of the same build chunk fail withERR_PNPM_ENOENT: no such file or directory, rename '.../node_modules/_tmp_...'#12880.pnpm updateno longer replaces the specifier a project declares for a dependency that is also listed inoverrides. Acatalog:reference stays acatalog:reference, and a declared range stays as written, instead of being rewritten to the version the override resolved to #12115.pnpm updateno longer moves the range a project declares for a dependency thatoverridesalso lists, even when the override repeats that range verbatim. Previously the updatedpackage.jsondisagreed with the lockfile, so the nextpnpm install --frozen-lockfilefailed with a specifier mismatch #14224.Make
pnpm add --lockfile-onlyskip dependency linking pnpm/pnpm#14286.--productionis accepted again as an alias of--prodoninstall,fetch,prune,update,list,why, andsbom, and the install thatverifyDepsBeforeRunreproduces is now spelled with--prod.pnpm runno longer aborts with "unexpected argument '--production' found" after a production-only install #14147.The progress output no longer overwrites the lines above it once it grows taller than the terminal window #14270.
Restoring a dependency's build from the remote side-effects cache no longer downloads files the store already holds.
Forward
patchedDependencieshashes andpackageExtensionsto pnpr so server-side resolution preserves patches and package extensions in the lockfile and installed packages.Published the workspace task graph and scheduler as
@pnpm/workspace.task-schedulerso other workspace commands can use the same dependency-aware scheduling as recursive run and exec.The environment variables for the remote side-effects cache are named for the setting they configure:
PNPM_SIDE_EFFECTS_CACHE_REMOTE_KEY_ID,..._BUILDER_ID,..._IMAGE_DIGEST,..._ARCHITECTURE_BASELINE,..._PRIVATE_KEY,..._BUILD_ENV,..._TRUSTED_KEYSand..._PUBLISH. ThePNPM_REMOTE_SIDE_EFFECTS_CACHE_*names keep working, and the new one wins when both are set.A
devEngines.packageManagerrange pin on pnpm is now recorded inpnpm-lock.yaml'spackageManagerDependencieswhen the running pnpm already satisfies it, using the running version and keeping the range as the recorded specifier. Previously only an exact pin — or a range resolved on the way through a version switch — reached the lockfile, so a range pin written by hand (or by any tool other thanpnpm add/pnpm self-update) left the project without the shared resolution the pin exists to provide.Fixed recursive
runcleanup on Windows when a lifecycle script fails while another script's process tree is still running.The update notification now suggests
pnpm self-updatewhenPNPM_HOMEmanages the pnpm in use, and the standalone install script otherwise — under Corepack, or when another package manager installed pnpm.pnpm self-updateunder Corepack names the standalone install script too.Enforce
allowBuildswhen a prepared git dependency is reused from the shared store, and use the lockfile's canonical git resolution ID in approval suggestions.Topologically sorting workspace projects now runs in linear time, fixing installs and lockfile updates that stalled for seconds on workspaces with thousands of projects forming deep dependency chains #14149, #14151.
Platinum Sponsors
Gold Sponsors
v11.24.0: pnpm 11.24Compare Source
Minor Changes
Patch Changes
Fixed pnpm v11 incorrectly reporting
confirmModulesPurgeas unrecognized when set inpnpm-workspace.yaml. The Rust CLI now identifies the unsupported option as a pnpm v11 setting instead of suggesting an unrelated setting.pnpm install --frozen-lockfileno longer fails withERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILEwhen the pinned pnpm version recorded inpnpm-lock.yamlhas to be re-resolved before it can be installed. It runs the pnpm version the lockfile pins and leaves the lockfile unchanged #14124.Under
nodeLinker: hoisted, peer-resolution variants of an injected directory dependency (afile:snapshot) are materialized as separate copies again instead of collapsing onto the first-seen variant. Each copy keeps its own peer-resolved dependency set, so a project pinning one peer version no longer resolves another project's variant — Bit root components with conflicting peers across injected copies rely on this.Fixed
pnpm install --merge-git-branch-lockfiles --frozen-lockfilefailing withERR_PNPM_OUTDATED_LOCKFILEwhen a branch lockfile predates the removal of a dependency, or its move to another dependency group #13966. A dependency that no project declares anymore is no longer reinstated by the merge, and the packages it was the only path to are dropped with it.Batch workspace publishing accepts a shared scope-specific credential, rejects mismatched credentials for a registry before publishing, and runs the
publishandpostpublishscripts after each completed registry group pnpm/pnpm#14101.The Rust CLI now honors five settings it recognized but ignored:
updateNotifier,legacyDirFiltering,initAuthorName/initAuthorEmail/initAuthorUrl,initLicense, andinitVersion.pnpm installandpnpm addcheck once a day for a newer pnpm and print how to get it (turn it off withupdateNotifier: false); a{<dir>}filter selector can go back to matching the subtree below the directory withlegacyDirFiltering: true; andpnpm initwrites the configured author, license, and version into thepackage.jsonit scaffolds.PNPM_CONFIG_INIT_VERSIONis now read as well.maxsockets, npm's spelling ofmaxSockets, is no longer ignored: both spellings are read frompnpm-workspace.yaml, the global config file, the environment, and the command line, in that increasing order of precedence — a value passed on the command line now wins even when the two sides spelled the setting differently.A
lastUpdateChecktimestamp dated in the future — after a clock change, a restored snapshot, or a hand-edited state file — no longer silences the update check until that time comes around.legacyDirFilteringno longer reaches the workspace-root selectors pnpm generates for itself: the!{<workspace-root>}exclusion a recursiverun/exec/add/testappends, and the{<workspace-root>}inclusion--workspace-rootappends. Read as subtree matches they named every project below the root, so a recursive command under the setting selected nothing at all, and--workspace-rootpulled in every project below the root instead of the root alone #14101.pnpm install --frozen-lockfileno longer fails whenpnpm-lock.yamlrecords the pinned pnpm version alongside an engine package the running pnpm does not install it from. An entry pinning another version is still refused, and a plain install rewrites the block #14124.v11.23.0: pnpm 11.23Compare Source
Minor Changes
pnpm config getandpnpm config listnow show the settings pnpm acts on under their documented names:registriesshows the registries pnpm resolves from, merged across every source (.npmrc,pnpm-workspace.yaml, the global config, CLI flags), in the shape the setting is written in: keyed by registry URL, with the default registry declared as the bare@scope. Built-in routes are included — the@jsrscope and thenpmjsandghprefixes — unless pointed elsewhere. Previouslypnpm config get registriesprintedundefined.updateandauditshow the effective sections, whichever spelling set them. The deprecated internal spellings (updateConfig,auditConfig,auditLevel) are no longer listed.catalogsshows the complete resolved catalog set — the singularcatalogblock is itsdefaultentry — whichever spelling declared it.registryand@scope:registryentries show the merged routes rather than raw.npmrcvalues, so they always agree with theregistriesview.Settings that no supported pnpm version recognizes get their own warning. A key in the global config file that this version of pnpm does not read is no longer reported with advice to move it to a project-level
pnpm-workspace.yaml(where it would be ignored too); the warning now says the setting is not recognized by this version of pnpm, names the pnpm version that does read it when there is one (for example,globalShimsis a pnpm v12 setting), and suggests the closest real setting name when the key looks like a typo. Unrecognized and non-camelCase keys in a project'spnpm-workspace.yaml, previously ignored silently, are now reported the same way.pnpm config get <key>andpnpm get <key>no longer print config-load warnings, so a script capturing the value gets the value alone.The
importPackagepnpmfile hook is deprecated. pnpm now prints a warning when a pnpmfile defines it, and the hook will be removed in the next major version. It also opts the installation out of the parallel package importer, making installation slower. If you rely on this hook, comment on #14101.node_modules/.modules.yamlno longer records the registries an install resolved from, and the recorded copy is dropped from the file on the first install that rewrites it.It dated from the lockfile format that spelled a dependency's path relative to its registry, where reading an installed tree meant knowing the registries it was installed with. Dependency paths have not carried a registry for several major versions, and the recorded copy outlived its use:
pnpm list,pnpm why, and single-project installs preferred it over the project's own configuration, so a project whose registry had changed since its last install was still read through the old one.They now use the configured registries, like every other command already did.
When
enableGlobalVirtualStoreis on, every process pnpm spawns for the project (pnpm run,pnpm exec, lifecycle scripts) now receives aNODE_PATHpointing at the project's hoistednode_modules, plus aNODE_OPTIONS--importflag that registers a resolve hook restoringNODE_PATHlookups for ESM imports. Dependencies that import undeclared ("phantom") packages keep resolving under the global virtual store — for both CommonJS and ESM — without installing the@pnpm/plugin-esm-node-pathconfig dependency pnpm/pnpm#9618. Tools run bypnpm dlxresolve such dependencies too: the JS CLI passes them the same environment, while the Rust CLI's dlx cache is self-contained, so its layout already exposes them.A registry can now declare that its abbreviated metadata carries the
timefield, soresolutionMode: time-basedreads the full metadata document only from the registries that need it:registry.npmjs.orgomitstimefrom abbreviated metadata, so a time-based resolution has to fall back to the much larger full document. That fallback used to be all-or-nothing:registrySupportsTimeFieldanswered for every registry at once, so a project resolving from both the public registry and a Verdaccio instance either paid for full metadata everywhere or claimed atimefield npmjs does not serve. The answer is now per registry, andregistrySupportsTimeFieldremains the answer for every registry that does not declare one.The declaration is also sent to a pnpr server, which applies it to the resolution it runs on the client's behalf.
A pnpr resolve request now carries the client's registries the way the
registriessetting declares them — keyed by URL, with the scopes routed to each, the bare-specifier prefix each answers to, and each one'sserverType— in place of the prefix map it used to send.The server routes them through the same inversion the config reader runs, so a pnpr-served install resolves a scoped dependency from the registry that scope is routed to, which it previously could not: only the default registry and the prefix-addressed ones reached the server. A declared
serverTypereaches it too, so the tarball URLs pnpr omits from the lockfile match the ones the client reconstructs.Built-in scope routes the project has not pointed elsewhere are not declared, so a pnpr server's allowlist is not asked about
npm.jsr.ioon requests that resolve no JSR package.A registry a request only declares is no longer refused up front for being off the server's allowlist — a client describes its whole configuration, including scopes a given resolve never reaches, so a stray
@scope:registryin a developer's~/.npmrcno longer fails every install against a pnpr server that does not serve it. The boundary moves to the fetch itself: an origin the resolve does reach is refused before the request leaves the server, with the same message.This changes the resolve and verify-lockfile request bodies. A pnpr server and its clients have to be on matching versions; the protocol is still experimental and unversioned.
The
registriessetting now declares a registry once, keyed by its URL, with everything about that registry in the entry: how it lays out tarball URLs, the scopes routed to it, and the bare-specifier prefix it answers to.serverTypetells pnpm how the registry lays out its tarball URLs, which decides whether a URL can be omitted frompnpm-lock.yaml:npm— the registry behaves likeregistry.npmjs.org, which also serves a scoped package from its percent-encoded path. Declare this for a faithful mirror or caching proxy of the public registry so its tarball URLs can be omitted too.artifactory— JFrog Artifactory repeats the scope in a scoped package's tarball filename (@acme/widget/-/@acme/widget-1.0.0.tgz) where the npm registry strips it (@acme/widget/-/widget-1.0.0.tgz). Declaring it lets pnpm rebuild that URL, so it is omitted frompnpm-lock.yamlinstead of being written out for every scoped package pnpm/get-npm-tarball-url#16.scopeslists the@-prefixed scopes that resolve from this registry. A bare'@'is the scope-less default registry, the one theregistrysetting names.prefixis the alias a dependency addresses this registry by, as in"foo": "work:^1.0.0".The layout is never inferred from the registry URL, so nothing changes unless you declare it;
registry.npmjs.orgcontinues to behave asnpmwithout being declared. Because the lockfile depends onserverType, it is read frompnpm-workspace.yamlonly — aserverTypein the globalconfig.yamlis ignored, so one developer's machine cannot shape a lockfile their collaborators read back with a different layout. Credentials are rejected in this setting, in a key as well as in a field, and still belong in.npmrc. An entry that routes nothing to itself and matches no configured registry is reported as a warning rather than silently ignored.Migrating
The older
registriesshape, a map of<scope>: <url>strings, still works and needs no change:namedRegistriesis deprecated in favor of theprefixfield, and is still read for prefixesregistriesdoes not declare.toLockfileResolutionandisCanonicalRegistryTarballUrlnow take their registry and layout as an options object rather than positional arguments, so@pnpm/lockfile.utilsand@pnpm/resolving.tarball-urlget a major bump.An install that had to re-hash store files to verify them now reports it. If that cost more than a second, it says how long —
The integrity of N files was checked in 2.5s.— and if it was quick but covered more than a thousand files, it names the cause instead: their timestamps changed since the store recorded them, which a backup tool, an antivirus scan or a copied store can do.Added
virtualStoreType, which names where the virtual store lives — one store per machine, or one per project:It is the canonical spelling of
enableGlobalVirtualStore, which keeps working. When a project sets both,virtualStoreTypewins. It can also be set throughPNPM_CONFIG_VIRTUAL_STORE_TYPEand read back withpnpm config get virtualStoreType. The default is unchanged —project, so the shared store stays opt-in.The setting is independent of
nodeLinker.isolatedandpnpboth work with either store type, andhoistedwrites no virtual store at all, so it is unaffected.Patch Changes
pnpm add --allow-buildnow adds to theallowBuildsentries already inpnpm-workspace.yamlinstead of replacing them #13872.Kept pending build approvals available after removing an unrelated dependency.
pnpm approve-buildsnow removesonlyBuiltDependencies,onlyBuiltDependenciesFile,neverBuiltDependencies, andignoredBuiltDependenciesfrompnpm-workspace.yamlwhen it writesallowBuilds. Those settings were replaced byallowBuildsin pnpm 11 and silently ignored since, so a workspace migrated from pnpm 10 kept them around looking active.pnpm auditno longer reports a patched version that was never published or is deprecated. The inferred patched range (e.g.>=4.17.24from<=4.17.23) is now checked against the registry packument, and the report is corrected to the lowest non-deprecated published version that satisfies it (e.g.>=4.18.1when4.17.24does not exist and4.18.0is deprecated). When no published version satisfies the range, the report showsPatched versions: None. This also preventspnpm audit --fixfrom adding overrides orminimumReleaseAgeExcludeentries for patches that do not exist #13824.pnpm audit --fixandpnpm audit --fix updateno longer add aminimumReleaseAgeExcludeentry when the registry packument shows that the minimum patched version was never published. Previously such entries were written for versions that do not exist, which would have let a later publish of that version bypass theminimumReleaseAgegate #11563.The
--jsonoutput ofpnpm auditnow returnspatched_versions: nullfor advisories whose inferred patch is not available (never published, skipped, yanked, or deprecated), making it easier for tooling to distinguish "no fix available" from "fix available at version X".Fixed
pnpm patch-commitin project and edit paths containing non-ASCII characters.The package and bump pickers of
pnpm changenow size their page from the terminal height instead of always showing 7 rows. They fall back to 7 rows when the terminal height is unknownpnpm/pnpm#13815.Canceling a
pnpm changeprompt with Ctrl-c no longer prints a stack trace. It reportsChange canceledand exits with a success status, like the other interactive commands #13814.Re-fetch full registry metadata when
minimumReleaseAgeis enabled and an abbreviated packument'stimemap omits timestamps for some versions. This prevents mature versions from being filtered out and resolution from falling back to the lowest matching version pnpm/pnpm#13741.A config dependency carrying an inline integrity (the
<version>+<integrity>form, or the object form without atarball) now takes its tarball URL from the registry's packument instead of deriving it from the registry URL, so migrating one costs an extra metadata request. On a registry that serves tarballs from a path pnpm cannot derive, GitLab's group endpoint for one, installing such a config dependency failed with a 404 while the same package installed fine as a regular dependency #13765.Fixed
PNPM_CONFIG_NODE_VERSIONbeing ignored when setting the Node.js version used for compatibility checks.A custom fetcher can no longer replace the archive integrity that
pnpm-lock.yamlpins: the locked value is restored after acanFetchorfetchhook rewrites the resolution, and delegating a locked archive to a directory or git source now fails instead of installing unverified content.The Rust CLI now also loads the pnpmfiles named by the
pnpmfilesetting (a single path or an ordered list), and hands custom fetchers nativelocalTarballandremoteTarballcallbacks — including on a fresh install that has to compute a missing tarball integrity, which is then reused by later offline installs. File maps a fetcher returns are accepted only when they match what those native callbacks extracted.Fixed an issue where running
pnpm dedupe --checkin projects withnodeLinker: hoistedwould cause dependencies to be moved out ofnode_modulesintonode_modules/.ignored.pnpm deploy --prodandpnpm deploy --no-optionalno longer list the excluded dependency groups in the deployedpackage.jsonandpnpm-lock.yaml. The deployed lockfile referenced packages that the deploy left out of its graph, so installing in the deploy directory afterwards created dangling symlinks #13623.Don't treat files like
license16.jsonas a package license when deciding if the workspace LICENSE file should be included in the packed package.pnpm exec --recursive --no-reporter-hide-prefixno longer prints a blank prefixed line after each chunk of a command's output, and no longer splits a line in two when it straddles a chunk boundary.Fixed
404errors when installing from a registry that serves scoped packages only from a percent-encoded path, such as GitHub Enterprise Server. Outsideregistry.npmjs.org, a tarball URL that encodes the scope separator as%2for%2Fis no longer mistaken for one that pnpm can rebuild from the package name, version, and registry, so it is kept inpnpm-lock.yamland requested verbatim on the next install #13534.Fixed
trustPolicyExcludeandminimumReleaseAgeExcludebeing ignored when set to a single string instead of a list. The value was read one character at a time, so the exclusion never matched the package it named — and a*anywhere in it matched every package, silently switching the policy off.pnpm initnow pins the exact pnpm version instead of a^range, and records it in thepackageManagerfield alongsidedevEngines.packageManager. Corepack reads onlypackageManagerand accepts nothing but an exact version, so it rejected the generatedpackage.jsonwith "expected a semver version" pnpm/pnpm#13969. A package created inside an existing workspace is still left unpinned — it follows the pin at the workspace root — and--no-init-package-managerstill scaffolds a manifest without any pin. In pnpm 12,pnpm initalso honorsinitTypeand its--init-typeflag, so the manifest it writes is the same one pnpm 11 writes.Fixed an issue where package overrides were written into the metadata cache, causing removed overrides to keep applying on subsequent installs pnpm/pnpm#13918.
On Windows, upgrading pnpm no longer leaves a stale
pnpm.ps1behind. PowerShell resolvespnpm.ps1ahead ofpnpm.cmd, so a shim written by an older installation kept running the previous version. Linking the pnpm CLI's bins now deletes it #13919.Fixed an inconsistency where
minimumReleaseAgeExclude(andtrustPolicyExclude) wildcard/bare-name rules behaved differently in the evaluator and normalizer. A bare rule now consistently evaluates as matching every version, preventing unexpected behavior and silent widening of version policy exemptions when pnpm rewrites the workspace manifest pnpm/pnpm#13725.A frozen install no longer rewrites the
packageManagerDependenciesblock ofpnpm-lock.yaml. When the pnpm version pinned bydevEngines.packageManager(or bypackageManager) is missing from the lockfile or no longer matches it,--frozen-lockfilenow fails withERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILEinstead of resolving the version and saving it, so a manifest whose pin was bumped without regenerating the lockfile can no longer pass CI #14009.A git dependency installed over HTTPS from a hosted repository now keeps its branch, tag, or version range in the specifier recorded in
package.json. It was written back without one, so the nextpnpm updatemoved the dependency to the repository's default branch #13999.Fixed
pnpm update --global --latestfailing with a 404 error when a globally installed package was not added from the registry by name. Packages installed from a local path (link:/file:), a git repository, a tarball URL, annpm:alias, or a named registry now keep their spec during a global update instead of being looked up by name in the default registry. See #12854.Fix recursive
pnpm update <name>@<version>so an exact pinned update stays scoped to the requested version line: copies of the same package on another major line — or, for a0.xrequest, another minor line — keep their locked resolution instead of being re-resolved along with the target.Under
nodeLinker: hoisted, a dependency declared against a peer-resolution variant of a package version is no longer dropped from the installed layout. All variants of a version share one hoisted copy, and edges pointing at any of them now resolve to it, so the depending project keeps the package in its.package-map.jsonand the depending package keeps it in itsnode_modules/.bin.Fixed
pnpm install --merge-git-branch-lockfilesdeleting the per-branch lockfiles when thelockfilesetting isfalse. Such an install never reads them, so it has nothing to merge them into and now leaves them alone.Fixed
pnpm installsometimes not exiting after printingDone in Xs#12297.Fixed pnpm failing to read
.modules.yamlfiles containing long dependency paths #13875. The manifest is now parsed as JSON (the format pnpm writes it in), falling back to the YAML parser only for manifests written by old pnpm versions.With
preferSymlinkedExecutables,NODE_PATHagain points at the virtual store of the workspace root when pnpm is run from inside a workspace package, so scripts can resolve dependencies that live only in the hoisted store #13912.Reduced registry metadata requests during dependency resolution by reusing cached metadata when lockfile preferences prove that no uncached version can win pnpm/pnpm#13976.
pnpm pkg getandpnpm pkg setnow accept hyphens inside a dot-notation property path, sopnpm pkg get dependencies.some-package-namereads the key instead of failing withERR_PNPM_UNEXPECTED_TOKEN_IN_PROPERTY_PATH. The bracketed and quoted forms already worked and are unchanged.A resolve request now carries the client's
resolutionMode, so an install delegated to a pnpr server picks versions the way the client would.time-basedandlowest-directreached the server as nothing at all, leaving it on itshighestdefault: the returned lockfile pinned the highest satisfying version of every dependency, and the setting appeared to be ignored.This adds a field to the resolve request body. A server older than its client ignores it and keeps resolving
highest; the protocol is still experimental and unversioned.Fixed
pnpminstalls using pnpr to honor the client'sautoInstallPeers,dedupePeers, andexcludeLinksFromLockfilesettings pnpm/pnpm#13389.pnpm removenow prunes undecided entries ("set this to true or false") fromallowBuildsinpnpm-workspace.yamlwhensharedWorkspaceLockfile: trueand the corresponding packages are removed pnpm/pnpm#13892.Fixed workspace discovery for
pnpm-workspace.yamlfiles without apackagesfield so commands only consider the workspace root instead of recursively scanning nested projects #14047.A runtime installed through
devEngines.runtimenow matches the host whensupportedArchitectureslists several platforms. Listingos: [darwin, linux]andcpu: [x64, arm64]used to install the runtime built for the first entry of each list, so a machine running Linux on arm64 got a macOS x64 Node.js that could not execute #13898.pnpm sbomnow fails withERR_PNPM_SBOM_MISSING_IMPORTERSwhenpnpm-lock.yamlhas no entry for a selected project, instead of writing an SBOM that under-reports that project's dependencies. Previously this crashed withCannot read properties of undefined (reading 'devDependencies').pnpm self-updatenow rewrites a simpledevEngines.packageManager.versionrange (^/~) to the newly installed version, keeping the operator — matching howpnpm updateandpnpm runtime setrewrite ranges. Complex ranges such as>=8.0.0that the new version satisfies are still left unchanged #13935.pnpm self-update <tag>no longer downgrades when the dist-tag points at the pnpm version already running and that version is younger thanminimumReleaseAge. The maturity cutoff moved the tag back to the previous mature release, sopnpm self-update next-12on v12.0.0-rc.4 switched to v12.0.0-rc.3.pnpm set-scriptnow updatespackage.jsoninstead of failing withERR_PNPM_NOT_IMPLEMENTEDpnpm/pnpm#13956.pnpm updatenow preserves the existing range operator when updating a prerelease dependency. See #7002.Installs are faster in workspaces that declare inter-workspace dependencies with plain ranges (
"*","^1.2.3") rather than theworkspace:protocol. WithpreferWorkspacePackagesenabled, linking such a dependency no longer makes a registry request that cannot change the outcome — and workspace packages that were never published no longer cost a 404 on every install.Added
fetchWarnTimeoutMsandfetchMinSpeedKiBpsto the Rust pnpm CLI and its N-API bindings. Slow registry metadata requests and tarball downloads now emit pnpm-compatible warnings without exposing URL credentials, query parameters, fragments, or control characters pnpm/pnpm#12042.An override change is now absorbed by the fast lockfile update even when another, unchanged override uses the
catalog:protocol. Previously anycatalog:-valued override forced a full re-resolution whenever the override list changed, which could move unrelated packages in the lockfile (for example afterpnpm audit --fixadded an override).Packed workspace package manifests now preserve dependency order, making repeated
pnpm packoutput deterministic #10167.pnpm update <name>@<version>now fails withERR_PNPM_UPDATE_VERSION_ON_INDIRECT_DEPwhen the package is not a direct dependency of any selected project, instead of quietly updating it to whatever a fresh install would resolve. There is nowhere to record the version in that case, so the request cannot be honored, and the error points at theoverridesentry that does pin a transitive dependency. Ranges and tags are unaffected, and a package that any selected project declares directly still takes its version as before.trustPolicy: no-downgradeno longer aborts the install withERR_PNPM_MISSING_TIMEon registries that serve no per-versiontimefield whenminimumReleaseAgeIgnoreMissingTimeis set. The trust check reads the same publish dates theminimumReleaseAgecheck does, so it now honors the same opt-in and skips the affected package with a warning #12446.minimumReleaseAgeIgnoreMissingTimeno longer lets a lockfile entry the registry does not list pass theminimumReleaseAgecheck during lockfile verification. The opt-in covers a registry that cannot date its releases; a packument that does date every version it lists is saying it never published this one, which stays a hard failure.The missing-
timewarning now names the check it is reporting on, so a package whoseminimumReleaseAgeandtrustPolicychecks are both skipped warns about both instead of only the first.pnpm update <pkg>@<version>now updates only the selected packages and leaves unrelated dependencies unchanged. A selector that renames the package it installs —pnpm update <alias>@npm:<pkg>@<version>or thejsr:equivalent — now targets the package the alias installs rather than the alias.Fixed
verifyDepsBeforeRunbeing ignored when set toinstall,warn,error, orpromptthrough thePNPM_CONFIG_VERIFY_DEPS_BEFORE_RUNenvironment variable or the--config.verify-deps-before-runflag #13816. Only the boolean values were accepted before, so a string value was silently dropped.pnpm version <bump>with--dry-runno longer editspackage.jsonfiles. It now only reports the bumps it would make, and skips the working tree check, the version lifecycle scripts, the commit, and the tagpnpm/pnpm#13953.Platinum Sponsors
Gold Sponsors
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
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.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate CLI.
79bf45f5ac7eff1e83467eff1e834671118cbb5e71118cbb5e397e8efd02397e8efd024767bd248e4767bd248e462e424516462e424516fbf134de11fbf134de1172714f51c772714f51c7c8f245170ec8f245170ead1cc5ccf2ad1cc5ccf2394bee5fd3394bee5fd3a18cb88cd5a18cb88cd59798064c1e9798064c1efcb82aaed5fcb82aaed57e4a55b7787e4a55b7780b4c4f7e580b4c4f7e5810a87c7fa6View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.