Pular para o conteúdo principal

pnpm 12.0

· 10 min para ler
Zoltan Kochan
Lead maintainer of pnpm

pnpm 12 is stable. It is a rewrite of pnpm in Rust. Upgrading should not feel like a migration. The commands, flags, settings, and lockfile format of pnpm 11 all carry over, and the documentation covers both versions.

The few things that behave differently are listed in What's different in pnpm 12. This post covers what is new.

Installing it

latest on npm still points at the pnpm 11 line, so install pnpm 12 from the next-12 tag:

pnpm self-update next-12

See Installing pnpm 12 for the other ways, including without Node.js. Homebrew, winget, Scoop, and Chocolatey don't offer it yet.

Breaking changes

Git dependencies are identities

For repositories on GitHub, GitLab, and Bitbucket, a specifier now only says which repository you want. github:owner/repo, owner/repo, git+https://…, and git+ssh://git@… all resolve through the host's HTTPS URL, and the lockfile never records an SSH URL for those hosts. Whether a machine talks to the host over SSH is that machine's business. If you need SSH for private repositories, tell git to rewrite the URL:

git config --global url."git@github.com:".insteadOf https://github.com/

pnpm shells out to git, so the rewrite applies to every git operation pnpm runs. Unknown hosts keep their exact URL, SSH included. A URL with embedded credentials is also kept as written. The full rules are in How git dependencies are resolved.

An unrecognized setting in pnpm-workspace.yaml is reported

pnpm used to ignore settings it did not recognize. Misspell minimumReleaseAge and the policy was silently gone. pnpm 12 reports the unknown key and suggests the closest real setting name when the key looks like a typo.

Whether this is an error or a warning depends on the version pin. If the project pins a pnpm version and the running pnpm satisfies it, the command fails with ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS. The setting cannot be meant for some other pnpm version, so it is a mistake. Without a matching pin it is a warning, and the command goes ahead. The pnpm config subcommands never fail on it, so you can still inspect and fix a broken file.

Lockfiles of cyclic dependency graphs

Peer resolution now breaks dependency cycles at a fixed place. It orders the packages in a cycle by package id and always cuts the same edge, no matter where the install entered the cycle.

