pnpm 12.18.0
pnpm 12.18.0 packs, publishes, and installs packages with platform variants on pnpr registries. It also adds per-skill approval of agent skills and pnpm store add for offline installs from downloaded tarballs. Repeat installs and verifyDepsBeforeRun no longer read the package.json of unchanged workspace projects. The release fixes several peer dependency resolution bugs too.
Minor Changes#
Agent skills#
-
pnpm now names the link to a dependency's agent skill after the skill alone. A skill that the
@acme/kitpackage ships inskills/auth/is linked as.claude/skills/auth, not.claude/skills/pnpm-@acme+kit-auth. The next install replaces links with the old names. pnpm lists the linked skills in a# pnpmblock of each agent skill directory's.gitignore#17455.When two approved packages ship a skill of the same name, or the name is already taken in an agent skill directory, pnpm links the skill as
pnpm-<package>-<skill>. Before, the install failed. -
pnpm approvenow asks which agent skills to link when a package ships more than one. pnpm records the chosen skills as a list, such aspermissions.<pkg>.skills: [guide, migrate], and does not offer the other skills for approval again. The first time a project approves a skill,pnpm approvealso asks which agent directories to link skills into, and records the answer underskills.dirs. -
pnpm links agent skills nested in category directories, such as
skills/engineering/implement/SKILL.md, up to three levels belowskills/. Before, it linked only the skills directly underskills/.
Platform variants#
These features need a registry declared with serverType: pnpr.
-
pnpm packpacks one archive per entry ofpublishConfig.variants, each platform variant as<name>-<version>~<tag>.tgz.pnpm pack --variant <target>packs only the archive whose entry lists that target.pnpm pack --jsonprints theintegrityandsizeof every archive it writes. -
pnpm publishpublishes a package'spublishConfig.variants. pnpm uploads every archive before the publish request that references them. Publishing variants to any other registry is refused unless--no-variantsis given, which publishes only the archive that targetsany. -
pnpm publish --upload-variant <target>uploads the archive of onepublishConfig.variantsentry to the registry's staging area and prints<target>=<integrity>.pnpm publish --variant-integrity <target>=<integrity>then publishes the version from archives that other jobs uploaded. With--recursive, each value names its package as<name>~<target>=<integrity>. -
pnpm publish -r --batchnow uploads each package archive on its own request and skips archives the registry already holds, so the batch request no longer grows with the archives it publishes. A batch of more than 256 packages still carries every archive inline. Packages withpublishConfig.variantscan be published in a batch. A batch with variants that would reference more than 256 archives on one registry fails withERR_PNPM_BATCH_PUBLISH_TOO_MANY_ARCHIVESpnpm/tasks#244. -
pnpm publish --provenancesigns one statement that names every archive of a version withpublishConfig.variants. A platform variant's archive is named by the version's package URL with avariantqualifier. -
Rerunning
pnpm publishafter the version was already published now succeeds when the published version is the same release. pnpm compares the archives, the provenance statement when the rerun signs one, and the dist-tag. When the published version differs, the publish fails withERR_PNPM_PUBLISHED_RELEASE_DIFFERS, which names what differs. -
pnpm installs the platform variant of a package that fits the host, or the package's own archive when none does. The lockfile records every variant. A version published with variants only, and no archive for other hosts, installs on a host one of the variants fits. On any other host, an optional dependency is skipped, and any other dependency fails with an error that lists the platforms the version is published for.
-
supportedArchitecturesplatform lists accept platform tags, the same tagspublishConfig.variantsentries name. An entry can state floors and an ABI, such aslinux-x64-manylinux_2_28,darwin-arm64-macos_14_0,linux-x64-napi8orlinux-x64-node127, and any Node.js OS and CPU, such asfreebsd-x64. The spellings accepted before still work. -
With
supportedArchitecturesset, pnpm also fetches into the store the variants that the other listed platforms need, when it downloads a package with platform variants.
pnpr registries#
-
pnpm downloads less metadata from a registry declared with
serverType: pnpr. A large package document leaves out the manifests of rarely picked versions, and pnpm fetches one only when it picks that version. In a benchmark that resolves 787 packages, the package documents shrank from 30.3 MB to 2.5 MB gzipped. pnpm caches these documents inv12/metadata-pnpr, and thepnpm cachecommands cover them.pnpm warns when a registry that is not declared
serverType: pnprturns out to be pnpr. For a registry declaredserverType: pnpr, aminimumReleaseAgelonger than 30 days fails withERR_PNPM_UNSUPPORTED_MINIMUM_RELEASE_AGE. -
pnpm publishsends release notes to registries declared withserverType: pnpr. A package's notes are the section for its version inCHANGELOG.md, or the notes thatpnpm version -rcomposed from change intents.--release-notes <file>gives the notes from a file, and--no-release-notessends none.--publication-namenames a release published with--batch.In a workspace that uses pnpm's versioning,
pnpm version -r --release-notes <file>puts a summary above the composed notes of the release.pnpm view <pkg>@<version> releaseNotes,pnpm outdated --notes, andpnpm update --interactiveshow the release notes from these registries.
Store and hooks#
-
pnpm store addnow prepares the store for offline installs from package tarballs downloaded some other way. Afterpnpm store add ./tarballs/*.tgz,pnpm install --offline --frozen-lockfileinstalls each registry package whose lockfile entry pins one of those tarballs #2978. -
Custom pnpmfile fetchers can use
fetchers.gitHostedTarball,fetchers.git, andfetchers.binarypnpm/tasks#191.
Patch Changes#
Installing packages#
-
Running
nodeorpnpm installin a project that pins a Node.js version pnpm has not downloaded yet no longer hangs when aglobalPnpmfileis configured #17530. -
pnpm installwithenableGlobalVirtualStoreno longer fails on Windows for a dependency from a named registry, such asgh:ornpmjs:#17665. -
Concurrent installs that share a global virtual store no longer fail with
ERR_PNPM_CMD_SHIM_SYMLINK_BINwhen they link the same executable at the same time #17587. -
pnpm installandpnpm cinow succeed when dependency skills are approved but no agent skill directory exists. The skills stay unlinked until a directory exists #17574. -
Commands run from a workspace package now use the same default store as the workspace root. Before,
pnpm addin a package could fail withERR_PNPM_UNEXPECTED_STOREwhen the workspace root was its own mount point and the home store was on another volume, as in a sandbox or a container.A workspace installed with the old behavior may need one
pnpm install --forcefrom the workspace root. -
pnpm deploynow keeps a symlink in the project whose target is missing from the source, such as a link into anode_modulesdirectory that was removed after the build. Deploy used to drop such a link silently #17571. -
pnpm installnow links the runtime that an earlierpnpm install --no-runtimeskipped, andpnpm install --no-runtimeremoves a runtime that an earlier install linked #17542. -
With
enableGlobalVirtualStore,pnpm installagain runs a dependency's build scripts in a project whose global virtual store slot another project already built, unless the side-effects cache has build output to restore for it. A build that writes only outside its own package, such as a git hook installer, now takes effect in every project #17526. -
Commands of a package whose
binpaths contain excess parent directory segments, such as../dist/cli.js, are no longer missing. Such paths now resolve inside the package #17586. -
Generated POSIX shell shims now quote their paths, so shell special characters in a path stay literal.
Lockfile and local paths#
-
Packages from the default registry keep plain lockfile keys, even when a prefix such as
npmjspoints at the registry set asregistry. A dependency written with such a prefix shares the plain entry of the same package. The next install rewrites keys that earlier versions qualified this way #17575. -
pnpm install --frozen-lockfile,pnpm approve-builds, andpnpm rebuildnow accept a lockfile that resolves a*dependency to a prereleaselatestversion. pnpm wrote such a lockfile and then reported it as outdated #17594. -
pnpm install --frozen-lockfilenow accepts a lockfile written withexcludeLinksFromLockfileandlinkWorkspacePackageson, when a project depends on a workspace package through acatalog:entry. It used to fail withERR_PNPM_OUTDATED_LOCKFILE. -
pnpm install --frozen-lockfileno longer rejects a freshly generated lockfile whenaddMissingPeerTypesis enabled and an injected workspace package has peer dependencies #17668. -
Relative local paths in root
overridesnow resolve from the workspace root when each project has its own lockfile #17691.Installing a local directory dependency no longer fails when another project's install is writing a lockfile in that directory.
-
Relative local paths in root catalogs now resolve for dependencies and peers #17690.
-
pnpm installnow resolves auto-installed relativeworkspace:andfile:peers from the directory of the package that declares them #17676, #17689. -
pnpm dedupe --checkleavespnpm-lock.yamluntouched while it looks for deduplication changes. IfconfigDependencieschanged since the last install, the check keeps the lockfile they sync, aspnpm installdoes #17628. -
pnpm dedupe --checkno longer fails when the only difference is the integrity of a locked version that the registry now reports with a stronger algorithm, such as asha512integrity replacing thesha1shasum it reported when the lockfile was written #17692.
Peer dependencies#
-
Peer deduplication no longer gives a project different copies of a dependency across direct and transitive paths when an optional peer has no provider #17666.
-
Dependencies no longer bind to another project's peer variant when the provider has nested peers, including providers that finish resolving later #17593.
-
Re-resolving a lockfile no longer preserves optional peer bindings from other projects when no provider is in scope #17592.
-
pnpm peers checkwarns when a required peer is resolved without a provider declared by the project or its dependency ancestors, withautoInstallPeers: falseanddedupePeerDependents: true. These warnings do not change the command's exit code or causestrictPeerDependenciesfailures.
Speed and memory#
-
pnpm install, andpnpm runandpnpm execwithverifyDepsBeforeRun, no longer read thepackage.jsonof workspace projects whose files did not change since the last install. This also holds with a pnpmfile,configDependencies, orsharedWorkspaceLockfile: false. In an up-to-date workspace withsharedWorkspaceLockfile: false,pnpm installno longer runs the install of each project pnpm/tasks#256. -
A recursive
pnpm runorpnpm execwithverifyDepsBeforeRunset no longer reads everypackage.jsonin the workspace once per project whensharedWorkspaceLockfileisfalse. In a 300-project workspace,pnpm -r exec truewent from 490 ms to 42 ms pnpm/tasks#256. -
pnpm installmakes fewerstatcalls when it hardlinks files from the store, as few as 12.7.0 made #17543. -
Picking versions from registry metadata uses less temporary memory.
verifyDepsBeforeRun and repeat installs#
-
With
verifyDepsBeforeRun,pnpm runandpnpm execnow report dependencies as out of date after a dependency is added to thepackage.jsonof a local directory dependency, such as"lib": "file:../lib". Before, the command ran without the new dependency installed. -
verifyDepsBeforeRunno longer reports afile:directory dependency as out of date after an edit topackage.jsonthat leaves its dependencies unchanged #17697. -
verifyDepsBeforeRunno longer reports dependencies as out of sync when only theversionfield of a single-project repository changed #17560. The same holds for a workspace project that no dependency can resolve to. A script run after such a change no longer starts an install first. -
A repeat
pnpm installnow rewrites the bin shims afterextendNodePathchanges. Before, it reported "Already up to date" and the shims kept theNODE_PATHof the previous setting.
Adding and updating dependencies#
-
pnpm add <pkg>without a version now saves the range that the other workspace projects declare for the package, or the one a matching override sets. Acatalog:reference still comes first, and thetagsetting is used only when none of these exists pnpm/tasks#191. -
pnpm updatewith only negated selectors, such aspnpm update '!foo', now leaves the dependencies listed inupdate.ignoreDepsunchanged. Negatedcrate:andpypi:selectors, such as!crate:sha2, now exclude that dependency and update the rest pnpm/tasks#243. -
pnpm update --latestkeeps three kinds of Cargo dependencies where they are, unless the crate is named on the command line. Before, it moved each one to the newest release and left a workspace that did not build.- A renamed dependency, such as
tower-0-4 = { package = "tower", version = "0.4" }, stays within its requirement if the workspace also declares the crate under its own name. - A crate replaced through
[patch.crates-io]keeps its version, with a warning. - A crate whose newest release is older than its current version, such as a prerelease newer than the latest stable release, keeps its version, with a warning. Only an explicit version, such as
pnpm update crate:foo@1, moves it back.
Other new major versions are still selected and may need code changes.
- A renamed dependency, such as
-
pnpm updatenow keeps a Cargo dependency at its current version, with a warning, if the new version lacks a feature the workspace enables on it. If the crate is named on the command line, as inpnpm update --latest crate:sha2, the update fails with an error that names the missing features. -
pnpm updatenow keeps the operator of every existing Cargo requirement, so~1.0.0is updated to~1.1.0, and an exact=requirement stays exact unless the selector gives a range, such ascrate:foo@^2. ThesavePrefixandsaveExactsettings apply only to new Cargo dependencies. Saved Cargo requirements omit build metadata. -
pnpm updatenow updates a Cargo dependency whose inline table spans several lines, such as one with a multi-linefeaturesarray. It failed with "its Cargo.toml dependency declaration is not a string or single-line inline table". -
pnpm updateno longer applies thesavePrefixandsaveExactsettings to Python requirements. AsavePrefixsuch as^madepnpm updatefail in a project with Python dependencies.pnpm updatenow keeps the precision of a~=requirement, so~=1.0is updated to~=1.5and still allows any 1.x release. With--latest, a==1.*requirement is updated to==2.*.
Store#
-
pnpm store prunenow keeps packages linked from a configured modules directory pnpm/tasks#230. -
pnpm store prunenow removes aliases of pruned packages. -
pnpm store statusno longer reports untouched packages as modified when the global virtual store is enabled. If the installed global-store layout cannot be verified, the command reportsERR_PNPM_STORE_STATUS_UNVERIFIABLEand asks for a reinstall #17631. -
pnpm store addnow fails withERR_PNPM_FROZEN_STORE_WRITEwhenfrozenStoreis enabled. It used to write package files into the read-only store without recording them, so no install could use them. -
pnpm cat-index <name>@<version>no longer shows the index of a package whose name differs only in letter case.
Other commands and settings#
-
pnpm linknow fails without changingpackage.jsonorpnpm-workspace.yamlwhen the linked package's name is not a valid npm package name. It used to write that name to both files, which made every later install fail #17475. -
pnpm link <dir>now resolves a relative<dir>against the current directory. In a directory without apackage.json, such as a workspace'spackagesfolder,pnpm link .used to link the nearest project above it into itself #17475. -
A Node.js version range such as
>=26.13, for example indevEngines.runtime, now resolves to the newest stable release that satisfies it. pnpm picks a prerelease only when no stable release matches #17516. -
Switching to the pnpm version a project pins no longer writes a temporary
_tmp_*file into the project directory, which file watchers reported on every command #17681. -
pnpm now reads the
linkWorkspacePackages,injectWorkspacePackages,extendNodePath,frozenStore, andoptimisticRepeatInstallsettings fromPNPM_CONFIG_*environment variables, such asPNPM_CONFIG_LINK_WORKSPACE_PACKAGES=true.--config.extend-node-pathnow applies too. -
Recursive scripts run with
--sequentialor--workspace-concurrency=1print GitHub Actions log groups again #17629. -
When
pnpm docscannot open a browser, the URL it prints no longer includes the query string or the fragment.