pnpm 11.8

pnpm 11.8 adds install dry-run previews, Node.js package map generation, richer SBOM output, pnpm view defaulting to the current package, and correct pnpm run --no-bail exit codes. It also includes a config-dependency lockfile traversal fix and many install/update determinism fixes.

Minor Changes#

pnpm install --dry-run#

pnpm install now supports --dry-run. It runs full dependency resolution and reports what a real install would change, but writes nothing to disk: no lockfile, no manifests, and no node_modules updates.

A completed dry run exits with code 0, matching the preview behavior of npm install --dry-run (#7340).

Node.js package maps#

pnpm now generates node_modules/.package-map.json during isolated and hoisted installs. The map describes how packages should resolve through the installed node_modules layout.

The new nodeExperimentalPackageMap setting injects the generated map into pnpm-managed Node.js script environments by adding Node's --experimental-package-map option to NODE_OPTIONS. The nodePackageMapType setting chooses between standard maps, which expose declared dependencies only, and loose maps, which also map packages reachable through the installed layout.

SBOM improvements#

pnpm sbom can now write SBOMs to files with --out, generate one SBOM per selected workspace package with --split, and use the selected package's metadata as the root component when --filter selects a single package.

CycloneDX output now marks components reachable only through devDependencies with scope: "excluded" and the cdx:npm:package:development property. Runtime components, including installed optional dependencies, keep the default required scope.

CLI behavior#

pnpm run --no-bail now continues running every matched script, but exits with a non-zero code if any script failed. Recursive runs already behaved this way; non-recursive runs now match them (#8013).

pnpm view can now be run without a package name. In that case it searches upward for the nearest project manifest and uses its name field.

Patch Changes#

  • Security: Validate config dependency names and versions from the env lockfile before using them to build filesystem paths. A committed lockfile can no longer use traversal-shaped configDependencies names or versions to write outside node_modules/.pnpm-config or the store. Names must be valid npm package names, and versions must be exact semver versions. See GHSA-qrv3-253h-g69c.
  • Fixed pnpm update overriding the version range policy of a named catalog whose name parses as a version, such as catalog:express4-21 (#10321).
  • Fixed link: workspace protocol dependencies switching to file: after pnpm rm when injectWorkspacePackages: true is set and the target workspace dependency has its own dependencies.
  • Stopped warning about matching packageManager and devEngines.packageManager values when both pin the same package manager, version, and integrity hash (#12028).
  • Fixed interactive progress output leaving characters behind when external processes write to the terminal, such as SSH passphrase prompts (#12350).
  • Fixed pnpm approve-builds missing packages whose build approval was revoked and then re-added (#12221).
  • Skipped the redundant Windows warning about an existing node.exe when it already matches the target (#12203).
  • Fixed macOS Gatekeeper blocking native binaries imported from the store by removing the com.apple.quarantine extended attribute from native binaries after import (#11056).
  • Fixed optimisticRepeatInstall incorrectly reporting "Already up to date" when only pnpm-lock.yaml changed, and fixed the same checks for git branch lockfiles (#12100).
  • Fixed recursive updates that mix transitive dependency patterns with direct dependency selectors, such as pnpm up -r "@babel/core" uuid (#12103).
  • Registered pnpm update --no-save in CLI help and option parsing.
  • Fixed pnpm import for Yarn v2 lockfiles when js-yaml v4 is installed.
  • Fixed repeated reinstall prompts when enableGlobalVirtualStore is enabled by keeping the virtual store directory recorded during post-install builds aligned with the install step (#12307).
  • Documented the --cpu, --os, and --libc flags in pnpm install --help (#12359).
  • Avoided reading README.md from disk during publish when the publish manifest already provides a readme field.
  • Fixed pnpm peers check rejecting loose peer dependency ranges that the installed peer satisfies (#12149).
  • Preserved workspace: dependencies that point at local paths during pnpm update (#3902).
  • Fixed a lockfile non-convergence case where incremental installs could keep a duplicate transitive dependency that a fresh install would remove.
  • pnpm install detects changes inside local file: dependencies and local tarballs again, bypassing the optimistic fast path for those projects (#11795).
  • Preserved the existing Node.js runtime version prefix when resolving node@runtime:<range> to a concrete version.
  • Shortened CAFS temporary package directories to leave room for lifecycle scripts that create IPC socket paths under TMPDIR.
  • Reporter output for pnpm store and pnpm config subcommands now goes to stderr, so scripts can safely capture stdout.
  • Avoided relinking unchanged child dependencies and removed stale child links during warm installs.
  • Fixed lockfile churn where transitivePeerDependencies could be dropped or shifted when a package participates in a dependency cycle (#5108).
  • Fixed pnpm install reporting "Already up to date" after a catalog entry in pnpm-workspace.yaml was reverted to a previous version (#12418).
  • Kept lockfile overrides that resolve through a catalog in sync when pnpm update bumps the catalog entry.
  • Fixed pnpm version --recursive so it honors workspace selection instead of always bumping every workspace package (#11348).