The lockfile now depends only on the dependency graph. Reordering workspace projects or dependencies, or installing twice, produces the same bytes. In pnpm 11 it could produce a different lockfile each time (#13846, #13865). Large workspaces with many cycles also resolve peers 2–3× faster and use about 25% less memory, and their lockfiles shrink.

Existing lockfiles keep working. --frozen-lockfile installs them as they are, and an install that does not re-resolve leaves them untouched. The first install that does re-resolve rewrites the peer variants of cyclic packages, so expect a one-time lockfile diff. Details in How peers are resolved.

On Linux, auto now tries a hardlink before a reflink. Hardlinks are cheaper to create, and on btrfs this roughly halves the time an install spends materializing node_modules from a warm store.

Cloning is still the second choice, so a store that refuses a hardlink gets a clone, and packageImportMethod: clone still asks for one outright. Nothing changes on ext4, which never supported cloning, so auto already hardlinked there. macOS keeps clone-first because APFS clonefile is fast.

engineStrict follows the edge, not the subtree

With engineStrict on, pnpm 12 fails the install when a package that is being installed depends, through regular dependencies, on a package with an incompatible engine. It no longer matters that the whole subtree sits under an optionalDependencies entry. pnpm 11 installs the package and prints an install-check warning. A package reachable only through optional edges, or through a package that was itself skipped, is still skipped in both versions (#13286).

New features

Some of these also ship in pnpm 11.25: registry revisions, the remote side-effects cache, audit.ignorePrune, batch staged approval, and the pnpm init change. The rest depend on the Rust rewrite and are pnpm 12 only.

Project-aware global bins

A globally installed node, deno, or bun now runs the version the current project pins. There is no shell hook to install and no use command to run. The new globalShims setting chooses which global packages get this treatment. It defaults to { node: true, deno: true, bun: true }, and your own value is merged into that default, so globalShims: { typescript: true } adds TypeScript without repeating the three runtimes.

Stable Node.js releases switch without asking, because pnpm verifies them against the Node.js release team's signatures. Deno, Bun, Node.js prereleases, and package bins you enabled yourself all ask "Do you trust this project?" once per project and per version. pnpm remembers the answer on that machine. Set PNPM_SHIM_BYPASS=1 to skip the shim for one command. See Project-aware global bins.

pnpm installs the other package managers

pnpm can now install npm, Yarn Classic, Yarn Berry, Yarn 6 (yarnpkg/zpm), and Bun. Each comes from its own trusted source, and the ones published to npm are checked against npm's signature for that exact version before they run.

This shows up in a few places. A git-hosted dependency is built with the package manager its own repository asks for, so a repository that uses Yarn installs fine on a machine that only has pnpm. pnx runs one for a single command, as in pnx yarn@4 install, pnx npm@11 ci, or pnx node@22. And pnpm shim add yarn links a yarn command that runs whatever version the current project pins.

One consequence is that the name now means the tool, not the npm package that happens to share its name. pnpm add -g yarn@4 installs Yarn Berry. Inside a project, pnpm add yarn@4 writes "packageManager": "yarn@4.18.0" to package.json, since that is the field Corepack reads. The other package managers are recorded in devEngines.packageManager. If you want the npm package, say so with an explicit source, as in pnpm add yarn@npm:yarn@1.22.22. More in Other package managers.

Registry revisions

A registry can now offer a replacement tarball for a version that is already published, for example a rebuild with a vulnerability patched out. The version number stays the same, and the original tarball at the name@version URL is never rewritten. pnpm calls the replacement a revision, identifies it by its full SHA-512 digest, and records it in the lockfile as one extra line:

pnpm-lock.yaml
packages:
lodash@4.17.21:
resolution:
integrity: sha512-<replacement-digest>
revision: 1

An entry without a revision line is revision 0, the original tarball. That is what every existing lockfile entry already means, so a lockfile that uses no replacements does not change at all.

A dependency or override can pin a revision as <version>+rN. pnpm update --patches picks up new revisions without changing any version. pnpr serves revisions for the packages it hosts and passes them through from an upstream registry that has them. The format is described in Registry revisions.

pnpm init pins the latest pnpm

pnpm init now pins the latest released pnpm, not the version that ran the command. A project scaffolded with an old pnpm used to inherit that old version through its own pin (#7490). If pnpm cannot find out what latest is, because there is no network, the registry is slow, offline is set, or minimumReleaseAge or trustPolicy rejects it, it pins the running version as before. The lookup never fails or hangs the command.

Batch approval for staged publishing

pnpm stage approve can approve several staged packages at once. Run it without a stage id to pick from a list, or pass the ids. One one-time password covers the whole batch, and pnpm only asks for a new one when the registry stops accepting it. In a workspace, packages are approved in dependency order. If a package's workspace dependency fails to get approved, pnpm skips that package too, so nothing gets published that points at a version the registry never received.

audit.ignorePrune

With audit.ignorePrune: true, pnpm audit --fix drops ignored GHSA entries that no longer show up in the audit report. Your list of tolerated advisories stops collecting entries for dependencies you removed long ago.

Global commands refuse to run under sudo

pnpm setup, pnpm self-update, and every other command that changes the global installation now fail under sudo with ERR_PNPM_SUDO_NOT_SUPPORTED. Before, they quietly modified root's home directory. pnpm keeps global packages and configuration in your own home, so none of these commands needs root. Read-only global commands such as pnpm bin --global still work under sudo.

A remote side-effects cache (proof of concept)

This is an opt-in proof of concept. Instead of running a dependency's lifecycle scripts on every machine, an install can upload the build output to pnpr, signed and scoped to your organization, and later installs download it.

The repository only declares which organization and which packages take part, through remoteSideEffectsCache.organization and packages. Signing settings are refused in pnpm-workspace.yaml and must come from the global config file or the environment. A repository you cloned therefore cannot make your machine sign artifacts with your key. Whenever the cache fails, pnpm runs the build locally as usual. Restoring works on Linux/glibc x64 and arm64 only for now. See Shared side-effects cache.

Fixes worth knowing about

The compatibility database drops its static-analysis entries

The built-in compatibility database no longer contains the dependencies that static analysis of published packages found. Many of those were packages a dependent imports only for its types. Installing them was unnecessary at best. At worst it broke things. @typescript-eslint/types got a typescript dependency that resolved to the newest release, which put TypeScript 7 under older @typescript-eslint versions, and ESLint failed with Cannot read properties of undefined (reading 'Intrinsic'). The @yarnpkg/extensions entries and pnpm's own hand-written entries stay.

A store inside the project when nothing above it is linkable

If no directory above the project accepts a hard link, pnpm now creates the default store at <project>/node_modules/.pnpm-store instead of in the pnpm home directory. This happens in an AI agent sandbox that grants write access only to the project, or in a container where only the project is mounted writable. In those environments the home store is read-only or on another volume, and every package had to be copied instead of hard linked (#13525).

The filterLog pnpmfile hook is deprecated

pnpm 12 ignores hooks.filterLog and warns when a pnpmfile defines it. Use loglevel to choose how much pnpm reports.

Feedback

Please report any issues you run into.