Major Changes
-
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.
Minor Changes
-
Added global build approvals pnpm/pnpm#14101.
-
Added recursive global outdated checks pnpm/pnpm#14101.
-
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.- The
registryand@scope:registryentries show the merged routes rather than raw.npmrcvalues, so they always agree with theregistriesview.
-
Added support for configuring
stateDirin the Rust pnpm CLI pnpm/pnpm#12042. -
Added bounded workspace concurrency for recursive run and exec commands pnpm/pnpm#14101.
-
@pnpm/napigained reporter output, reverse dependency queries, and lockfile access.installandrebuildacceptoptions.reporterand render pnpm's terminal output — progress line, packages-diff summary, lifecycle output, and theDone in …footer. Rendered output goes to stdout, or to anonOutputcallback for a host that writes its own output through JavaScript. New reporting options:hideLifecycleOutput,ignoredBuildsInstructionText, andhideLinkedPkgsDiff.getDependentsreturns the reverse dependency trees behindpnpm why, annotated with thepackage.jsonfields named inmanifestFields.renderDependentsreturns those trees rendered as tree, parseable, or JSON output.readLockfileandwriteLockfileread and writepnpm-lock.yaml(or the current lockfile under the virtual store).filterLockfileByImportersreturns a lockfile narrowed to what the named importers reach.readModulesManifestreturns the.modules.yamlstate of an installednode_modules.Top-level lockfile keys pnpm does not define are no longer dropped when a lockfile is loaded and saved, so state a tool records beside pnpm's own keys survives a rewrite.
-
pnpmnow supports per-branch lockfiles in its Rust engine:gitBranchLockfilegives each git branch its ownpnpm-lock.<branch>.yaml, so two branches can hold different resolutions without conflicting on one file. A branch that has no lockfile yet installs against the sharedpnpm-lock.yaml.mergeGitBranchLockfiles(and the--merge-git-branch-lockfilesflag onpnpm install) folds every branch lockfile back intopnpm-lock.yamland deletes them, which is what merging a branch into the mainline needs.mergeGitBranchLockfilesBranchPattern(and--merge-git-branch-lockfiles-branch-pattern) names the branches that merge automatically, so a mainline branch does not have to pass the flag by hand #12042.
-
Added
PNPM_CONFIG_VIRTUAL_STORE_ONLYandPNPM_CONFIG_ENABLE_MODULES_DIRsupport to the Rust pnpm CLI. -
Added support for the
lockfileDirsetting and its--lockfile-dir <dir>flag onpnpm install,add,update, andremove.pnpm-lock.yaml, the rootnode_modulesholding the virtual store, and the config dependencies now live in the given directory, each project is recorded under its path relative to it, and every project keeps its ownnode_modulesof symlinks — so several projects can share one lockfile #12042. -
Added support for the
preferSymlinkedExecutablessetting. On POSIX systems,node_modules/.binentries are created as symlinks to the executable files instead of shell shims, andNODE_PATHpointing at the virtual store of the workspace root is exported to spawned scripts so they can resolve dependencies from the hoisted store. Like the TypeScript CLI, the setting turns on automatically whennodeLinkeris set tohoisted. -
Added the six CLI flags the TypeScript pnpm CLI accepts but the Rust CLI did not #14101:
--streamprints a recursive command's script output as it arrives, one line at a time, prefixed with the project it came from, instead of letting the scripts write to the terminal directly.--parallelimplies it, as in pnpm.--aggregate-outputholds each script's streamed output until the script exits and then prints it as one block, so concurrent projects can't interleave.--reporter-hide-prefixdrops that project prefix from the scripts' own output lines. On a recursivepnpm exec, the opposite spelling--no-reporter-hide-prefixturns the prefixing on.--use-stderrsends the reporter's output to stderr, leaving stdout for the command's own result.--ignore-workspaceruns the command as if the project were standalone: no workspace root is discovered, sopnpm-workspace.yamlcontributes neither settings nor sibling projects, and a blocked dependency build is not scaffolded into itsallowBuilds.--workspace-packagesoverrides thepackagespatterns ofpnpm-workspace.yamlfor the run.
The
stream,aggregateOutput,reporterHidePrefix,useStderr, andignoreWorkspacesettings are now read frompnpm-workspace.yaml, the globalconfig.yaml, and theirPNPM_CONFIG_*environment variables too. -
Added support for the
shellEmulatorsetting. With it enabled, the scriptspnpm runexecutes, a project's own lifecycle scripts, and dependencies' build scripts run in a built-in POSIX shell instead of the platform's (sh -c, orcmd /d /s /con Windows), so scripts written forshbehave the same on every OS.scriptShellis not used while the emulator is on. -
The Rust engine now checks that a package read back from the store is the package it was recorded as. When the tarball's
package.jsonnames a different name or version than the store entry was keyed for — a broken lockfile, or a registry serving content that doesn't match its metadata — the install fails withERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE. Set the newstrictStorePkgContentChecksetting tofalseto downgrade the failure to a warning and install from the entry anyway #12042. -
pnpmnow supports three workspace settings in its Rust engine:includeWorkspaceRoot(and the universal--include-workspace-root/--no-include-workspace-rootflags) keeps the workspace root project in a recursiverun,exec,add, ortest, which otherwise leave it out.ignoreWorkspaceCyclesanddisallowWorkspaceCyclescontrol the report an install makes when workspace projects depend on each other in a cycle: it is a warning by default, anERR_PNPM_DISALLOW_WORKSPACE_CYCLESerror underdisallowWorkspaceCycles, and silent underignoreWorkspaceCycles#12042.
-
Added support for the remaining pnpm default settings, including recursive command controls, optional dependency selection, workspace-root checks, color modes, lockfile compatibility, and pack manifest options.
-
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. -
Added the commands the Rust CLI was still missing:
pnpm get <key>andpnpm set <key> <value>— the top-level spellings ofpnpm config getandpnpm config set.pnpm store status— reports the packages whose files no longer match the store they were expanded from, failing withERR_PNPM_MODIFIED_DEPENDENCY; andpnpm store add <pkg>...— fetches packages into the store without writing a manifest, a lockfile, ornode_modules.pnpm env use --global <version>andpnpm env list [<selector>], the deprecated Node.js-only front end topnpm runtime.pnpm edit,pnpm profile,pnpm token, andpnpm xmasnow fail withERR_PNPM_NOT_IMPLEMENTEDpointing at the npm CLI, instead of being taken for a package script.
-
An install that resolves the dependency graph now reports the unmet peer dependencies it leaves behind, matching the TypeScript CLI. By default it warns once —
Issues with peer dependencies found. Run "pnpm peers check" to list them.— and withstrictPeerDependenciesit fails withERR_PNPM_PEER_DEP_ISSUESafter the artifacts are written, listing every unmet peer. This coverspnpm install,add,remove,updateand--lockfile-only;pnpm dedupereported the same verdict already, and now shares the reporting with them.peerDependencyRulesare applied before the verdict, so a rule that covers every issue leaves nothing to report, and a--filtered install reports only on the projects it installed. An install that skips resolution — a frozen install, or one whosepnpm-lock.yamlis already up to date — reports nothing, as in the TypeScript CLI;pnpm peers checkinspects such a tree #14098. -
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. -
Added filtered and split SBOM generation with per-project lockfiles, including reachable workspace projects and incomplete-graph validation pnpm/pnpm#14101.
Patch Changes
-
Kept pending build approvals available after removing an unrelated dependency.
-
Fixed resolving the
chcpcommand on Windows duringpnpm setupby looking forchcp.combeforechcppnpm/pnpm#13991. -
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. -
pnpm dedupeaccepts thepnpm installoptions that pnpm documents for it —--lockfile-only,--ignore-scripts,--offline, and--prefer-offline— instead of rejecting them withunexpected argument. Without--lockfile-only,pnpm dedupenow also updatesnode_modules, as an install does #14107. -
pnpm dedupein the Rust engine now fails withERR_PNPM_PEER_DEP_ISSUESwhenstrictPeerDependenciesis set and unresolved peer dependency issues remain after deduplication, matching the TypeScript CLI #14099. Previously it only ever printed a warning, regardless of the setting. -
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. -
pnpm install --devandpnpm deploy --devno longer install optional dependencies, and--prodnow takes precedence when combined with--dev, matching the TypeScript pnpm CLI. -
A dependency published with
"bin": "", such asurl-loader@1.1.2, no longer fails the install withERR_PNPM_CMD_SHIM_PROBE_SHIM_SOURCE#13962. An emptybindeclares no command, as it does in pnpm v11, so no shim is written for the package; adirectories.binentry on the same package is still linked. -
A dependency pinned to an exact version carrying semver build metadata (
"@parcel/codeframe": "2.0.0-canary.1718+d8408010f") installs again instead of failing withERR_PNPM_NO_MATCHING_VERSION#14096. npm strips build metadata when it publishes a version, so pnpm strips it from the version it looks up, matching npm and pnpm v11. -
A package's
filesentries now match only at the package root, the way npm reads them. A baresrcused to also match nested directories such asexample/src, so a dependency installed from git could ship the repository's own example app. The same filter decides whatpnpm packandpnpm publishput in a tarball and whatpnpm deploycopies, so those stop carrying the extra files too. Exclusions such as!**/__tests__and!*.mapstill match at any depth. A package already in the store keeps its old file set until it is fetched again. -
A
pnpm install --filter <selector>run that has nothing to do now reports "Already up to date" without entering the install pipeline, the same way an unfilteredpnpm installalready did #14033. -
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. -
Settings written to a
pnpm-workspace.yamlblock that uses inline (flow) YAML —catalog: { foo: ^1.0.0 },overrides: { foo: 1.0.0 },minimumReleaseAgeExclude: [foo@1.0.0]— are now edited in place instead of failing or corrupting the file.pnpm audit,pnpm link,pnpm approve-builds,pnpm patch,pnpm add --config, and catalog updates all keep the block's flow style, its other entries, and its comments #14108. -
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. -
When a git-hosted dependency is blocked from running build scripts, the error now suggests an
allowBuildsentry that actually approves it. It quoted the bare package name, which never matches a git-hosted package, so following the suggestion left the install failing the same way #14002. -
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. -
Added support for the
globalPnpmfilesetting, which names a user-level pnpmfile that runs for every project ahead of the project's own. Like pnpm, it is left out of the lockfile'spnpmfileChecksum, so editing it does not decide whether a lockfile is still current.pnpmfileandglobalPnpmfileare now also readable fromPNPM_CONFIG_PNPMFILEandPNPM_CONFIG_GLOBAL_PNPMFILE. -
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. -
A repeat
pnpm installwithnodeLinker: hoistedis a no-op again when a workspace package declares the dependencies #14001. The hoisted linker installs them into the rootnode_modules, but the up-to-date check previously looked under each package's ownnode_modulesand reinstalled the whole tree every time. A hoisted install also no longer reports the packages it just wrote as broken. -
ignorePnpmfilecan now be set inpnpm-workspace.yamland read fromPNPM_CONFIG_IGNORE_PNPMFILE, not only passed as--ignore-pnpmfile, so a project or a machine can turn pnpmfile hooks off once instead of adding the flag to every command. The flag still applies on top. As in pnpm, the globalconfig.yamlcannot set it: a pnpmfile belongs to the project that ships it. -
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. -
--config.minimum-release-ageis honored again, along with--config.minimum-release-age-exclude,--config.minimum-release-age-ignore-missing-timeand--config.minimum-release-age-strict. Each overrides the matchingpnpm-workspace.yamlsetting, and the exclude flag may be repeated to build a list #13929. -
An unreadable
node_modules/.modules.yamlno longer makespnpm installdeletenode_modulesand relink every package on each run. The unparsable state file is now reported as an error instead #14062. -
pnpm outdatedandpnpm update --interactivenow leave out the dependencies listed inupdateConfig.ignoreDependencies, instead of reporting them and offering them for update. -
Fixed
pnpm outdatedandpnpm update --interactiveoffering versions blocked byminimumReleaseAgepnpm/pnpm#14004. -
pnpm packwrites tar entries in the POSIX ustar header form npm uses —ustar\0magic and the explicit0regular-file typeflag — instead of the GNU form with a NUL typeflag, which strict tar readers such as publint mistake for the end-of-archive marker #13924. -
Fixed
--config.ignore-scripts=truenot being honored by CLI commands such aspnpm pack#13986. -
pnpm install <pkg>now adds the package, the same aspnpm add <pkg>and matching the JavaScript CLI. It previously ended in a usage error:pnpm i valibotprintederror: unexpected argument 'valibot' foundinstead of saving the dependency #13886. -
Fixed Plug'n'Play projects to preload
.pnp.cjsfor dependency and project lifecycle scripts,pnpm run, andpnpm exec. The generated loader now also exposes the public Yarn PnP API surface. -
Workspace packages declared with a parent-relative pattern in
pnpm-workspace.yaml(../shared,../../docs/*) are discovered again. They were dropped from the project list, sopnpm list -rand--filterdid not see them and a frozen install of a lockfile that already held their importer entries failed withERR_PNPM_PACKAGE_MANAGER_UNSAFE_IMPORTER_PATH. -
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 path named by the
pnpmfilesetting that is not on disk now fails withERR_PNPM_PNPMFILE_NOT_FOUNDand names the file, instead of surfacing as a generic pnpmfile execution failure. Discovery of the default.pnpmfile.mjs/.pnpmfile.cjsis unaffected: a project that ships neither still installs normally. -
pnpm removenow prunes undecided entries ("set this to true or false") fromallowBuildsinpnpm-workspace.yamlwhensharedWorkspaceLockfile: trueand the corresponding packages are removed pnpm/pnpm#13892. -
Suggest
pnpm shim add <runtime>after pinning a project runtime when no project-aware global shim is installed. Explicit project-aware shims now reject unrelated global bin conflicts and are restored after a matching global package is removed or replaced by a version that drops its bin. -
pnpm -r update --latest --depth 0 <selector>now fails withERR_PNPM_NO_PACKAGE_IN_DEPENDENCIESwhen no project in the workspace declares a matching dependency, instead of silently doing nothing. -
Fixed repeat installs paying for a full lockfile comparison forever after a modification-time collision. When a
package.jsonwas last modified inside the same clock tick that the install recorded as its validation baseline — a fast install, a checkout that copied files with identical timestamps, or any filesystem that keeps only whole-second modification times — the manifest kept reading as possibly-modified, so every laterpnpm installandverify-deps-before-runcheck re-compared the manifests against the lockfile instead of taking the fast path #13907. -
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 sbomnow honours--filter-prod, the full--filterselector syntax (dependency queries such aspkg...,{dir}and glob paths,[since]change queries, exclusions), and--workspace-root. Selectors that match no project printNo projects matched the filtersand write no SBOM, and--splitemits its per-project SBOMs in a stable order.The universal
--fail-if-no-matchflag is supported too: any filtered command whose selectors match no workspace project now exits with code 1 #14064. -
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 updatenow preserves the existing range operator when updating a prerelease dependency. See #7002. -
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>@<tag>now saves the version the dist tag resolved to inpackage.json, keeping the range operator the dependency already declared, instead of saving the tag itself. A dependency declared through acatalog:reference, aworkspace:ornpm:alias, or a path or git specifier keeps its declaration, and one that already tracks a dist tag records the tag asked for #14092. -
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. -
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. -
On Windows,
pnpm store pathnow returns a conventional drive path without the\\?\verbatim prefix when the project and pnpm home are on different drives #13987.
Platinum Sponsors
|
|
|
|
Gold Sponsors
|
|
|
|
|
|
|
|
|
|
|
|