<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>pnpm Blog</title>
    <link>https://pnpm.io/blog</link>
    <description>pnpm Blog</description>
    <lastBuildDate>Thu, 15 Oct 2026 00:00:00 GMT</lastBuildDate>
    <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
    <language>en</language>
    <item>
      <title><![CDATA[pnpm 12.14.0]]></title>
      <link>https://pnpm.io/blog/releases/12.14.0</link>
      <guid>https://pnpm.io/blog/releases/12.14.0</guid>
      <pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release adds the sideEffectsCache.exclude setting, stops pnpm install from waiting on the daily update check, and keeps pnpm from aborting with a core dump when the reader of its output exits.]]></description>
      <content:encoded><![CDATA[<p>This release adds the <code>sideEffectsCache.exclude</code> setting, stops <code>pnpm install</code> from waiting on the daily update check, and keeps pnpm from aborting with a core dump when the reader of its output exits.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<ul>
<li>
<p>Added the <code>sideEffectsCache.exclude</code> setting for packages whose build depends on something pnpm cannot include in the cache key, such as the system libraries a native addon links against. pnpm builds the listed packages in every project and never restores or saves their builds through the side-effects cache, locally or on a <code>remoteCache</code> server. Under the global virtual store, each project gets its own copy of these packages and of the packages that depend on them. Entries use the same <code>name</code>, <code>@scope/*</code>, and <code>name@version</code> patterns as <code>minimumReleaseAgeExclude</code> <a href="https://github.com/pnpm/pnpm/issues/5271">#5271</a>.</p>
</li>
<li>
<p><code>pnpm login</code> now prints the <code>npm-notice</code> response header during web-based login. Registries use it to show the verification code that completes the sign-in in the browser.</p>
</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="installing-packages">Installing packages<a class="hash-link" href="#installing-packages" aria-label="Direct link to Installing packages">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> and <code>pnpm add</code> no longer wait for the daily update check. If the command finishes first, pnpm skips the update notice and checks again next time. On a registry that accepted the connection but never answered, an install that had nothing to do used to take minutes <a href="https://github.com/pnpm/pnpm/issues/16839">#16839</a>.</p>
</li>
<li>
<p><code>pnpm install</code>, <code>pnpm add</code> and <code>pnpm update</code> now reuse <code>pnpm-lock.yaml</code> when the workspace configures a catalog entry that no dependency references, such as an unused entry or one whose package an <code>overrides</code> entry replaces. Before, every resolution in such a workspace resolved all dependencies again from the registry <a href="https://github.com/pnpm/pnpm/issues/16923">#16923</a>.</p>
</li>
<li>
<p><code>pnpm install</code> now looks up a git dependency's branch or tag once per install, even when many workspace projects depend on it. With <code>linkWorkspacePackages</code> enabled, it used to run <code>git ls-remote</code> once per project <a href="https://github.com/pnpm/pnpm/issues/16899">#16899</a>.</p>
</li>
<li>
<p>A filtered install, such as <code>pnpm install --filter &#x3C;pkg></code>, now keeps the <code>pnpm-lock.yaml</code> entries of workspace projects that are missing from disk, as in a sparse checkout. Their importers, packages, and catalog entries stay even when the filter matches every project on disk <a href="https://github.com/pnpm/pnpm/issues/16931">#16931</a>.</p>
</li>
<li>
<p>pnpm now decides whether a dependency that a package also declares as an optional peer is linked as a dependency or as a peer from the shallowest path that reaches the package. When an existing lockfile records such a dependency as a regular dependency and a newly added dependency now provides the peer, pnpm records it as a peer, as a fresh install does. The lockfile entry of such a package can change on the next install <a href="https://github.com/pnpm/pnpm/issues/16901">#16901</a>.</p>
</li>
<li>
<p>An install that adds nothing to the store no longer changes the modification time of the store directory. CI caches that check modification times, such as GitLab's, no longer re-upload an unchanged store <a href="https://github.com/pnpm/pnpm/issues/16929">#16929</a>.</p>
</li>
</ul>
<h3 id="running-commands-and-scripts">Running commands and scripts<a class="hash-link" href="#running-commands-and-scripts" aria-label="Direct link to Running commands and scripts">#</a></h3>
<ul>
<li>
<p>pnpm no longer aborts with a core dump when the reader of its output has exited, as in <code>pnpm list | head -1</code> or <code>pnpm start 2>&#x26;1 | head -1</code>. The command finishes with its usual exit code, and a failing script's exit code is kept. A write to stdout that fails for another reason, such as a full disk, ends the command with exit code 1 <a href="https://github.com/pnpm/pnpm/issues/16904">#16904</a> <a href="https://github.com/pnpm/tasks/issues/132">pnpm/tasks#132</a>.</p>
</li>
<li>
<p>On Windows, <code>pnpm exec</code>, <code>pnpm run</code>, and commands such as <code>pnpm nx</code> now return when the command they run exits, even if it left a detached process running and pnpm's output is redirected or piped. Previously a shell reading that output waited until the detached process, such as the Nx daemon, exited <a href="https://github.com/pnpm/pnpm/issues/16930">#16930</a>.</p>
</li>
<li>
<p>Commands that do not write the project manifest now leave a missing <code>package.json</code> absent. This covers queries such as <code>pnpm why</code> and <code>pnpm outdated</code>, <code>pnpm fetch</code>, <code>pnpm dedupe --check</code>, and commands that exit with an error, such as <code>pnpm rt ls</code>. These commands used to create one from the initialization template. <code>pnpm sbom</code> run from a lockfile alone now reports the root component as <code>unknown</code> with version <code>0.0.0</code> <a href="https://github.com/pnpm/pnpm/issues/16907">#16907</a>.</p>
</li>
<li>
<p><code>pnpm run</code> now reports a failed script as a JSON record under <code>--reporter=ndjson</code>. It printed a plain <code>[ELIFECYCLE]</code> line to stderr.</p>
</li>
<li>
<p><code>pnpm run</code> no longer prints <code>[ELIFECYCLE] Command failed with exit code N.</code> to stderr when a script fails under <code>--reporter=silent</code>, <code>--silent</code>, or <code>--loglevel=silent</code>.</p>
</li>
<li>
<p><code>pnpm pipeline --dry-run</code> no longer writes a run report when no projects are selected. With <code>--json</code> it prints <code>{"tasks": []}</code> <a href="https://github.com/pnpm/tasks/issues/126">pnpm/tasks#126</a>.</p>
</li>
</ul>
<h3 id="packing-and-publishing">Packing and publishing<a class="hash-link" href="#packing-and-publishing" aria-label="Direct link to Packing and publishing">#</a></h3>
<ul>
<li>
<p><code>pnpm pack</code> and <code>pnpm publish</code> now ship the whole gitignored directory that <code>main</code>, <code>browser</code>, or <code>bin</code> points into, as npm does. Previously only the <code>main</code> and <code>bin</code> files themselves were packed, and a gitignored <code>browser</code> file was left out <a href="https://github.com/pnpm/pnpm/issues/16908">#16908</a>.</p>
</li>
<li>
<p><code>pnpm pack</code> and <code>pnpm publish</code> now honor a <code>.npmignore</code> or <code>.gitignore</code> inside a subdirectory when <code>package.json</code> has a <code>files</code> field. The package root's ignore files and those inherited from the workspace are still skipped <a href="https://github.com/pnpm/pnpm/issues/16909">#16909</a>.</p>
</li>
</ul>
<h3 id="setting-up-pnpm-and-toolchains">Setting up pnpm and toolchains<a class="hash-link" href="#setting-up-pnpm-and-toolchains" aria-label="Direct link to Setting up pnpm and toolchains">#</a></h3>
<ul>
<li>
<p><code>--registry</code>, <code>--https-proxy</code>, <code>--http-proxy</code>, and <code>--no-proxy</code> now apply when pnpm resolves and downloads the pnpm version a project pins in <code>packageManager</code>. Before, these lookups went to <code>https://registry.npmjs.org/</code>, which a firewall that only allows a private registry blocks <a href="https://github.com/pnpm/pnpm/discussions/16911">#16911</a>.</p>
</li>
<li>
<p>On Windows, <code>pnpm install</code> now installs the Rust toolchain for rustup's default host, so a machine set up with <code>rustup set default-host x86_64-pc-windows-gnu</code> gets the MinGW toolchain. Without a default host, pnpm installs the MSVC toolchain for the machine's architecture, as rustup does. This includes a pnpm built for MinGW, such as the MSYS2 package, which has installed the MinGW toolchain since 12.13.0. An x64 pnpm on ARM64 Windows now installs the ARM64 toolchain <a href="https://github.com/pnpm/pnpm/issues/16854">#16854</a>.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 11.28.6]]></title>
      <link>https://pnpm.io/blog/releases/11.28.6</link>
      <guid>https://pnpm.io/blog/releases/11.28.6</guid>
      <pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release stops pnpm publish from sending a CI identity token to a registry it was not issued for. It also fixes installs that dropped a dependency also declared as an optional peer, and accepts empty httpProxy and httpsProxy settings again.]]></description>
      <content:encoded><![CDATA[<p>This release stops <code>pnpm publish</code> from sending a CI identity token to a registry it was not issued for. It also fixes installs that dropped a dependency also declared as an optional peer, and accepts empty <code>httpProxy</code> and <code>httpsProxy</code> settings again.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="security">Security<a class="hash-link" href="#security" aria-label="Direct link to Security">#</a></h3>
<ul>
<li>
<p><code>pnpm publish</code> now uses an <code>NPM_ID_TOKEN</code> only if its <code>aud</code> claim matches the selected registry, such as <code>npm:registry.npmjs.org</code>. A token for another registry is skipped with a warning, and pnpm falls back to the configured credentials.</p>
</li>
<li>
<p>The warning about renamed package files with invalid names no longer prints control characters from those file names.</p>
</li>
</ul>
<h3 id="configuration">Configuration<a class="hash-link" href="#configuration" aria-label="Direct link to Configuration">#</a></h3>
<ul>
<li>
<p>pnpm again accepts <code>httpProxy</code> and <code>httpsProxy</code> set to <code>null</code> or written with no value in <code>pnpm-workspace.yaml</code> and the global config file, and treats them as unset. Since 11.28.5 these values failed with <code>ERR_PNPM_INVALID_SETTING</code>.</p>
</li>
<li>
<p>pnpm now switches to the pnpm version a project pins even when it cannot read its configuration. Previously, a setting it rejected in the global config file, an <code>.npmrc</code> file, <code>_auth</code>, or <code>pnpm-workspace.yaml</code> stopped every command, <code>pnpm --version</code> included.</p>
</li>
<li>
<p>A key under a registry URL in the <code>_auth</code> setting that does not start with <code>@</code> is now skipped with a warning, so a field a later pnpm version adds there no longer breaks this one. A key that holds an <code>authToken</code> object but lacks its <code>@</code>, such as <code>org</code> for <code>@org</code>, is still an error.</p>
</li>
</ul>
<h3 id="resolving-dependencies">Resolving dependencies<a class="hash-link" href="#resolving-dependencies" aria-label="Direct link to Resolving dependencies">#</a></h3>
<ul>
<li>
<p>pnpm no longer drops a dependency that a package also declares as an optional peer dependency, for example <code>ajv</code> of <code>ajv-formats</code>. Since 11.28.0, in some dependency graphs the package got the dependency neither as a dependency nor as a peer, and failed to import it <a href="https://github.com/pnpm/pnpm/issues/16901">#16901</a>.</p>
</li>
<li>
<p><code>pnpm install</code> no longer fails with <code>ERR_PNPM_NO_MATCHING_VERSION</code> after a change to <code>overrides</code> when the lockfile resolves an optional peer dependency to an npm alias of another package <a href="https://github.com/pnpm/pnpm/issues/16654">#16654</a>.</p>
</li>
<li>
<p><code>pnpm install</code> and <code>pnpm dedupe</code> now link an optional peer to the workspace package that the workspace root depends on when the picked version matches it. Previously they installed the registry package with the same name and version <a href="https://github.com/pnpm/pnpm/issues/16706">#16706</a>.</p>
</li>
<li>
<p>Removal overrides such as <code>"debug>supports-color": "-"</code> now also apply to an optional peer that a package declares only in <code>peerDependenciesMeta</code> <a href="https://github.com/pnpm/pnpm/issues/16681">#16681</a>.</p>
</li>
<li>
<p><code>pnpm dedupe</code> now preserves a hoisted workspace package when another package uses a separately installed peer with the same name <a href="https://github.com/pnpm/pnpm/issues/16935">#16935</a>.</p>
</li>
<li>
<p><code>pnpm install</code> and <code>pnpm add --config</code> now apply <code>minimumReleaseAge</code> when they resolve a config dependency. A config dependency range resolves to the newest version that is old enough, so a later clean <code>pnpm install --frozen-lockfile</code> accepts the lockfile <a href="https://github.com/pnpm/pnpm/issues/16660">#16660</a>.</p>
</li>
<li>
<p><code>pnpm install --fix-lockfile</code> no longer removes the <code>deprecated</code> and <code>hasBin</code> fields from lockfile entries <a href="https://github.com/pnpm/pnpm/issues/6600">#6600</a>.</p>
</li>
</ul>
<h3 id="workspaces-and-catalogs">Workspaces and catalogs<a class="hash-link" href="#workspaces-and-catalogs" aria-label="Direct link to Workspaces and catalogs">#</a></h3>
<ul>
<li>
<p>A filtered install, such as <code>pnpm install --filter &#x3C;pkg></code>, now keeps the <code>pnpm-lock.yaml</code> entries of workspace projects that are missing from disk, as in a sparse checkout. Their importers, packages, and catalog entries stay even when the filter matches every project on disk <a href="https://github.com/pnpm/pnpm/issues/16931">#16931</a>.</p>
</li>
<li>
<p><code>catalogPrune</code> no longer removes catalog entries that <code>pnpm-lock.yaml</code> still records for workspace projects missing from disk. This affected <code>pnpm remove</code> and installs that leave the lockfile unchanged, such as frozen installs. Before, a following frozen install failed with <code>ERR_PNPM_LOCKFILE_CONFIG_MISMATCH</code> <a href="https://github.com/pnpm/pnpm/issues/16679">#16679</a>.</p>
</li>
<li>
<p>Sped up workspace discovery with subtree exclusions such as <code>!generated/**</code>.</p>
</li>
</ul>
<h3 id="linking-node_modules">Linking node_modules<a class="hash-link" href="#linking-node_modules" aria-label="Direct link to Linking node_modules">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> no longer fails on Windows with <code>EPERM</code> on <code>rmdir node_modules/@scope</code> when several workspace packages under one scope are hoisted, for example with <code>nodeLinker: hoisted</code> <a href="https://github.com/pnpm/pnpm/issues/16690">#16690</a>.</p>
</li>
<li>
<p><code>pnpm install</code> hardlinks files from a group-writable or world-writable store again. Before this fix, pnpm copied every file from such a store into <code>node_modules</code> <a href="https://github.com/pnpm/pnpm/issues/16677">#16677</a>.</p>
</li>
<li>
<p>With <code>enableGlobalVirtualStore</code>, an install that updates <code>node_modules</code> now restores the dependency links of a package in the global virtual store that an interrupted install left without them. Before, such an install kept the incomplete package if the project's <code>node_modules</code> already recorded it <a href="https://github.com/pnpm/pnpm/issues/16642">#16642</a>.</p>
</li>
<li>
<p>Removing a <code>link:</code> dependency now also removes its symlink from <code>node_modules</code> when <code>nodeLinker</code> is <code>hoisted</code> <a href="https://github.com/pnpm/pnpm/issues/16884">#16884</a>.</p>
</li>
<li>
<p>An install that adds nothing to the store no longer changes the modification time of the store directory. CI caches that check modification times, such as GitLab's, no longer re-upload an unchanged store <a href="https://github.com/pnpm/pnpm/issues/16929">#16929</a>.</p>
</li>
</ul>
<h3 id="injected-workspace-packages">Injected workspace packages<a class="hash-link" href="#injected-workspace-packages" aria-label="Direct link to Injected workspace packages">#</a></h3>
<ul>
<li>
<p>With <code>sharedWorkspaceLockfile: false</code>, an injected workspace package installed in the same run as its dependent now holds only the files its <code>files</code> field selects. The copy used to also hold other files of the project, such as <code>tsconfig.json</code> <a href="https://github.com/pnpm/pnpm/issues/16683">#16683</a>.</p>
</li>
<li>
<p>A script listed in <code>syncInjectedDepsAfterScripts</code> no longer fails when it rewrites <code>package.json</code> while pnpm is copying its edits into the injected copies. The sync after the script now replaces a half-copied manifest.</p>
</li>
<li>
<p>Sped up frozen installs of unchanged injected workspace packages that use hardlinks.</p>
</li>
</ul>
<h3 id="deploying">Deploying<a class="hash-link" href="#deploying" aria-label="Direct link to Deploying">#</a></h3>
<ul>
<li>
<p><code>pnpm deploy</code> without <code>injectWorkspacePackages</code> now binds a peer dependency of a linked workspace package to the version its parent provides, as an injected install does. Previously the deploy failed with <code>ERR_PNPM_DEPLOY_AMBIGUOUS_PEER</code> whenever other packages in the deployed graph depended on different versions of that peer <a href="https://github.com/pnpm/pnpm/issues/16807">#16807</a>.</p>
</li>
<li>
<p><code>pnpm deploy</code> in a workspace with <code>injectWorkspacePackages</code> no longer crashes when a linked workspace package declares a peer dependency named after an <code>Object.prototype</code> member, such as <code>constructor</code>.</p>
</li>
</ul>
<h3 id="setting-up-pnpm">Setting up pnpm<a class="hash-link" href="#setting-up-pnpm" aria-label="Direct link to Setting up pnpm">#</a></h3>
<ul>
<li>
<p>pnpm now relinks the bins of a pnpm version it installed earlier for <code>packageManager</code> or <code>pnpm with</code>. A pnpm 12 version installed by a pnpm release older than 11.28.4 could keep a launcher that ran <code>node</code> on the native binary, so every command failed with <code>SyntaxError: Invalid or unexpected token</code> <a href="https://github.com/pnpm/pnpm/issues/16646">#16646</a>.</p>
</li>
<li>
<p><code>pnpm setup</code> no longer tries to install pnpm globally from npm's <code>bin</code> directory when pnpm was installed with npm. It printed <code>Failed to create bin at &#x3C;PNPM_HOME>/bin/pnpm</code> <a href="https://github.com/pnpm/pnpm/issues/16855">#16855</a>.</p>
</li>
</ul>
<h3 id="output">Output<a class="hash-link" href="#output" aria-label="Direct link to Output">#</a></h3>
<ul>
<li>
<p>Escape codes in script output that moved the cursor or cleared the screen garbled pnpm's output. pnpm now drops them and keeps only color codes. Color codes are dropped too when pnpm's own output is not in color.</p>
</li>
<li>
<p>When the registry rejects <code>pnpm stage publish</code>, the error message now starts with "Failed to stage package".</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.13.0]]></title>
      <link>https://pnpm.io/blog/releases/12.13.0</link>
      <guid>https://pnpm.io/blog/releases/12.13.0</guid>
      <pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release adds the pnpm profile and pnpm token commands, the storeIndexFormat: files setting for stores shared over a network filesystem, and faster Python dependency resolution.]]></description>
      <content:encoded><![CDATA[<p>This release adds the <code>pnpm profile</code> and <code>pnpm token</code> commands, the <code>storeIndexFormat: files</code> setting for stores shared over a network filesystem, and faster Python dependency resolution.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<ul>
<li>
<p>Added the <code>pnpm profile</code> command to view and update registry user profile settings.</p>
</li>
<li>
<p>Added <code>pnpm token</code> to list, create, and revoke authentication tokens on the registry.</p>
</li>
<li>
<p>Added the <code>storeIndexFormat</code> setting. Set it to <code>files</code> to keep a store's package index as one file per package instead of the SQLite <code>index.db</code>, for a store on a network filesystem that several machines use at once. SQLite's locking does not work across machines, so concurrent installs on such a store could corrupt <code>index.db</code>.</p>
</li>
<li>
<p>Added the <code>nodePackageMap</code> setting, which groups the package-map settings. <code>nodePackageMap.mode: write-only</code> writes <code>node_modules/.package-map.json</code> for other tools without making Node.js load it. <code>mode: load</code> also makes Node.js load the map, like <code>nodeExperimentalPackageMap: true</code>. <code>nodePackageMap.type</code> replaces <code>nodePackageMapType</code>. The old settings still work <a href="https://github.com/pnpm/pnpm/issues/14937">#14937</a>.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">nodePackageMap:</span>
  <span class="hljs-attr">mode:</span> <span class="hljs-string">write-only</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">loose</span>
</code></pre>
</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="setting-up-pnpm">Setting up pnpm<a class="hash-link" href="#setting-up-pnpm" aria-label="Direct link to Setting up pnpm">#</a></h3>
<ul>
<li>
<p><code>pnpm setup</code> now works when pnpm was installed with npm on macOS. It installed pnpm from npm's <code>bin</code> directory and left a <code>$PNPM_HOME/bin/pnpm</code> that could not run <a href="https://github.com/pnpm/pnpm/issues/16855">#16855</a>.</p>
</li>
<li>
<p>pnpm runs again after <code>pnpm setup</code> when <code>globalShims</code> enables pnpm. Every command failed with "the global target of the pnpm shim points back at the shim" <a href="https://github.com/pnpm/pnpm/issues/16874">#16874</a>.</p>
</li>
<li>
<p>A pnpm built for the MinGW target on Windows, such as the MSYS2 package, now installs the MinGW Rust toolchain that <code>rust-toolchain.toml</code> names. It used to install the MSVC toolchain.</p>
</li>
</ul>
<h3 id="installing-packages">Installing packages<a class="hash-link" href="#installing-packages" aria-label="Direct link to Installing packages">#</a></h3>
<ul>
<li>
<p>On macOS, <code>pnpm install</code> no longer fails with <code>ERR_PNPM_LOCKFILE_WRITE_FILE</code> when the project is on a virtiofs or SMB share. pnpm now falls back to a regular <code>fsync</code> when the file system rejects a full sync <a href="https://github.com/pnpm/pnpm/issues/16846">#16846</a>.</p>
</li>
<li>
<p>On macOS, <code>pnpm install</code>, <code>pnpm patch-commit</code>, and other commands that load a pnpmfile no longer hang at random.</p>
</li>
<li>
<p><code>pnpm install</code> now puts the executables of a Python build backend's requirements on the <code>PATH</code> while the backend runs. Projects built with <code>uv_build</code> now install. Before, they failed with "uv-build was not properly installed".</p>
</li>
<li>
<p>Removing a <code>link:</code> dependency now also removes its symlink from <code>node_modules</code> when <code>nodeLinker</code> is <code>hoisted</code> <a href="https://github.com/pnpm/pnpm/issues/16884">#16884</a>.</p>
</li>
<li>
<p><code>pnpm-lock.yaml</code> no longer records the tarball URL of a package installed from a scoped registry, such as one set by <code>@types:registry=</code>, when pnpm can rebuild the URL from that registry <a href="https://github.com/pnpm/pnpm/issues/16866">#16866</a>.</p>
</li>
</ul>
<h3 id="packing-auditing-and-pipelines">Packing, auditing, and pipelines<a class="hash-link" href="#packing-auditing-and-pipelines" aria-label="Direct link to Packing, auditing, and pipelines">#</a></h3>
<ul>
<li>
<p><code>pnpm pack</code> and <code>pnpm publish</code> now pack only <code>package.json</code>, the README, the license, and the files named in <code>main</code> and <code>bin</code> when <code>files</code> is an empty array. They used to pack every file in the package <a href="https://github.com/pnpm/pnpm/issues/16889">#16889</a>.</p>
</li>
<li>
<p><code>pnpm audit</code> and <code>pnpm audit signatures</code> no longer check runtimes installed through <code>devEngines.runtime</code>, such as <code>node@runtime:24.21.0</code>, against the npm package of the same name. <code>pnpm audit signatures</code> reported these runtimes as having an invalid registry signature <a href="https://github.com/pnpm/pnpm/issues/16869">#16869</a>.</p>
</li>
<li>
<p><code>pnpm pipeline</code> now runs the root's tasks when <code>pnpm-workspace.yaml</code> lists only the root in <code>packages</code> or omits <code>packages</code>, as <code>pnpm -r run</code> does. A change to a root file in such a workspace no longer triggers a full-run warning. When a full run has no projects to run, <code>pnpm pipeline</code> now reports that no projects are selected <a href="https://github.com/pnpm/pnpm/issues/16849">#16849</a>.</p>
</li>
</ul>
<h3 id="performance">Performance<a class="hash-link" href="#performance" aria-label="Direct link to Performance">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> resolves Python dependencies faster. With <code>--lockfile-only</code>, a project with 10 dependencies now resolves in about 4 seconds, down from about 40. When the index publishes a metadata file beside each wheel (PEP 658), as PyPI does, pnpm downloads only the wheels of the releases it selects.</p>
</li>
<li>
<p>A repeat install against an existing lockfile no longer reads registry metadata for the dependencies it takes from that lockfile. On a warm store this removes thousands of metadata lookups from a repeat install of a large workspace.</p>
</li>
<li>
<p><code>pnpm install</code> with <code>enableModulesDir: false</code> now verifies the packages already in the store while the dependencies resolve, and checks a package's files in parallel. On a 5,100-package install with the store on a network filesystem, this removed a 19-second pause between resolution and fetching.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.12.1]]></title>
      <link>https://pnpm.io/blog/releases/12.12.1</link>
      <guid>https://pnpm.io/blog/releases/12.12.1</guid>
      <pubDate>Sun, 11 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release adds OIDC authentication to pnpm dist-tag and the tool: protocol. pnpm publish now checks which registry an NPM_ID_TOKEN was issued for before using it.]]></description>
      <content:encoded><![CDATA[<p>This release adds OIDC authentication to <code>pnpm dist-tag</code> and the <code>tool:</code> protocol. <code>pnpm publish</code> now checks which registry an <code>NPM_ID_TOKEN</code> was issued for before using it.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p><code>pnpm publish</code> now uses an <code>NPM_ID_TOKEN</code> only if its <code>aud</code> claim matches the selected registry, such as <code>npm:registry.npmjs.org</code>. A token for another registry is skipped with a warning, and pnpm falls back to the configured credentials.</p>
</li>
<li>
<p><code>pnpm dist-tag add</code>, <code>rm</code>, and <code>ls</code> now support OIDC authentication in CI. Enable "Allow npm dist-tag" in the package's trusted publishing settings to manage tags without a stored npm token.</p>
</li>
<li>
<p>Added the <code>tool:</code> protocol for the tools pnpm downloads itself: Node.js, Bun, Deno, and Yarn. <code>pnpm add bun@tool:1.3.0</code>, <code>pnx node@tool:22</code>, and <code>pnx yarn@tool:4</code> work like the <code>runtime:</code> spelling, which stays supported as an alias. A lockfile written for <code>runtime:</code> stays up to date when <code>package.json</code> switches to <code>tool:</code>, and the other way around.</p>
<p>A named registry can no longer be called <code>tool</code>.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.12.0]]></title>
      <link>https://pnpm.io/blog/releases/12.12.0</link>
      <guid>https://pnpm.io/blog/releases/12.12.0</guid>
      <pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release shares task results and dependency builds through a remote cache, adds the addMissingPeerTypes setting, and lets pnpm add --config and pnpm update manage configuration dependencies. It also fixes dangerouslyAllowAllBuilds running the build scripts of packages that allowBuilds denies.]]></description>
      <content:encoded><![CDATA[<p>This release shares task results and dependency builds through a remote cache, adds the <code>addMissingPeerTypes</code> setting, and lets <code>pnpm add --config</code> and <code>pnpm update</code> manage configuration dependencies. It also fixes <code>dangerouslyAllowAllBuilds</code> running the build scripts of packages that <code>allowBuilds</code> denies.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<ul>
<li>
<p><code>pnpm pipeline</code> can share task results between machines. A result is a signed artifact stored on a pnpr server or on a server that speaks the Turborepo Remote Cache API, such as Vercel Remote Cache.</p>
<p>The remote side-effects cache can also store dependency builds on a Turborepo Remote Cache server. Both caches read the new <code>remoteCache</code> setting, which names the server and holds the signing keys. <code>pnpm pipeline --report</code> records the run under the organization <code>remoteCache.org</code> names.</p>
</li>
<li>
<p>Added the <code>addMissingPeerTypes</code> setting, which links the <code>@types</code> package of a peer dependency next to every package that peer-depends on it. For example, a package that peer-depends on <code>react</code> gets the project's <code>@types/react</code> linked next to it. This lets TypeScript find the types of a peer dependency when the global virtual store is on <a href="https://github.com/pnpm/pnpm/discussions/15689">#15689</a>.</p>
</li>
<li>
<p><code>pnpm add --config</code> and <code>pnpm update</code> now handle configuration dependencies the way they handle other dependencies <a href="https://github.com/pnpm/pnpm/issues/16814">#16814</a>.</p>
<p><code>pnpm add --config</code> saves a version range in <code>pnpm-workspace.yaml</code> by the same rules as <code>pnpm add</code>, so <code>pnpm add --config my-config@latest</code> saves a range such as <code>^1.2.0</code>. It also resolves the package again when <code>pnpm-lock.yaml</code> already locks an older version.</p>
<p><code>pnpm update</code> updates configuration dependencies within their ranges, and <code>pnpm update --latest</code> moves them to their latest version. A selector such as <code>pnpm update my-config</code> updates only the configuration dependencies it names.</p>
</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p>A package set to <code>false</code> in <code>allowBuilds</code> no longer runs its build scripts when <code>dangerouslyAllowAllBuilds</code> is <code>true</code>. The two settings together now allow every build except the denied ones. Previously, <code>dangerouslyAllowAllBuilds</code> ignored the denials.</p>
</li>
<li>
<p>pnpm now switches to the pnpm version a project pins even when it cannot read its configuration. Previously, a setting it rejected in the global config file, an <code>.npmrc</code> file, or <code>_auth</code>, or a <code>pnpm-workspace.yaml</code> it could not parse, stopped every command, <code>pnpm --version</code> included.</p>
</li>
<li>
<p><code>pnpm update</code> and <code>pnpm why</code> accept <code>--depth Infinity</code> again. They failed with <code>invalid value 'Infinity' for '--depth &#x3C;DEPTH>'</code> <a href="https://github.com/pnpm/pnpm/issues/16817">#16817</a>.</p>
</li>
<li>
<p><code>pnpm deploy</code> without <code>injectWorkspacePackages</code> now binds a peer dependency of a linked workspace package to the version its parent provides, as an injected install does. Previously the deploy failed with <code>ERR_PNPM_DEPLOY_AMBIGUOUS_PEER</code> whenever other packages in the deployed graph depended on different versions of that peer <a href="https://github.com/pnpm/pnpm/issues/16807">#16807</a>.</p>
</li>
<li>
<p><code>pnpm deploy --legacy</code> no longer fails with <code>ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER</code> when a workspace dependency of the deployed project declares a <code>workspace:</code> peer dependency that the project does not depend on itself. pnpm now copies that peer from the workspace into the deploy directory.</p>
<p>An auto-installed peer declared with <code>workspace:</code> now always resolves to the workspace project. Previously, <code>workspace:*</code> and <code>workspace:^1.0.0</code> peers could be installed from the registry <a href="https://github.com/pnpm/pnpm/issues/16806">#16806</a>.</p>
</li>
<li>
<p>Repeat installs no longer resolve again when an injected workspace package lists a dependency also as a peer dependency.</p>
</li>
<li>
<p>Sped up frozen installs of unchanged injected workspace packages that use hardlinks.</p>
</li>
<li>
<p>Sped up workspace discovery with subtree exclusions such as <code>!generated/**</code>.</p>
</li>
<li>
<p>A key under a registry URL in the <code>_auth</code> setting that does not start with <code>@</code> is now skipped with a warning, so a field a later pnpm version adds there no longer breaks this one. A key that holds an <code>authToken</code> object but lacks its <code>@</code>, such as <code>org</code> for <code>@org</code>, is still an error.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.11.2]]></title>
      <link>https://pnpm.io/blog/releases/12.11.2</link>
      <guid>https://pnpm.io/blog/releases/12.11.2</guid>
      <pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release fixes --workspace-concurrency=Infinity, filters set by an updateConfig hook, and store fetches with enable-modules-dir=false.]]></description>
      <content:encoded><![CDATA[<p>This release fixes <code>--workspace-concurrency=Infinity</code>, filters set by an <code>updateConfig</code> hook, and store fetches with <code>enable-modules-dir=false</code>.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p><code>--workspace-concurrency=Infinity</code> now runs workspace projects with no concurrency limit. It used to fail with <code>invalid digit found in string</code> <a href="https://github.com/pnpm/pnpm/issues/16793">#16793</a>.</p>
</li>
<li>
<p><code>pnpm install</code> and other recursive commands now apply the <code>filter</code> and <code>filterProd</code> that an <code>updateConfig</code> hook sets. They used to run on every workspace project <a href="https://github.com/pnpm/pnpm/issues/16792">#16792</a>.</p>
</li>
<li>
<p><code>enable-modules-dir=false</code> now also fetches the packages an install reuses from an existing lockfile, so the store holds every package the lockfile lists.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.11.1]]></title>
      <link>https://pnpm.io/blog/releases/12.11.1</link>
      <guid>https://pnpm.io/blog/releases/12.11.1</guid>
      <pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release fixes two ways pnpm install could fail, runs tools of any Rust release through pnx, and treats registry.npmjs.com as an alias of the npm registry.]]></description>
      <content:encoded><![CDATA[<p>This release fixes two ways <code>pnpm install</code> could fail, runs tools of any Rust release through <code>pnx</code>, and treats <code>registry.npmjs.com</code> as an alias of the npm registry.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p><code>pnpm install</code> no longer fails when <code>packageManager</code> pins the pnpm version that is already running and the registry does not publish that version. pnpm warns and continues. A registry mirror that has not synced a release no longer blocks the commands of a project pinned to it.</p>
</li>
<li>
<p><code>pnpm install</code> no longer fails with <code>ERR_PNPM_CMD_SHIM_CHMOD</code> when <code>node_modules/.bin</code> holds a shim that another user created, if everyone can already execute it and it is not world-writable. This happens when several users share one checkout.</p>
</li>
<li>
<p><code>enable-modules-dir=false</code> (<code>enableModulesDir: false</code> through the Node.js addon) fetches the registry packages the host can install into the store again, as pnpm v10 did, while still writing nothing under <code>node_modules</code>. The setting exists for a <code>node_modules</code> that something else mounts from the store, such as a FUSE daemon, and that consumer no longer has to download each package on first access. A plain <code>--lockfile-only</code> run still fetches nothing.</p>
</li>
<li>
<p><code>pnpm pack</code> and <code>pnpm publish</code> no longer put <code>.npmignore</code> and <code>.gitignore</code> files in the tarball. A <code>files</code> entry that names one still ships it.</p>
</li>
<li>
<p>pnpm now treats <code>https://registry.npmjs.com/</code> as an alias of <code>https://registry.npmjs.org/</code>. Registry requests, credentials, and trusted publishing use the canonical hostname.</p>
</li>
<li>
<p><code>pnx --package=rust@&#x3C;channel> &#x3C;tool></code> runs a tool of that Rust release, for example <code>pnx --package=rust@nightly-2026-01-01 cargo build</code>. pnpm installs the release with the components and targets from <code>rust-toolchain.toml</code> and the target of each <code>--target</code> argument.</p>
</li>
<li>
<p><code>pnpm install</code> now links agent skills for more coding agents. It detects the agent from <code>ANTIGRAVITY_AGENT</code>, <code>COPILOT_AGENT</code>, <code>COPILOT_CLI</code>, <code>CODEX_THREAD_ID</code>, <code>CODEX_SANDBOX</code>, <code>AI_AGENT</code>, and <code>CLAUDE_CODE</code> <a href="https://github.com/pnpm/tasks/issues/116">pnpm/tasks#116</a>.</p>
</li>
<li>
<p>When the registry rejects <code>pnpm stage publish</code>, the error message now starts with "Failed to stage package".</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.11.0]]></title>
      <link>https://pnpm.io/blog/releases/12.11.0</link>
      <guid>https://pnpm.io/blog/releases/12.11.0</guid>
      <pubDate>Fri, 09 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release adds Rust toolchain management, links the agent skills that dependencies ship, adds the permissions setting, and keeps the colors of streamed script output.]]></description>
      <content:encoded><![CDATA[<p>This release adds Rust toolchain management, links the agent skills that dependencies ship, adds the <code>permissions</code> setting, and keeps the colors of streamed script output.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<ul>
<li>
<p><code>pnpm install</code> now links the agent skills that direct dependencies ship under <code>skills/&#x3C;name>/SKILL.md</code> into the project's agent skill directories, such as <code>.claude/skills</code>. A package's skills are linked only after you approve them with <code>pnpm approve</code>. The <code>skills.dirs</code> setting chooses the directories <a href="https://github.com/pnpm/rfcs/pull/35">pnpm/rfcs#35</a>.</p>
<p>Added the <code>permissions</code> setting, which records what each dependency may do. Its <code>build</code> capability works like <code>allowBuilds</code> and takes precedence over it. <code>pnpm approve-builds</code> writes to <code>permissions</code> when <code>pnpm-workspace.yaml</code> already has it, and to <code>allowBuilds</code> otherwise.</p>
<p>Added <code>pnpm permissions</code>, which lists the granted and denied permissions and the packages awaiting approval. <code>pnpm approve</code> reviews build scripts and agent skills in one prompt <a href="https://github.com/pnpm/rfcs/pull/36">pnpm/rfcs#36</a>.</p>
</li>
<li>
<p>pnpm now installs and runs Rust toolchains.</p>
<ul>
<li>With <code>cargo.enabled</code>, <code>pnpm install</code> installs the toolchain named in <code>rust-toolchain.toml</code>. pnpm verifies the release signature, stores the toolchain once per machine, and links it into <code>.pnpm/rust</code>. <code>pnpm run</code> and <code>pnpm exec</code> put its <code>cargo</code> and <code>rustc</code> on the <code>PATH</code>.</li>
<li><code>pnpm add -g rust@&#x3C;channel></code> installs a toolchain globally. The <code>cargo</code> and <code>rustc</code> commands run it outside projects that pin their own. <code>pnpm update -g</code>, <code>pnpm ls -g</code>, and <code>pnpm remove -g</code> manage it like any global package.</li>
<li>In a project, <code>pnpm add rust@&#x3C;channel></code> pins the toolchain in <code>rust-toolchain.toml</code>.</li>
<li><code>pnpm shim add rust</code> adds project-aware shims for <code>cargo</code>, <code>rustc</code>, and the other Rust tools. In a project with a <code>rust-toolchain.toml</code>, they run the toolchain the file names and install it on first use. Elsewhere, the next command of the same name on <code>PATH</code> runs, such as rustup's.</li>
</ul>
</li>
<li>
<p><code>pnpm run</code> and <code>pnpm exec</code> now keep the colors of script output that they print under the project's name, such as with <code>--stream</code>. pnpm sets <code>FORCE_COLOR=1</code> for these scripts when its own output is in color, unless <code>FORCE_COLOR</code> is already set.</p>
<p>Script output is also rendered more cleanly:</p>
<ul>
<li>A line that a progress bar redraws with <code>\r</code> shows only its last state.</li>
<li>Escape codes that move the cursor or clear the screen are dropped.</li>
<li>Long colored lines are cut at the terminal width.</li>
<li><code>pnpm -r run</code> no longer garbles its live output when a script fails while other scripts are still running.</li>
</ul>
</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="installing-packages">Installing packages<a class="hash-link" href="#installing-packages" aria-label="Direct link to Installing packages">#</a></h3>
<ul>
<li>
<p>pnpm no longer panics with "unexpected error when polling the I/O driver" when it runs under QEMU user-mode emulation, such as a <code>linux/amd64</code> container on an Apple Silicon Mac <a href="https://github.com/pnpm/pnpm/issues/16696">#16696</a>.</p>
</li>
<li>
<p><code>pnpm view</code>, <code>pnpm update</code>, and other commands that read registry metadata now work behind proxies that end a response by closing the connection without a TLS <code>close_notify</code> alert <a href="https://github.com/pnpm/pnpm/issues/16704">#16704</a>.</p>
</li>
<li>
<p>pnpm now switches to the version a project pins in <code>packageManager</code> or <code>devEngines.packageManager</code> even when <code>pnpm-workspace.yaml</code> has a setting the running pnpm cannot read, such as a <code>lockfile.includeResolutionSettings</code> section. If pnpm does not switch, it still reports that setting <a href="https://github.com/pnpm/pnpm/issues/16675">#16675</a>.</p>
</li>
<li>
<p>When the <code>pnpm</code> package has to download its native binary on first run, it now uses the registry and credentials from <code>.npmrc</code> and from the <code>npm_config_registry</code> and <code>pnpm_config_registry</code> environment variables. <code>COREPACK_NPM_REGISTRY</code> still takes precedence. A project <code>.npmrc</code> is not read when <code>COREPACK_INTEGRITY_KEYS</code> turns off the signature check <a href="https://github.com/pnpm/pnpm/issues/16655">#16655</a>.</p>
</li>
<li>
<p><code>pnpm install</code> and <code>pnpm add --config</code> now apply <code>minimumReleaseAge</code> when they resolve a config dependency. A config dependency range resolves to the newest version that is old enough, so a later clean <code>pnpm install --frozen-lockfile</code> accepts the lockfile <a href="https://github.com/pnpm/pnpm/issues/16660">#16660</a>.</p>
</li>
<li>
<p><code>pnpm remove</code> with <code>catalogPrune</code> no longer removes catalog entries that <code>pnpm-lock.yaml</code> still records for workspace projects missing from disk. Before, a following frozen install failed with <code>ERR_PNPM_LOCKFILE_CONFIG_MISMATCH</code> <a href="https://github.com/pnpm/pnpm/issues/16679">#16679</a>.</p>
</li>
<li>
<p>With <code>cargo.enabled</code>, <code>pnpm install</code> now writes the source replacement for vendored crates into <code>.pnpm/crates/config.toml</code> and includes it as optional from <code>.cargo/config.toml</code>. A checkout without <code>.pnpm</code> builds with plain Cargo <a href="https://github.com/pnpm/pnpm/issues/16659">#16659</a>.</p>
</li>
<li>
<p>With <code>nodeLinker.type</code> set to <code>loaded</code>, Node.js now stops with <code>ERR_PNPM_LOADER_UNSUPPORTED_NODE</code> when it preloads the store loader on a version the loader cannot serve. The supported versions are <code>^24.18.0 || >=26.2.0</code>. On other versions, CommonJS packages imported from ESM failed with <code>Cannot find module</code> on their first relative <code>require()</code>.</p>
</li>
<li>
<p>On Windows, <code>pnpm install</code> no longer fails with "The filename, directory name, or volume label syntax is incorrect" when a package contains a file whose name is invalid on Windows, such as <code>icon.svg?as=metadata.d.ts</code>. pnpm removes the invalid characters from the name and prints a warning that lists the renamed files.</p>
</li>
<li>
<p>On Windows, hoisting no longer fails intermittently with link errors when a junction is created or replaced concurrently <a href="https://github.com/pnpm/tasks/issues/53">pnpm/tasks#53</a>.</p>
</li>
</ul>
<h3 id="resolving-dependencies">Resolving dependencies<a class="hash-link" href="#resolving-dependencies" aria-label="Direct link to Resolving dependencies">#</a></h3>
<ul>
<li>
<p><code>pnpm install --no-optional</code> now installs the peer dependencies a project declares when <code>autoInstallPeers</code> is on. The lockfile marked such a peer <code>optional: true</code> when another dependency had it as an optional peer.</p>
</li>
<li>
<p><code>pnpm install</code> and <code>pnpm dedupe</code> now link an optional peer to the workspace package that the workspace root depends on when the picked version matches it. They installed the registry package with the same name and version <a href="https://github.com/pnpm/pnpm/issues/16706">#16706</a>.</p>
</li>
<li>
<p>Removal overrides such as <code>"debug>supports-color": "-"</code> now also apply to an optional peer that a package declares only in <code>peerDependenciesMeta</code> <a href="https://github.com/pnpm/pnpm/issues/16681">#16681</a>.</p>
</li>
<li>
<p><code>pnpm add</code> and other installs that re-resolve dependencies now keep the locked <code>devEngines.runtime</code> version while it still satisfies the declared range <a href="https://github.com/pnpm/pnpm/issues/16764">#16764</a>.</p>
</li>
<li>
<p><code>pnpm audit --fix update</code> now updates only the dependencies whose locked version is vulnerable <a href="https://github.com/pnpm/pnpm/issues/14928">#14928</a>.</p>
</li>
<li>
<p><code>pnpm outdated</code> and <code>pnpm update --interactive</code> now apply <code>overrides</code> before they look up the latest version. Before, a dependency overridden to an npm alias was compared with the latest version of the package the override replaces <a href="https://github.com/pnpm/pnpm/issues/16719">#16719</a>.</p>
</li>
</ul>
<h3 id="patched-dependencies">Patched dependencies<a class="hash-link" href="#patched-dependencies" aria-label="Direct link to Patched dependencies">#</a></h3>
<ul>
<li>
<p>Patches saved with CRLF line endings now apply, including a patch that creates or deletes a file. pnpm rejected the git headers of such a patch with <code>ERR_PNPM_INVALID_PATCH</code> and the message <code>invalid file mode: 100644</code> <a href="https://github.com/pnpm/pnpm/issues/16641">#16641</a>.</p>
</li>
<li>
<p>A patch that changes a file's mode, such as adding or removing the executable bit, now applies the new mode on Unix <a href="https://github.com/pnpm/tasks/issues/110">pnpm/tasks#110</a>.</p>
</li>
</ul>
<h3 id="injected-dependencies-and-deploy">Injected dependencies and deploy<a class="hash-link" href="#injected-dependencies-and-deploy" aria-label="Direct link to Injected dependencies and deploy">#</a></h3>
<ul>
<li>
<p>With <code>sharedWorkspaceLockfile: false</code>, an injected workspace package installed in the same run as its dependent now holds only the files its <code>files</code> field selects. The copy also held other files of the project, such as <code>tsconfig.json</code> <a href="https://github.com/pnpm/pnpm/issues/16683">#16683</a>.</p>
</li>
<li>
<p>A script listed in <code>syncInjectedDepsAfterScripts</code> no longer fails when it rewrites <code>package.json</code> while pnpm is copying its edits into the injected copies. The sync after the script now replaces a half-copied manifest.</p>
</li>
<li>
<p><code>pnpm deploy</code> with a shared lockfile no longer fails with <code>ERR_PNPM_LOCKFILE_CONFIG_MISMATCH</code> for <code>settings.dedupeInjectedDeps</code> or <code>settings.dedupePeerDependents</code> when <code>lockfile.includeResolutionSettings</code> is enabled. The deployed lockfile now records both settings as <code>false</code>, the values the deploy installs with.</p>
</li>
<li>
<p><code>pnpm deploy</code> now writes the dependencies in the deployed <code>package.json</code> sorted by name. It also sorts the <code>allowBuilds</code> entries in the deployed <code>pnpm-workspace.yaml</code>. Repeated deploys of the same lockfile now produce identical files <a href="https://github.com/pnpm/pnpm/issues/16687">#16687</a>.</p>
</li>
</ul>
<h3 id="packing-and-publishing">Packing and publishing<a class="hash-link" href="#packing-and-publishing" aria-label="Direct link to Packing and publishing">#</a></h3>
<ul>
<li>
<p><code>pnpm pack</code> and <code>pnpm publish</code> now match <code>.npmignore</code> and <code>.gitignore</code> rules the way npm does. A negation such as <code>!lib/**</code> or <code>!lib/**/!(*.map)</code> re-includes files under a directory that an earlier <code>*</code> rule excluded <a href="https://github.com/pnpm/pnpm/issues/16743">#16743</a>.</p>
</li>
<li>
<p><code>pnpm pack</code> and <code>pnpm publish</code> no longer always include root files that merely start with <code>README</code>, <code>LICENSE</code>, or <code>LICENCE</code>, such as <code>README_INTERNAL.md</code>. Only <code>README</code>, <code>LICENSE</code>, <code>LICENCE</code>, and <code>COPYING</code>, with or without an extension, ship regardless of <code>files</code> and <code>.npmignore</code>, as in npm <a href="https://github.com/pnpm/pnpm/issues/16753">#16753</a>.</p>
</li>
<li>
<p><code>pnpm publish --provenance=false</code> and <code>--no-provenance</code> now turn off provenance under trusted publishing, so a package can be published from a self-hosted runner. Setting <code>provenance: false</code> in <code>pnpm-workspace.yaml</code> does the same <a href="https://github.com/pnpm/pnpm/issues/16721">#16721</a>.</p>
</li>
</ul>
<h3 id="speed-and-resource-use">Speed and resource use<a class="hash-link" href="#speed-and-resource-use" aria-label="Direct link to Speed and resource use">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> hardlinks files from a group-writable or world-writable store again. pnpm copied every file from such a store into <code>node_modules</code> <a href="https://github.com/pnpm/pnpm/issues/16677">#16677</a>.</p>
</li>
<li>
<p>A repeat <code>pnpm install</code> no longer imports patched packages and packages with build scripts again when nothing changed. Their builds no longer run again either. This happened when <code>recursiveInstall</code> was <code>false</code> or the project had a <code>file:</code> dependency <a href="https://github.com/pnpm/pnpm/issues/16705">#16705</a>.</p>
</li>
<li>
<p>Verifying the lockfile against <code>trustPolicy</code> and <code>minimumReleaseAge</code> uses less memory. pnpm no longer keeps every published version's manifest of each checked package in memory until the install ends <a href="https://github.com/pnpm/pnpm/issues/16656">#16656</a>.</p>
</li>
<li>
<p><code>pnpm rebuild &#x3C;pkg></code> and <code>pnpm rebuild --pending</code> no longer read the manifest of every installed package to find build scripts. Only the selected packages are inspected and built. On a large <code>node_modules</code> served lazily, such as over a network or FUSE mount, this turned a rebuild of a few packages into a fetch of every package.</p>
</li>
<li>
<p><code>pnpm rebuild</code> no longer removes and recreates <code>node_modules</code> when the settings recorded in <code>node_modules/.modules.yaml</code> differ from the current configuration. The rebuild runs the build scripts against the installed packages as they are.</p>
</li>
<li>
<p><code>pnpm store prune</code> now compacts the store's <code>index.db</code> after removing package entries, so the file shrinks again <a href="https://github.com/pnpm/pnpm/issues/16717">#16717</a>.</p>
</li>
</ul>
<h3 id="registry-commands">Registry commands<a class="hash-link" href="#registry-commands" aria-label="Direct link to Registry commands">#</a></h3>
<ul>
<li>
<p><code>pnpm whoami</code>, <code>pnpm bugs</code>, <code>pnpm docs</code>, <code>pnpm repo</code>, <code>pnpm star</code>, <code>pnpm unstar</code>, and <code>pnpm stars</code> now honor the <code>--registry</code> option.</p>
</li>
<li>
<p><code>--registry</code> now takes precedence over a scope's configured registry for scoped packages in <code>pnpm access</code>, <code>pnpm view</code>, <code>pnpm repo</code>, <code>pnpm star</code>, <code>pnpm unstar</code>, <code>pnpm owner</code>, <code>pnpm deprecate</code>, <code>pnpm undeprecate</code>, <code>pnpm unpublish</code>, <code>pnpm dist-tag</code>, <code>pnpm team</code>, and <code>pnpm stage</code>. Without <code>--registry</code>, <code>pnpm access</code> now sends a scoped package or scope to the registry configured for that scope.</p>
</li>
<li>
<p>Registry commands now keep the path of a registry URL such as <code>https://example.com/npm/</code> when they build request URLs. This applies to <code>pnpm access</code>, <code>pnpm owner</code>, <code>pnpm ping</code>, <code>pnpm search</code>, <code>pnpm star</code>, <code>pnpm stars</code>, <code>pnpm team</code>, <code>pnpm unstar</code>, and <code>pnpm whoami</code>.</p>
</li>
<li>
<p><code>pnpm view</code> now honors the network retry settings.</p>
</li>
<li>
<p><code>pnpm repo</code> now fetches the repository URLs of several packages in parallel.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.10.1]]></title>
      <link>https://pnpm.io/blog/releases/12.10.1</link>
      <guid>https://pnpm.io/blog/releases/12.10.1</guid>
      <pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release fixes pnpm install failures after an overrides change and on a filtered frozen install with catalogPrune. It also fixes several bugs in the experimental nodeLinker.type: loaded, which now keeps its generated files in node_modules.]]></description>
      <content:encoded><![CDATA[<p>This release fixes <code>pnpm install</code> failures after an <code>overrides</code> change and on a filtered frozen install with <code>catalogPrune</code>. It also fixes several bugs in the experimental <code>nodeLinker.type: loaded</code>, which now keeps its generated files in <code>node_modules</code>.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p>With <code>nodeLinker.type: loaded</code>, pnpm now writes its generated files to <code>node_modules</code>, which projects already ignore in git. The store manifest and loader are <code>node_modules/.pnpm/.store-manifest.json</code> and <code>node_modules/.pnpm/.store-loader.mjs</code>. Bin shims are in <code>node_modules/.bin</code>.</p>
<p>Earlier versions wrote <code>.pnpm-store.json</code> and <code>.pnpm-store-loader.mjs</code> to the project root, and a <code>.pnpm</code> directory to the root and to each workspace package. Delete them after reinstalling.</p>
</li>
<li>
<p>With <code>nodeLinker.type: loaded</code>, packages that ship their own <code>node_modules</code> directory, such as <code>npm</code> with its bundled dependencies, now load from the store. Before, one such package in the install stopped every Node.js process from starting.</p>
</li>
<li>
<p>With <code>nodeLinker.type: loaded</code>, scripts can now run a Node.js runtime installed through <code>devEngines.runtime</code>. Before, every script that called <code>node</code> re-ran its own shim until it failed with "Argument list too long".</p>
</li>
<li>
<p>With <code>nodeLinker.type: loaded</code>, Node.js processes start faster. In a project with 13,000 stored files, the startup overhead per process dropped from 67 ms to 18 ms.</p>
</li>
<li>
<p><code>pnpm install</code> no longer fails with <code>ERR_PNPM_NO_MATCHING_VERSION</code> after a change to <code>overrides</code> when the lockfile resolves an optional peer dependency to an npm alias of another package <a href="https://github.com/pnpm/pnpm/issues/16654">#16654</a>.</p>
</li>
<li>
<p>A frozen install with <code>catalogPrune</code> no longer removes catalog entries that <code>pnpm-lock.yaml</code> still records. Before, <code>pnpm install --frozen-lockfile --filter</code> failed with <code>ERR_PNPM_LOCKFILE_CONFIG_MISMATCH</code> when some workspace projects were missing from disk <a href="https://github.com/pnpm/pnpm/issues/16638">#16638</a>.</p>
</li>
<li>
<p><code>pnpm install --fix-lockfile</code> no longer removes the <code>deprecated</code> and <code>hasBin</code> fields from lockfile entries <a href="https://github.com/pnpm/pnpm/issues/6600">#6600</a>.</p>
</li>
<li>
<p>With <code>enableGlobalVirtualStore</code>, an install that updates <code>node_modules</code> now repairs a package in the global virtual store that an interrupted install left without some of its dependency links or package files. Before, such an install kept the incomplete package if the project's <code>node_modules</code> already recorded it <a href="https://github.com/pnpm/pnpm/issues/16642">#16642</a>.</p>
</li>
<li>
<p><code>pnpm install</code> now skips the Cargo and Python projects inside a nested directory that has its own <code>pnpm-workspace.yaml</code> or <code>.git</code> directory, such as a git worktree of the same workspace or a separate clone.</p>
</li>
<li>
<p>The <code>Request took</code> warning for package metadata now starts timing when pnpm sends the request. Before, it also counted the time the request waited for a free request slot, so large installs printed it for requests the registry answered quickly.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.10.0]]></title>
      <link>https://pnpm.io/blog/releases/12.10.0</link>
      <guid>https://pnpm.io/blog/releases/12.10.0</guid>
      <pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release adds an experimental loaded node linker, lets pnpm-lock.yaml record resolution settings, and reads cached registry metadata faster. It also carries several security fixes, including one that stops a dependency version from writing files outside the global virtual store.]]></description>
      <content:encoded><![CDATA[<p>This release adds an experimental <code>loaded</code> node linker, lets <code>pnpm-lock.yaml</code> record resolution settings, and reads cached registry metadata faster. It also carries several security fixes, including one that stops a dependency version from writing files outside the global virtual store.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<ul>
<li>
<p>Added experimental <code>nodeLinker: { type: loaded }</code> installation. Compatible dependencies load directly from the content-addressable store through an automatically registered Node.js loader. <code>nodeLinker.excluded</code> selects packages and their dependency trees to install in the global virtual store.</p>
</li>
<li>
<p><code>lockfile.includeResolutionSettings: true</code> makes <code>pnpm-lock.yaml</code> record <code>autoDedupe</code>, <code>dedupeInjectedDeps</code>, <code>dedupePeerDependents</code> and <code>linkWorkspacePackages</code>. Installs then treat a lockfile that records other values as outdated. A lockfile that records <code>autoDedupe</code> is reused by later installs on any machine, so <code>pnpm run</code> after <code>pnpm install --frozen-lockfile</code> no longer starts another install <a href="https://github.com/pnpm/pnpm/issues/16583">#16583</a>.</p>
</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="security">Security<a class="hash-link" href="#security" aria-label="Direct link to Security">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> now prevents dependency versions with path traversal from writing files outside the global virtual store.</p>
</li>
<li>
<p>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 <code>version+integrity</code>.</p>
</li>
<li>
<p>Lockfile verification now checks the tarballs inside a <code>variations</code> resolution against the registry. A <code>name@version</code> lockfile entry with an empty <code>variations</code> resolution is now rejected.</p>
</li>
<li>
<p><code>pnpm audit signatures</code> now verifies signatures against the integrity recorded in the lockfile. Packages without a recorded integrity cannot pass signature verification.</p>
</li>
<li>
<p><code>pnpm install</code> and <code>pnpm publish</code> now reject archive metadata larger than 64 MiB before reading it into memory. Publishing a pre-built tarball also rejects manifests and README files larger than 64 MiB.</p>
</li>
<li>
<p>Two URL or local path dependencies no longer share a virtual store directory when one URL has <code>+</code>, <code>#</code>, <code>:</code>, or <code>?</code> where the other has <code>/</code>. Such dependencies, including git dependencies pinned with <code>#</code>, now get a hash suffix on their directory name.</p>
</li>
<li>
<p>The warning about an ignored project <code>.npmrc</code> registry setting no longer prints the username and password of a URL-scoped key such as <code>//user:password@registry.example.com/${PATH}/:_authToken</code>.</p>
</li>
</ul>
<h3 id="installing-and-resolving-dependencies">Installing and resolving dependencies<a class="hash-link" href="#installing-and-resolving-dependencies" aria-label="Direct link to Installing and resolving dependencies">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> now fails with <code>ERR_PNPM_UNSUPPORTED_PROTOCOL</code> when a dependency uses a specifier with a protocol pnpm does not support, such as Yarn's <code>patch:</code>. On Windows, such a specifier failed with <code>os error 123</code>. On other platforms, pnpm linked it to a directory that does not exist. Reading a <code>package.json</code> that fails now names the file <a href="https://github.com/pnpm/pnpm/issues/16590">#16590</a>.</p>
</li>
<li>
<p><code>pnpm install</code> now fails with <code>ERR_PNPM_PACKAGE_MANIFEST_INVALID_ATTRIBUTE</code> when a project declares a dependency whose specifier is not a string, such as <code>"is-positive": 42</code>. Before, the dependency was silently left out of the lockfile. A <code>readPackage</code> hook can still correct the specifier.</p>
</li>
<li>
<p>Fixed <code>pnpm install</code> failing with <code>ERR_PNPM_CMD_SHIM_RESOLVE_PATH</code> when an executable's parent directory contains a dangling symlink.</p>
</li>
<li>
<p><code>pnpm install --frozen-lockfile</code> no longer fails with <code>ERR_PNPM_RESOLUTION_SHAPE_MISMATCH</code> when a <code>name@version</code> lockfile entry has a resolution served by a custom fetcher <a href="https://github.com/pnpm/tasks/issues/108">pnpm/tasks#108</a>.</p>
</li>
<li>
<p><code>pnpm install --fix-lockfile</code> repairs a lockfile whose importer references a package that has no snapshot entry, as left by a badly merged lockfile. It failed with <code>ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY</code> since 12.8.0 <a href="https://github.com/pnpm/pnpm/issues/16618">#16618</a>.</p>
</li>
<li>
<p>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, <code>pnpm install</code> and <code>pnpm dedupe</code> kept the newer locked version of the peer, so the lockfile held two copies of it, for example two copies of <code>vue</code> <a href="https://github.com/pnpm/tasks/issues/61">pnpm/tasks#61</a>.</p>
</li>
<li>
<p><code>pnpm dedupe</code> now reads registry metadata for a dependency pinned to an exact version, as <code>pnpm install</code> does. If the registry metadata disagreed with the package's <code>package.json</code>, the lockfile it wrote depended on whether <code>minimumReleaseAge</code> was set <a href="https://github.com/pnpm/pnpm/issues/16615">#16615</a>.</p>
</li>
</ul>
<h3 id="speed-and-size">Speed and size<a class="hash-link" href="#speed-and-size" aria-label="Direct link to Speed and size">#</a></h3>
<ul>
<li>
<p>Dependency resolution reads cached registry metadata faster. The metadata cache moved to <code>&#x3C;cache-dir>/v12/</code>, so the first install after upgrading downloads registry metadata again. A damaged cache entry is downloaded again, or reported as an error when <code>--offline</code> is set. <code>pnpm cache prune</code> also removes the metadata cache that older pnpm versions wrote under <code>&#x3C;cache-dir>/v11/</code> <a href="https://github.com/pnpm/pnpm/pull/13512">#13512</a>.</p>
</li>
<li>
<p>Package metadata requests no longer wait behind queued tarball downloads when <code>maxSockets</code> or a proxy limits the connections to a registry. Large installs resolve faster and print fewer <code>Request took</code> warnings.</p>
</li>
<li>
<p>Sped up installs in large workspaces on macOS when the dependency links already exist. pnpm now keeps a link that already points at the right package without trying to create it first. Relinking the direct dependencies of 1,000 workspace projects took 45 ms, down from 116 ms <a href="https://github.com/pnpm/tasks/issues/65">pnpm/tasks#65</a>.</p>
</li>
<li>
<p>Commands in a project that pins a different pnpm version start about 13 ms faster on macOS. pnpm now runs the pinned version's binary directly, without the shell script in front of it <a href="https://github.com/pnpm/tasks/issues/66">pnpm/tasks#66</a>.</p>
</li>
<li>
<p>The pnpm binary is about 0.9 MB smaller, and the arm64 Linux binary is about 1 MB smaller still.</p>
</li>
</ul>
<h3 id="running-scripts-and-commands">Running scripts and commands<a class="hash-link" href="#running-scripts-and-commands" aria-label="Direct link to Running scripts and commands">#</a></h3>
<ul>
<li>
<p><code>pnpm run</code> no longer prints <code>[ELIFECYCLE] Command failed ...</code> after Ctrl+C ends the script. pnpm still exits the way the script's shell did: on Windows with the shell's exit code (<code>cmd</code> reports <code>-1073741510</code>, PowerShell <code>1</code>), on Unix by re-raising <code>SIGINT</code> <a href="https://github.com/pnpm/pnpm/issues/16579">#16579</a>.</p>
</li>
<li>
<p><code>pnpm run "/&#x3C;regex>/"</code> now accepts JavaScript regular expression syntax such as lookahead and lookbehind. A selector like <code>"/^hello:(?!b).*$/"</code> failed with <code>ERR_PNPM_NO_SCRIPT</code> <a href="https://github.com/pnpm/pnpm/issues/16604">#16604</a>.</p>
</li>
<li>
<p><code>pnpm run</code> and <code>pnpm exec</code> now forward <code>--config.*</code> command-line flags to the install started by <code>verifyDepsBeforeRun</code> <a href="https://github.com/pnpm/tasks/issues/60">pnpm/tasks#60</a>.</p>
</li>
<li>
<p>On Windows, a process started by <code>pnpm run</code> or <code>pnpm exec</code> can again start a child with <code>CREATE_BREAKAWAY_FROM_JOB</code>. That child keeps running after pnpm exits, even if the command fails <a href="https://github.com/pnpm/pnpm/issues/16628">#16628</a>.</p>
</li>
<li>
<p>Empty <code>nodeOptions</code> values from command-line flags and environment variables now override lower-priority settings. Scripts retain <code>NODE_OPTIONS</code> from the parent environment or <code>extraEnv</code> when <code>nodeOptions</code> is empty.</p>
</li>
<li>
<p>pnpm now reads the <code>failIfNoMatch</code> setting from <code>pnpm-workspace.yaml</code>, so a filter that matches no workspace project exits with code 1 when the setting is <code>true</code>. The new <code>--no-fail-if-no-match</code> flag turns the setting off for one command <a href="https://github.com/pnpm/pnpm/issues/16577">#16577</a>.</p>
</li>
</ul>
<h3 id="configuration-setup-and-pnpm-versions">Configuration, setup, and pnpm versions<a class="hash-link" href="#configuration-setup-and-pnpm-versions" aria-label="Direct link to Configuration, setup, and pnpm versions">#</a></h3>
<ul>
<li>
<p><code>pnpm config get --global</code> and <code>pnpm config list --global</code> now show only the global configuration, also when run inside a project. Settings from the project's <code>pnpm-workspace.yaml</code> and <code>.npmrc</code> were included before. The same applies to <code>--location=global</code> <a href="https://github.com/pnpm/pnpm/issues/16598">#16598</a>.</p>
</li>
<li>
<p>pnpm now prints config warnings, such as an unset environment variable in <code>.npmrc</code>, when loading the config fails.</p>
</li>
<li>
<p>pnpm 11 releases older than 11.28.4 can run pnpm 12 again when the <code>packageManager</code> field pins it. Since 12.9.0 they failed with <code>SyntaxError: Invalid or unexpected token</code> <a href="https://github.com/pnpm/pnpm/issues/16594">#16594</a>.</p>
</li>
<li>
<p>On Windows, <code>pnpm self-update</code> no longer runs the update a second time when it replaces a <code>pnpm.cmd</code> linked by pnpm 12.8 or older. cmd.exe read on in the replaced <code>pnpm.cmd</code>, printed an error about a command that is not recognized, and ran the new pnpm once more <a href="https://github.com/pnpm/pnpm/issues/16573">#16573</a>.</p>
</li>
<li>
<p><code>pnpm setup</code> now puts <code>$PNPM_HOME/bin</code> first on <code>PATH</code> in login shells that inherited it further down, such as the VS Code terminal on macOS. Before, another <code>node</code> took precedence over the one installed by <code>pnpm runtime set node -g</code>. Run <code>pnpm setup</code> again to update the block in your shell config <a href="https://github.com/pnpm/pnpm/issues/16635">#16635</a>.</p>
</li>
<li>
<p><code>pnpm setup</code> now names the shell config file even if it is already up to date <a href="https://github.com/pnpm/pnpm/issues/16608">#16608</a>.</p>
</li>
</ul>
<h3 id="updating-auditing-and-publishing">Updating, auditing, and publishing<a class="hash-link" href="#updating-auditing-and-publishing" aria-label="Direct link to Updating, auditing, and publishing">#</a></h3>
<ul>
<li>
<p><code>pnpm update --latest</code> now applies the <code>savePrefix</code> setting when it rewrites a dependency whose range has no operator of its own, such as <code>&#x3C;2.0.0</code>.</p>
</li>
<li>
<p>The interactive <code>pnpm audit --fix</code> picker now shows each patched version with the <code>saveExact</code> and <code>savePrefix</code> style that the override is written with <a href="https://github.com/pnpm/pnpm/issues/13209">#13209</a>.</p>
</li>
<li>
<p><code>pnpm unpublish &#x3C;pkg>@&#x3C;version></code> 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 <a href="https://github.com/pnpm/pnpm/issues/16568">#16568</a>. It also no longer mistakes a sibling path such as <code>/npm-mirror/</code> for the registry path <code>/npm/</code> <a href="https://github.com/pnpm/tasks/issues/94">pnpm/tasks#94</a>.</p>
</li>
</ul>
<h3 id="output-and-messages">Output and messages<a class="hash-link" href="#output-and-messages" aria-label="Direct link to Output and messages">#</a></h3>
<ul>
<li>
<p>A warning about a project's <code>devEngines</code> or <code>packageManager</code> pin is now printed to stderr. A command such as <code>pnpm cache path</code> or <code>pnpm list --json</code> keeps only its own output on stdout <a href="https://github.com/pnpm/pnpm/issues/16584">#16584</a>.</p>
</li>
<li>
<p><code>pnpm list</code> now reports the correct package paths when <code>nodeLinker</code> is <code>hoisted</code> <a href="https://github.com/pnpm/pnpm/issues/9593">#9593</a>.</p>
</li>
<li>
<p>Resolution errors now name the failing dependency and its parent packages. Fatal errors appear as structured error records with their error codes when using <code>--reporter=ndjson</code>.</p>
</li>
<li>
<p>The error for an invalid git repository in the lockfile now has the code <code>ERR_PNPM_INVALID_GIT_REPOSITORY</code>. Its message now lists every rejected form of the value.</p>
</li>
<li>
<p><code>pnpm runtime --help</code> and <code>pnpm help runtime</code> now name the <code>set</code> subcommand and the runtimes it accepts <a href="https://github.com/pnpm/pnpm/issues/16580">#16580</a>.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 11.28.5]]></title>
      <link>https://pnpm.io/blog/releases/11.28.5</link>
      <guid>https://pnpm.io/blog/releases/11.28.5</guid>
      <pubDate>Tue, 06 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release reads cached registry metadata faster and makes pnpm config get --global ignore project settings. It also carries several security fixes for package archives, git dependencies, and config dependencies.]]></description>
      <content:encoded><![CDATA[<p>This release reads cached registry metadata faster and makes <code>pnpm config get --global</code> ignore project settings. It also carries several security fixes for package archives, git dependencies, and config dependencies.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="security">Security<a class="hash-link" href="#security" aria-label="Direct link to Security">#</a></h3>
<ul>
<li>
<p>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 <code>version+integrity</code>.</p>
</li>
<li>
<p>Lockfile verification now checks the tarballs inside a <code>variations</code> resolution against the registry. A <code>name@version</code> lockfile entry with an empty <code>variations</code> resolution is now rejected.</p>
</li>
<li>
<p><code>pnpm audit signatures</code> now verifies signatures against the integrity recorded in the lockfile. Packages without a recorded integrity cannot pass signature verification.</p>
</li>
<li>
<p>pnpm now rejects a git dependency whose lockfile repository is empty, begins with <code>-</code>, or contains a null byte. Git can no longer read such a value as a command-line option <a href="https://github.com/pnpm/tasks/issues/84">pnpm/tasks#84</a>.</p>
</li>
<li>
<p>A git dependency with a <code>#path:</code> subpath can no longer reach files outside the repository through a symlink in the subpath.</p>
</li>
<li>
<p><code>pnpm pack</code>, <code>pnpm publish</code>, and installs of git and local directory dependencies now leave out files that a directory symlink or a <code>bundleDependencies</code> entry points to outside the package directory <a href="https://github.com/pnpm/tasks/issues/83">pnpm/tasks#83</a> <a href="https://github.com/pnpm/tasks/issues/93">pnpm/tasks#93</a>.</p>
</li>
<li>
<p><code>pnpm deploy</code> with <code>deployAllFiles</code> now rejects symlinks that point outside the package directory. Local package installs with this setting apply the same check.</p>
</li>
<li>
<p>pnpm no longer hangs on a package archive with a negative PAX record length or an entry of 4 GiB or more <a href="https://github.com/pnpm/tasks/issues/78">pnpm/tasks#78</a> <a href="https://github.com/pnpm/tasks/issues/79">pnpm/tasks#79</a>.</p>
</li>
<li>
<p>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. <code>pnpm publish</code> also rejects manifests and README files larger than 64 MiB in pre-built tarballs before reading them into memory.</p>
</li>
<li>
<p>Two URL or local path dependencies no longer share a virtual store directory when one URL has <code>+</code>, <code>#</code>, <code>:</code>, or <code>?</code> where the other has <code>/</code>. Such dependencies, including git dependencies pinned with <code>#</code>, now get a hash suffix on their directory name.</p>
</li>
<li>
<p><code>pnpm licenses</code> now removes terminal control characters from package metadata in table output.</p>
</li>
<li>
<p>The warnings about ignored project <code>.npmrc</code> registry and auth settings no longer print the username and password of a URL-scoped key such as <code>//user:password@registry.example.com/:_authToken</code>.</p>
</li>
</ul>
<h3 id="installing-and-resolving-dependencies">Installing and resolving dependencies<a class="hash-link" href="#installing-and-resolving-dependencies" aria-label="Direct link to Installing and resolving dependencies">#</a></h3>
<ul>
<li>
<p>Dependency resolution reads cached registry metadata faster. The metadata cache moved to <code>&#x3C;cache-dir>/v12/</code>, so the first install after upgrading downloads registry metadata again. A damaged cache entry is downloaded again, or reported as an error when <code>--offline</code> is set <a href="https://github.com/pnpm/pnpm/pull/13512">#13512</a>.</p>
</li>
<li>
<p><code>pnpm install</code> now fails with <code>ERR_PNPM_UNSUPPORTED_PROTOCOL</code> when a dependency uses a specifier with a protocol pnpm does not support, such as Yarn's <code>patch:</code>. pnpm linked such a dependency to a directory that does not exist <a href="https://github.com/pnpm/pnpm/issues/16590">#16590</a>.</p>
</li>
<li>
<p>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, <code>pnpm install</code> and <code>pnpm dedupe</code> kept the newer locked version of the peer, so the lockfile held two copies of it, for example two copies of <code>vue</code> <a href="https://github.com/pnpm/tasks/issues/61">pnpm/tasks#61</a>.</p>
</li>
<li>
<p><code>pnpm add</code> and <code>pnpm install</code> now keep the peer dependencies that <code>pnpm-lock.yaml</code> records for a package they did not update. A registry whose metadata disagrees with the package's <code>package.json</code>, for example by omitting <code>peerDependenciesMeta</code>, made <code>pnpm add</code> and <code>pnpm dedupe</code> write different lockfiles, so <code>pnpm dedupe --check</code> failed after <code>pnpm add</code> <a href="https://github.com/pnpm/pnpm/issues/16615">#16615</a>.</p>
</li>
</ul>
<h3 id="configuration">Configuration<a class="hash-link" href="#configuration" aria-label="Direct link to Configuration">#</a></h3>
<ul>
<li>
<p><code>pnpm config get</code> and <code>pnpm config list</code> with <code>--global</code> or <code>--location=global</code> now show only the global configuration. Both flags included the project's <code>.npmrc</code> before. <code>--location=global</code> also included the project's <code>pnpm-workspace.yaml</code>. <code>pnpm config get --global</code> failed when the global bin directory was not in PATH <a href="https://github.com/pnpm/pnpm/issues/16598">#16598</a>.</p>
</li>
<li>
<p>pnpm now prints config warnings, such as an unset environment variable in <code>.npmrc</code>, when loading the config fails.</p>
</li>
<li>
<p>pnpm now fails when <code>httpProxy</code> or <code>httpsProxy</code> in <code>pnpm-workspace.yaml</code> or the global configuration is not a string.</p>
</li>
<li>
<p><code>pnpm dlx</code> now uses the <code>release</code> entry of <code>nodeDownloadMirrors</code> from the workspace configuration when downloading Node.js runtimes <a href="https://github.com/pnpm/pnpm/issues/11281">#11281</a>. Mirrors for other channels, such as <code>rc</code> and <code>nightly</code>, still apply only from the global configuration.</p>
</li>
</ul>
<h3 id="commands">Commands<a class="hash-link" href="#commands" aria-label="Direct link to Commands">#</a></h3>
<ul>
<li>
<p><code>pnpm run</code> and <code>pnpm exec</code> now forward <code>--config.*</code> command-line flags to the install started by <code>verifyDepsBeforeRun</code> <a href="https://github.com/pnpm/tasks/issues/60">pnpm/tasks#60</a>.</p>
</li>
<li>
<p><code>pnpm dlx</code> with <code>--package</code> but no command now fails with <code>'pnpm dlx' requires a command to run</code>. Before, it installed the package and then crashed trying to run an empty command.</p>
</li>
<li>
<p><code>pnpm unpublish &#x3C;pkg>@&#x3C;version></code> 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 <a href="https://github.com/pnpm/pnpm/issues/16568">#16568</a>. It also no longer mistakes a sibling path such as <code>/npm-mirror/</code> for the registry path <code>/npm/</code> <a href="https://github.com/pnpm/tasks/issues/94">pnpm/tasks#94</a>.</p>
</li>
<li>
<p>The interactive <code>pnpm audit --fix</code> picker now shows each patched version with the <code>saveExact</code> and <code>savePrefix</code> style that the override is written with <a href="https://github.com/pnpm/pnpm/issues/13209">#13209</a>.</p>
</li>
<li>
<p><code>pnpm list</code> now reports the correct package paths when <code>nodeLinker</code> is <code>hoisted</code> <a href="https://github.com/pnpm/pnpm/issues/9593">#9593</a>.</p>
</li>
<li>
<p><code>pnpm setup</code> now puts <code>$PNPM_HOME/bin</code> first on <code>PATH</code> in login shells that inherited it further down, such as the VS Code terminal on macOS. Before, another <code>node</code> took precedence over the one installed by <code>pnpm runtime set node -g</code>. Run <code>pnpm setup</code> again to update the block in your shell config <a href="https://github.com/pnpm/pnpm/issues/16635">#16635</a>.</p>
</li>
<li>
<p><code>pnpm setup</code> now names the shell config file even if it is already up to date <a href="https://github.com/pnpm/pnpm/issues/16608">#16608</a>.</p>
</li>
</ul>
<h3 id="output-and-messages">Output and messages<a class="hash-link" href="#output-and-messages" aria-label="Direct link to Output and messages">#</a></h3>
<ul>
<li>
<p>A warning about a project's <code>devEngines</code> or <code>packageManager</code> pin is now printed to stderr. A command such as <code>pnpm cache path</code> or <code>pnpm list --json</code> keeps only its own output on stdout <a href="https://github.com/pnpm/pnpm/issues/16584">#16584</a>.</p>
</li>
<li>
<p>The warning for a non-root <code>resolutions</code> field now points at the <code>overrides</code> field in <code>pnpm-workspace.yaml</code> <a href="https://github.com/pnpm/pnpm/issues/11757">#11757</a>.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.9.1]]></title>
      <link>https://pnpm.io/blog/releases/12.9.1</link>
      <guid>https://pnpm.io/blog/releases/12.9.1</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release moves the WebContainer build into a separate @pnpm/wasm package, shrinks the pnpm package back to about 4 MB, and fixes pnpm publish with provenance from GitLab CI.]]></description>
      <content:encoded><![CDATA[<p>This release moves the WebContainer build into a separate <code>@pnpm/wasm</code> package, shrinks the <code>pnpm</code> package back to about 4 MB, and fixes <code>pnpm publish</code> with provenance from GitLab CI.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p>The WebAssembly build for StackBlitz WebContainers now ships as a separate <code>@pnpm/wasm</code> package. The <code>pnpm</code> and <code>@pnpm/exe</code> packages no longer include it, which brings their unpacked size back from about 55 MB to about 4 MB. In a WebContainer, install <code>@pnpm/wasm</code> with npm to get the <code>pnpm</code> command.</p>
</li>
<li>
<p><code>pnpm publish</code> with 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 in <code>invocation.parameters</code>, as npm does <a href="https://github.com/pnpm/pnpm/issues/16551">#16551</a>.</p>
</li>
<li>
<p><code>pnpm audit signatures</code> now uses the TLS settings of the redirect target when a registry redirects its signing-keys request, for example to registry.npmjs.org. A <code>cafile</code> scoped to a private registry no longer makes the redirected request fail <a href="https://github.com/pnpm/pnpm/issues/16541">#16541</a>.</p>
</li>
<li>
<p>Fixed <code>pnpm install --frozen-lockfile</code> rejecting an up-to-date lockfile when an injected workspace package uses a catalog entry in <code>peerDependencies</code> <a href="https://github.com/pnpm/pnpm/issues/16557">#16557</a>.</p>
</li>
<li>
<p>The <code>[&#x3C;since>]</code> filter selector works again with Git 2.24 through 2.27 <a href="https://github.com/pnpm/pnpm/issues/16561">#16561</a>. With Git older than 2.24, the selector now fails with an error that names the required Git version.</p>
<p>It also detects changes in projects whose directory names contain non-ASCII characters. Such a change used to be credited to the parent project. <code>changedFilesIgnorePattern</code> and <code>testPattern</code> now match changed files whose names contain non-ASCII characters.</p>
</li>
<li>
<p>The <code>pnpm</code> executable is about 10% smaller. On macOS arm64 it went from 45.1 MB to 40.3 MB.</p>
</li>
<li>
<p>Sped up trust downgrade checks for packages with long release histories.</p>
</li>
<li>
<p>With <code>optimisticRepeatInstall: false</code>, <code>pnpm install</code> now runs the projects' own lifecycle scripts, such as <code>prepare</code>, even when <code>node_modules</code> is already up to date <a href="https://github.com/pnpm/pnpm/issues/16545">#16545</a>.</p>
</li>
<li>
<p><code>pnpm self-update</code> now fails for Homebrew-installed pnpm and prints the <code>brew upgrade</code> command for the installed formula, such as <code>brew upgrade pnpm</code> or <code>brew upgrade pnpm@11</code>. It used to install a second copy of pnpm that the Homebrew one kept shadowing <a href="https://github.com/pnpm/pnpm/issues/16547">#16547</a>.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 11.28.4]]></title>
      <link>https://pnpm.io/blog/releases/11.28.4</link>
      <guid>https://pnpm.io/blog/releases/11.28.4</guid>
      <pubDate>Sat, 03 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 11.28.4 fixes two ways credentials could leak, makes pnpm install --frozen-lockfile accept several lockfiles it rejected, warns when an optional dependency cannot be fetched, and stops pnpm self-update from installing a second pnpm next to a Homebrew one.]]></description>
      <content:encoded><![CDATA[<p>pnpm 11.28.4 fixes two ways credentials could leak, makes <code>pnpm install --frozen-lockfile</code> accept several lockfiles it rejected, warns when an optional dependency cannot be fetched, and stops <code>pnpm self-update</code> from installing a second pnpm next to a Homebrew one.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li>
<p><code>pnpm login</code> no longer forwards credentials in its request body to another origin during redirects.</p>
</li>
<li>
<p>The error for a tarball that fails its integrity check no longer prints credentials, query strings, or fragments from the tarball URL.</p>
</li>
</ul>
<h3 id="installing-packages">Installing packages<a class="hash-link" href="#installing-packages" aria-label="Direct link to Installing packages">#</a></h3>
<ul>
<li>
<p><code>pnpm install --frozen-lockfile</code> now succeeds in a project with no dependencies when <code>pnpm-lock.yaml</code> records only the pinned pnpm version. Other commands write such a lockfile when they run before the first install. A lockfile missing the <code>---</code> line after that section is accepted too <a href="https://github.com/pnpm/pnpm/issues/16477">#16477</a>.</p>
</li>
<li>
<p><code>pnpm install --frozen-lockfile</code> again succeeds when a workspace project recorded in <code>pnpm-lock.yaml</code> has no directory, such as a project left out of a Docker build context. It still fails if the project's directory exists without a <code>package.json</code> <a href="https://github.com/pnpm/pnpm/issues/16453">#16453</a>.</p>
</li>
<li>
<p><code>pnpm install --frozen-lockfile</code> no longer fails with <code>ERR_PNPM_OUTDATED_LOCKFILE</code> for a workspace project that declares <code>dependenciesMeta</code> and whose dependencies are all workspace links. pnpm now records that project's <code>dependenciesMeta</code> in <code>pnpm-lock.yaml</code> <a href="https://github.com/pnpm/pnpm/issues/16457">#16457</a>.</p>
</li>
<li>
<p>Fixed frozen installs replacing a hoisted dependency with a workspace package of the same name. A later <code>pnpm dedupe</code> then removed the hoisted link <a href="https://github.com/pnpm/pnpm/issues/16485">#16485</a>.</p>
</li>
<li>
<p>With <code>enableGlobalVirtualStore</code> on, scripts can run entry points that a CommonJS require hook loads again, such as <code>ts-node index.ts</code>. They failed with <code>ERR_UNKNOWN_FILE_EXTENSION</code> on Node.js versions without built-in TypeScript support <a href="https://github.com/pnpm/pnpm/issues/16436">#16436</a>.</p>
</li>
</ul>
<h3 id="optional-dependencies">Optional dependencies<a class="hash-link" href="#optional-dependencies" aria-label="Direct link to Optional dependencies">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> now prints a warning with the error when an optional dependency cannot be fetched and is skipped. The skipped package is no longer linked into <code>node_modules</code> as a broken symlink or listed among the added dependencies. The <code>pnpm:skipped-optional-dependency</code> log reports the skip with the <code>fetch_failure</code> reason <a href="https://github.com/pnpm/pnpm/issues/16514">#16514</a>.</p>
</li>
<li>
<p>When an optional dependency fails to build, pnpm now removes its link from <code>node_modules</code>. A repeat <code>pnpm install</code> then reports "Already up to date" and no longer reruns the failing build <a href="https://github.com/pnpm/pnpm/issues/16468">#16468</a>.</p>
</li>
<li>
<p>Fixed frozen installs creating symlinks to the working directory for skipped optional dependencies and unresolved peer dependencies <a href="https://github.com/pnpm/pnpm/issues/16454">#16454</a>.</p>
</li>
</ul>
<h3 id="hoisted-node_modules">Hoisted node_modules<a class="hash-link" href="#hoisted-node_modules" aria-label="Direct link to Hoisted node_modules">#</a></h3>
<ul>
<li>
<p>With <code>nodeLinker: hoisted</code>, a filtered install now keeps the packages of the workspace projects an earlier install put in <code>node_modules</code>. This also covers the install that <code>pnpm --filter &#x3C;selector> run</code> and <code>pnpm --filter &#x3C;selector> exec</code> start before the command. Before, these installs removed every package that only the unselected projects needed <a href="https://github.com/pnpm/pnpm/issues/16483">#16483</a>.</p>
<p>A filtered install of a workspace project also no longer fails with <code>ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY</code> after a filtered install of another project.</p>
</li>
<li>
<p><code>pnpm install</code> with <code>nodeLinker: hoisted</code> now refreshes directories supplied by custom fetchers when reinstalling.</p>
</li>
</ul>
<h3 id="speed-and-network">Speed and network<a class="hash-link" href="#speed-and-network" aria-label="Direct link to Speed and network">#</a></h3>
<ul>
<li>
<p>pnpm now revalidates cached registry metadata with a conditional request, so the registry can answer <code>304 Not Modified</code>. Before, pnpm downloaded the whole document again for registries whose responses forbid caching, such as <code>Cache-Control: no-store</code> <a href="https://github.com/pnpm/pnpm/issues/16528">#16528</a>, and for packages published within <code>minimumReleaseAge</code> <a href="https://github.com/pnpm/pnpm/issues/16506">#16506</a>.</p>
</li>
<li>
<p>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 <a href="https://github.com/pnpm/pnpm/issues/12791">#12791</a>.</p>
</li>
</ul>
<h3 id="running-scripts">Running scripts<a class="hash-link" href="#running-scripts" aria-label="Direct link to Running scripts">#</a></h3>
<ul>
<li>
<p>Scripts run without a terminal no longer start a second <code>sh</code> each. One watchdog per pnpm command now ends every script's process group if pnpm is killed, so <code>pnpm -r run</code> across many projects starts half as many processes <a href="https://github.com/pnpm/pnpm/issues/16489">#16489</a>.</p>
</li>
<li>
<p><code>pnpm run</code> and <code>pnpm exec</code> now warn and run the command when the install that <code>verifyDepsBeforeRun</code> starts fails. This lets scripts run in sandboxes where pnpm cannot install, such as containers with a read-only store or no network <a href="https://github.com/pnpm/pnpm/issues/15173">#15173</a>.</p>
</li>
<li>
<p>A filtered <code>pnpm run</code> or <code>pnpm exec</code> now finds dependencies out of date when a workspace dependency of a selected project has no <code>node_modules</code> directory, as after a filtered install. With <code>verifyDepsBeforeRun: install</code>, pnpm installs that dependency before running the command <a href="https://github.com/pnpm/tasks/issues/45">pnpm/tasks#45</a>.</p>
</li>
<li>
<p><code>pnpm rebuild</code> and <code>pnpm approve-builds</code> refresh 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.</p>
</li>
</ul>
<h3 id="updating-pnpm">Updating pnpm<a class="hash-link" href="#updating-pnpm" aria-label="Direct link to Updating pnpm">#</a></h3>
<ul>
<li>
<p><code>pnpm self-update</code> now fails for Homebrew-installed pnpm and prints the <code>brew upgrade</code> command for the installed formula, such as <code>brew upgrade pnpm</code> or <code>brew upgrade pnpm@11</code>. It used to install a second copy of pnpm that the Homebrew one kept shadowing <a href="https://github.com/pnpm/pnpm/issues/16547">#16547</a>.</p>
</li>
<li>
<p>On Windows, <code>pnpm self-update</code> now replaces a <code>pnpm.exe</code> left in <code>PNPM_HOME</code> or in <code>PNPM_HOME\bin</code>. In <code>PNPM_HOME</code>, that executable kept running the old version after a successful update. In <code>PNPM_HOME\bin</code>, the update failed with <code>EPERM</code>. If the executable was in <code>PNPM_HOME</code>, <code>self-update</code> now asks you to run <code>pnpm setup</code> <a href="https://github.com/pnpm/pnpm/issues/9094">#9094</a>.</p>
</li>
<li>
<p>pnpm can now switch to a <code>packageManager</code> version below 11 on x64 musl Linux, such as Alpine <a href="https://github.com/pnpm/pnpm/issues/16467">#16467</a>.</p>
</li>
<li>
<p>A <code>devEngines.packageManager</code> range now records the running pnpm in <code>pnpm-lock.yaml</code> only if it meets <code>minimumReleaseAge</code>. 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 <a href="https://github.com/pnpm/pnpm/issues/16431">#16431</a>.</p>
</li>
</ul>
<h3 id="filtering-settings-and-other-commands">Filtering, settings, and other commands<a class="hash-link" href="#filtering-settings-and-other-commands" aria-label="Direct link to Filtering, settings, and other commands">#</a></h3>
<ul>
<li>
<p>The <code>[&#x3C;since>]</code> filter selector works again with Git 2.24 through 2.27 <a href="https://github.com/pnpm/pnpm/issues/16561">#16561</a>. With Git older than 2.24, the selector now fails with an error that names the required Git version.</p>
</li>
<li>
<p>Package-name filters now support <code>?</code> to match one character <a href="https://github.com/pnpm/pnpm/issues/2817">#2817</a>.</p>
</li>
<li>
<p><code>pnpm -r pkg get</code> now 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.</p>
</li>
<li>
<p>pnpm now reports an <code>INVALID_SETTING</code> error when <code>allowUnusedPatches</code> in <code>pnpm-workspace.yaml</code> is not a boolean, or when <code>ignoredOptionalDependencies</code> or <code>requiredScripts</code> is not an array of strings. A quoted <code>allowUnusedPatches</code> value such as <code>"false"</code> was treated as <code>true</code>.</p>
</li>
<li>
<p><code>pnpm store path</code>, <code>pnpm store status</code>, 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 <a href="https://github.com/pnpm/pnpm/issues/16554">#16554</a>.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.9.0]]></title>
      <link>https://pnpm.io/blog/releases/12.9.0</link>
      <guid>https://pnpm.io/blog/releases/12.9.0</guid>
      <pubDate>Fri, 02 Oct 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[This release runs pnpm in StackBlitz WebContainers, adds a per-registry networkConcurrency setting, and records every installed project in the store. It also carries a security fix for pnpm login.]]></description>
      <content:encoded><![CDATA[<p>This release runs pnpm in StackBlitz WebContainers, adds a per-registry <code>networkConcurrency</code> setting, and records every installed project in the store. It also carries a security fix for <code>pnpm login</code>.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<ul>
<li>
<p>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.</p>
</li>
<li>
<p>A <code>registries</code> entry can now set <code>networkConcurrency</code>, the most requests pnpm keeps in flight to that registry's origin. Requests to other registries keep the overall limit. The setting may live in <code>pnpm-workspace.yaml</code> or the global <code>config.yaml</code>.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">registries:</span>
  <span class="hljs-attr">https://npm.corp.example.com/:</span>
    <span class="hljs-attr">scopes:</span> [<span class="hljs-string">"@acme"</span>]
    <span class="hljs-attr">networkConcurrency:</span> <span class="hljs-number">4</span>
</code></pre>
</li>
<li>
<p><code>pnpm install</code> now records every project it installs in the store's <code>projects</code> directory, as a symlink to the project directory. A <code>--frozen-store</code> install without the global virtual store still records nothing. Only projects that used the global virtual store were recorded before <a href="https://github.com/pnpm/pnpm/issues/6929">#6929</a>.</p>
</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<ul>
<li><code>pnpm login</code> no longer forwards credentials in its request body to another origin during redirects.</li>
</ul>
<h3 id="installing-packages">Installing packages<a class="hash-link" href="#installing-packages" aria-label="Direct link to Installing packages">#</a></h3>
<ul>
<li>
<p>Fixed <code>pnpm install</code> failing on Android with <code>ERR_PNPM_STORE_DIR_ACQUIRE_OPERATION_LOCK</code> <a href="https://github.com/pnpm/pnpm/issues/16508">#16508</a>.</p>
</li>
<li>
<p><code>pnpm install --frozen-lockfile</code> again succeeds when a workspace project recorded in <code>pnpm-lock.yaml</code> has no directory, such as a project left out of a Docker build context. It still fails if the project's directory exists without a <code>package.json</code> <a href="https://github.com/pnpm/pnpm/issues/16453">#16453</a>.</p>
</li>
<li>
<p><code>pnpm install --frozen-lockfile</code> no longer requires a <code>pnpm-lock.yaml</code> in a project that has no dependencies. It also succeeds when <code>pnpm-lock.yaml</code> records only the pinned pnpm version, as other commands write it when they run before the first install <a href="https://github.com/pnpm/pnpm/issues/16477">#16477</a>.</p>
</li>
<li>
<p>Fixed <code>pnpm install --frozen-lockfile</code> rejecting a fresh lockfile when an injected workspace dependency has an optional peer supplied by another workspace project <a href="https://github.com/pnpm/pnpm/issues/16428">#16428</a>.</p>
</li>
<li>
<p>With <code>nodeLinker: hoisted</code>, a filtered install now keeps the packages of the workspace projects an earlier install put in <code>node_modules</code>. This also covers the install that <code>pnpm --filter &#x3C;selector> run</code> and <code>pnpm --filter &#x3C;selector> exec</code> start before the command. Before, these installs removed every package that only the unselected projects needed <a href="https://github.com/pnpm/pnpm/issues/16483">#16483</a>.</p>
</li>
<li>
<p><code>pnpm install</code> with <code>nodeLinker: hoisted</code> now refreshes directories supplied by custom fetchers when reinstalling. pnpm also keeps the symlinks inside those directories.</p>
</li>
<li>
<p>With <code>enableGlobalVirtualStore</code> on, scripts can run entry points that a CommonJS require hook loads again, such as <code>ts-node index.ts</code>. They failed with <code>ERR_UNKNOWN_FILE_EXTENSION</code> on Node.js versions without built-in TypeScript support <a href="https://github.com/pnpm/pnpm/issues/16436">#16436</a>.</p>
</li>
<li>
<p>pnpm now keeps each project's current lockfile and hidden hoisted dependencies in its own <code>node_modules/.pnpm</code> when <code>virtualStoreDir</code> points at a shared global virtual store. <code>--virtual-store-dir</code> now sets the global virtual store's location too <a href="https://github.com/pnpm/tasks/issues/47">pnpm/tasks#47</a>.</p>
</li>
<li>
<p><code>pnpm clean</code> no longer deletes the project when <code>virtualStoreDir</code> or <code>globalVirtualStoreDir</code> is 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 that <code>globalVirtualStoreDir</code> places inside the project, as it does for <code>virtualStoreDir</code>.</p>
</li>
</ul>
<h3 id="optional-dependencies">Optional dependencies<a class="hash-link" href="#optional-dependencies" aria-label="Direct link to Optional dependencies">#</a></h3>
<ul>
<li>
<p><code>pnpm install</code> no 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 <a href="https://github.com/pnpm/pnpm/issues/16511">#16511</a>.</p>
</li>
<li>
<p>When an optional dependency fails to build, pnpm now removes its link from <code>node_modules</code>. A repeat <code>pnpm install</code> then reports "Already up to date" and no longer reruns the failing build <a href="https://github.com/pnpm/pnpm/issues/16468">#16468</a>.</p>
</li>
<li>
<p><code>pnpm install</code> now prints a warning with the error when an optional dependency cannot be fetched and is skipped. The skipped package is no longer counted in the <code>Packages: +N</code> summary. The <code>pnpm:skipped-optional-dependency</code> log reports the skip with the <code>fetch_failure</code> reason <a href="https://github.com/pnpm/pnpm/issues/16514">#16514</a>.</p>
</li>
</ul>
<h3 id="resolving-dependencies">Resolving dependencies<a class="hash-link" href="#resolving-dependencies" aria-label="Direct link to Resolving dependencies">#</a></h3>
<ul>
<li>
<p>Fixed <code>pnpm install</code> changing an unchanged project's direct dependency to a sibling workspace's pinned version when its dependency tree contains a cycle <a href="https://github.com/pnpm/pnpm/issues/16417">#16417</a>.</p>
</li>
<li>
<p>With <code>autoDedupe</code> enabled, 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 filtered <code>pnpm --filter &#x3C;project> add</code> <a href="https://github.com/pnpm/pnpm/issues/16432">#16432</a>.</p>
</li>
<li>
<p><code>pnpm install</code> and <code>pnpm dedupe</code> now 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 as <code>vue</code> 3.5.40 to 3.5.43, the lockfile kept a second copy of <code>@vue/server-renderer</code> for <code>@vue/test-utils</code> <a href="https://github.com/pnpm/pnpm/issues/16443">#16443</a>.</p>
</li>
<li>
<p><code>pnpm dedupe --check</code> no longer fails right after <code>pnpm install</code> when a project's optional peer is satisfied by a package another workspace project installs. <code>pnpm dedupe</code> now picks the same versions for that package's dependencies as <code>pnpm install</code> <a href="https://github.com/pnpm/pnpm/issues/16447">#16447</a>.</p>
</li>
<li>
<p><code>pnpm install</code> no longer re-resolves an up-to-date lockfile on every run when a patched package is a peer in a peer cycle <a href="https://github.com/pnpm/pnpm/issues/16418">#16418</a>.</p>
</li>
<li>
<p>With <code>autoDedupe</code> enabled, <code>pnpm install --lockfile-only</code> no longer resolves the dependency graph again when nothing changed since an earlier <code>--lockfile-only</code> install deduplicated the lockfile. Such an install keeps the lockfile even if versions were published since it was written, or if only a setting such as <code>resolutionMode</code> changed. Run <code>pnpm dedupe</code> to apply such a change <a href="https://github.com/pnpm/pnpm/issues/16458">#16458</a>.</p>
</li>
</ul>
<h3 id="speed-and-network">Speed and network<a class="hash-link" href="#speed-and-network" aria-label="Direct link to Speed and network">#</a></h3>
<ul>
<li>
<p>A repeat <code>pnpm install</code> in a large workspace reports "Already up to date" faster <a href="https://github.com/pnpm/pnpm/issues/16487">#16487</a>.</p>
</li>
<li>
<p>Sped up dependency resolution of workspaces with many peer dependencies.</p>
</li>
<li>
<p><code>pnpm install</code> sends 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 that <code>minimumReleaseAgeExclude</code> lists without a version now reuse cached registry metadata the same way they do when <code>minimumReleaseAge</code> is not set <a href="https://github.com/pnpm/pnpm/issues/16458">#16458</a>.</p>
</li>
<li>
<p>pnpm no longer downloads every packument again on each install from a registry whose metadata responses forbid caching, such as <code>Cache-Control: no-store</code>. 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 <a href="https://github.com/pnpm/pnpm/issues/16528">#16528</a>.</p>
</li>
<li>
<p>Cached metadata for a package published within <code>minimumReleaseAge</code> is now revalidated with its ETag, so the npm registry can answer <code>304 Not Modified</code>. Before, the next install that checked the cache downloaded the whole document again <a href="https://github.com/pnpm/pnpm/issues/16506">#16506</a>.</p>
</li>
<li>
<p>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 <a href="https://github.com/pnpm/pnpm/issues/12791">#12791</a>.</p>
</li>
<li>
<p>Sped up <code>pnpm install --offline</code> when 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 <a href="https://github.com/pnpm/pnpm/issues/16495">#16495</a>.</p>
</li>
<li>
<p><code>pnpm install --offline</code> now reuses config dependency tarballs that are already present in the store <a href="https://github.com/pnpm/tasks/issues/46">pnpm/tasks#46</a>.</p>
</li>
</ul>
<h3 id="running-scripts">Running scripts<a class="hash-link" href="#running-scripts" aria-label="Direct link to Running scripts">#</a></h3>
<ul>
<li>
<p><code>pnpm -s &#x3C;script></code> runs the script again, with <code>-s</code> meaning <code>--sequential</code> as it does for <code>pnpm run -s &#x3C;script></code>. pnpm rejected it with "unexpected argument '-s' found" <a href="https://github.com/pnpm/pnpm/issues/16446">#16446</a>.</p>
</li>
<li>
<p><code>pnpm run</code> and <code>pnpm exec</code> now warn and run the command when the install that <code>verifyDepsBeforeRun</code> starts fails. This lets scripts run in sandboxes where pnpm cannot install, such as containers with a read-only store or no network <a href="https://github.com/pnpm/pnpm/issues/15173">#15173</a>.</p>
</li>
<li>
<p>A filtered <code>pnpm run</code> or <code>pnpm exec</code> now finds dependencies out of date when a workspace dependency of a selected project has no <code>node_modules</code> directory, as after a filtered install. With <code>verifyDepsBeforeRun: install</code>, pnpm installs that dependency before running the command <a href="https://github.com/pnpm/tasks/issues/45">pnpm/tasks#45</a>.</p>
</li>
<li>
<p>Scripts run without a terminal no longer start a second <code>sh</code> each. One watchdog per pnpm command now ends every script's process group if pnpm is killed, so <code>pnpm -r run</code> across many projects starts half as many processes <a href="https://github.com/pnpm/pnpm/issues/16489">#16489</a>.</p>
</li>
<li>
<p><code>Terminate batch job (Y/N)?</code> no longer appears after pressing Ctrl+C in a script started with <code>pnpm</code> from PowerShell or cmd on Windows <a href="https://github.com/pnpm/pnpm/issues/16502">#16502</a>.</p>
</li>
<li>
<p><code>pnpm rebuild</code> and <code>pnpm approve-builds</code> refresh 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.</p>
</li>
<li>
<p>When <code>pnpm run &#x3C;script></code> or <code>pnpm &#x3C;script></code> finds nothing to run and <code>--filter</code> follows the script name, the error now suggests putting the filter option before the script name <a href="https://github.com/pnpm/pnpm/issues/4655">#4655</a>.</p>
</li>
<li>
<p>Package-name filters now support <code>?</code> to match one character <a href="https://github.com/pnpm/pnpm/issues/2817">#2817</a>.</p>
</li>
</ul>
<h3 id="the-pinned-pnpm-and-pnpm-self-update">The pinned pnpm and <code>pnpm self-update</code><a class="hash-link" href="#the-pinned-pnpm-and-pnpm-self-update" aria-label="Direct link to The pinned pnpm and pnpm self-update">#</a></h3>
<ul>
<li>
<p>pnpm no longer downloads the project's pinned pnpm version again on every command when <code>nodeVersion</code> in <code>pnpm-workspace.yaml</code> names a different Node.js major than the <code>node</code> on <code>PATH</code>. Before, each of those commands took about a second longer and failed without network access <a href="https://github.com/pnpm/pnpm/issues/16497">#16497</a>.</p>
</li>
<li>
<p>Several <code>pnpm</code> commands started at once in a project that pins <code>packageManager</code> no longer fail with <code>The process cannot access the file because it is being used by another process</code> on Windows while the pinned pnpm is being installed.</p>
</li>
<li>
<p>pnpm can now switch to a <code>packageManager</code> version below 11 on x64 musl Linux, such as Alpine <a href="https://github.com/pnpm/pnpm/issues/16467">#16467</a>.</p>
</li>
<li>
<p>A <code>devEngines.packageManager</code> range no longer makes pnpm replace the version recorded in <code>pnpm-lock.yaml</code> with 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 meets <code>minimumReleaseAge</code>. Otherwise it records the newest version in the range that meets it, or the running pnpm if none does <a href="https://github.com/pnpm/pnpm/issues/16431">#16431</a>.</p>
</li>
<li>
<p>On Windows, <code>pnpm self-update</code> now replaces a <code>pnpm.exe</code> left in <code>PNPM_HOME</code> or in <code>PNPM_HOME\bin</code>. Windows ran that executable in place of the updated <code>pnpm.cmd</code> shim, so <code>pnpm --version</code> kept printing the old version after a successful update. If the executable was in <code>PNPM_HOME</code>, <code>self-update</code> now asks you to run <code>pnpm setup</code> <a href="https://github.com/pnpm/pnpm/issues/9094">#9094</a>.</p>
</li>
<li>
<p><code>pnpm self-update</code> now checks that a version installed as the JavaScript <code>pnpm</code> can start before making it the global <code>pnpm</code>. If Node.js is missing, the update fails and the current <code>pnpm</code> stays in place.</p>
</li>
</ul>
<h3 id="other-commands">Other commands<a class="hash-link" href="#other-commands" aria-label="Direct link to Other commands">#</a></h3>
<ul>
<li>
<p><code>pnpm deploy</code> now finds patches and local dependencies when the target directory sits under a symlink, such as <code>/tmp</code> on macOS. It failed with <code>ERR_PNPM_PATCH_NOT_FOUND</code> <a href="https://github.com/pnpm/pnpm/issues/16470">#16470</a>.</p>
</li>
<li>
<p><code>pnpm deploy --legacy</code> now resolves the deployed project's relative <code>file:</code>, <code>link:</code>, and path dependencies from the project's own directory <a href="https://github.com/pnpm/pnpm/issues/16475">#16475</a>.</p>
</li>
<li>
<p><code>pnpm update --global</code> now removes hard-linked executables from <code>PNPM_HOME</code> when migrating packages from the old global layout <a href="https://github.com/pnpm/pnpm/issues/16420">#16420</a>.</p>
</li>
<li>
<p><code>pnpm store prune</code> no longer fails on store index entries that pnpm 11 wrote for git-hosted packages without a <code>package.json</code>. Entries that still cannot be read are kept and counted in the prune summary.</p>
</li>
<li>
<p><code>pnpm store prune</code> now aborts when an error other than a missing directory occurs while scanning project directories in the mark phase.</p>
</li>
<li>
<p><code>pnpm config get</code> and <code>pnpm config list</code> now report a setting given on the command line with <code>--config.&#x3C;name>=&#x3C;value></code>. Before, a value such as <code>--config.node-linker=hoisted</code> reached the install but was absent from the reported configuration <a href="https://github.com/pnpm/pnpm/issues/16276">#16276</a>.</p>
</li>
<li>
<p><code>pnpm -r pkg get</code> now 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.</p>
</li>
<li>
<p>The install summary shows a <code>link:</code> dependency as <code>+ name &#x3C;- path</code>, and the Node.js API's <code>hideLinkedPkgsDiff</code> reporter option leaves matching linked dependencies out of the summary.</p>
</li>
<li>
<p>The <code>--force</code> help text of <code>pnpm install</code> and <code>pnpm add</code> now says that <code>--force</code> keeps skipping optional dependencies built for other platforms. It points to <code>forceIgnoresPlatform</code> and the <code>--os</code>, <code>--cpu</code>, and <code>--libc</code> options for installing them <a href="https://github.com/pnpm/pnpm/issues/16435">#16435</a>.</p>
</li>
<li>
<p>The <code>homepage</code> field of the published <code>pnpm</code> package points to <a href="https://pnpm.io">https://pnpm.io</a> again.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.8.2]]></title>
      <link>https://pnpm.io/blog/releases/12.8.2</link>
      <guid>https://pnpm.io/blog/releases/12.8.2</guid>
      <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 12.8.2 fixes a startup crash on Linux ppc64le and UnknownIssuer errors on systems without CA certificates. pnpm run no longer installs before every script on CI when autoDedupe is enabled, and resolution and hoisted installs on macOS are faster.]]></description>
      <content:encoded><![CDATA[<p>pnpm 12.8.2 fixes a startup crash on Linux ppc64le and <code>UnknownIssuer</code> errors on systems without CA certificates. <code>pnpm run</code> no longer installs before every script on CI when <code>autoDedupe</code> is enabled, and resolution and hoisted installs on macOS are faster.</p>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="platforms-and-environments">Platforms and environments<a class="hash-link" href="#platforms-and-environments" aria-label="Direct link to Platforms and environments">#</a></h3>
<ul>
<li>
<p>Fixed pnpm crashing on startup on Linux ppc64le <a href="https://github.com/pnpm/pnpm/issues/16380">#16380</a>.</p>
</li>
<li>
<p>Fixed installs failing with <code>UnknownIssuer</code> on Linux systems without CA certificates, such as <code>node:24-slim</code>, when <code>NODE_EXTRA_CA_CERTS</code> is set. The extra certificates now extend the bundled CA roots <a href="https://github.com/pnpm/pnpm/issues/16365">#16365</a>.</p>
</li>
<li>
<p>pnpm now creates its store operation locks and other per-user lock files in <code>$XDG_RUNTIME_DIR</code> when it points to a directory only the user can write to. Otherwise, pnpm still uses <code>/tmp</code> on Linux and macOS. Sandboxes that block writes to <code>/tmp</code> can point <code>XDG_RUNTIME_DIR</code> at a writable directory <a href="https://github.com/pnpm/pnpm/issues/16390">#16390</a>.</p>
</li>
<li>
<p>POSIX bin shims and the <code>pnpm</code>, <code>pn</code>, <code>pnpx</code>, and <code>pnx</code> launchers now run inside a Nix build, where the system default path holds none of the utilities they call. Installing again replaces the shims already in <code>node_modules</code> <a href="https://github.com/pnpm/pnpm/issues/16377">#16377</a>.</p>
</li>
<li>
<p>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 <code>pnpm install --auto-dedupe</code> failed with "Unknown option" even though the pinned pnpm supports it <a href="https://github.com/pnpm/pnpm/issues/16353">#16353</a>.</p>
</li>
</ul>
<h3 id="installing-and-resolving-dependencies">Installing and resolving dependencies<a class="hash-link" href="#installing-and-resolving-dependencies" aria-label="Direct link to Installing and resolving dependencies">#</a></h3>
<ul>
<li>
<p><code>pnpm install --frozen-lockfile</code> now fails when <code>Cargo.lock</code> does not satisfy a dependency requirement in <code>Cargo.toml</code>. The error names the crate and the version the lockfile holds <a href="https://github.com/pnpm/pnpm/issues/16355">#16355</a>.</p>
</li>
<li>
<p><code>pnpm install</code> returns "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.</p>
</li>
<li>
<p>With <code>injectWorkspacePackages: true</code>, a fresh <code>pnpm install</code> now records a workspace dependency as <code>link:</code> when its injected copy differs from the project only by an optional peer that peer-dependent dedupe merges. It was recorded as a peer-suffixed <code>file:</code> copy <a href="https://github.com/pnpm/pnpm/issues/16354">#16354</a>.</p>
</li>
<li>
<p><code>pnpm dedupe --check</code> now passes right after <code>pnpm dedupe</code> when 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 <a href="https://github.com/pnpm/pnpm/issues/16356">#16356</a>.</p>
</li>
<li>
<p>When <code>minimumReleaseAge</code> hides the version that <code>latest</code> points 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 new <code>1.0.0</code> was too new, <code>latest</code> fell back to an old <code>0.0.1</code> even though <code>1.0.0-beta.4</code> had been <code>latest</code> until then <a href="https://github.com/pnpm/pnpm/issues/16388">#16388</a>.</p>
</li>
<li>
<p>Git-hosted dependencies now respect <code>pmOnFail</code>. If it is set to anything other than <code>download</code>, a git-hosted dependency that pins a pnpm version is prepared by the running pnpm, and pnpm does not download the pinned version <a href="https://github.com/pnpm/pnpm/issues/16376">#16376</a>.</p>
</li>
<li>
<p>pnpmfile hooks such as <code>readPackage</code> now run once for a dependency that several packages request at the same time. They could run twice for it before.</p>
</li>
<li>
<p><code>childConcurrency</code> now defaults to 5, the documented value. It used to be capped at 4 and to follow the host's CPU count.</p>
</li>
</ul>
<h3 id="running-scripts">Running scripts<a class="hash-link" href="#running-scripts" aria-label="Direct link to Running scripts">#</a></h3>
<ul>
<li>
<p><code>pnpm run</code> and <code>pnpm exec</code> no longer install dependencies before every script on CI when <code>autoDedupe</code> is enabled. <code>pnpm install --frozen-lockfile</code> now keeps the deduplication record left by an earlier install <a href="https://github.com/pnpm/pnpm/issues/16374">#16374</a>.</p>
</li>
<li>
<p>On macOS and Linux, lifecycle scripts and <code>pnpm run</code> now always get <code>PATH</code> from the <code>PATH</code> variable. When the environment also held a <code>Path</code> variable, a script sometimes got <code>Path</code>'s value, and failed with <code>node: not found</code> <a href="https://github.com/pnpm/pnpm/issues/16308">#16308</a>.</p>
</li>
<li>
<p><code>pnpm run</code> now exits after a <code>SIGTERM</code> in a container where pnpm is PID 1 and the script runs pnpm again, as <code>"start": "pnpm serve"</code> does. Since 12.6.0 it kept waiting after the script had shut down, until the container runtime killed it.</p>
</li>
</ul>
<h3 id="other-commands-and-settings">Other commands and settings<a class="hash-link" href="#other-commands-and-settings" aria-label="Direct link to Other commands and settings">#</a></h3>
<ul>
<li>
<p><code>pnpm config get globalShims</code>, <code>pnpm shim list</code>, and global installs no longer read <code>globalShims</code> from a project's <code>pnpm-workspace.yaml</code>. Only the global config file, the pnpm home's own <code>pnpm-workspace.yaml</code>, and <code>PNPM_CONFIG_GLOBAL_SHIMS</code> set it, so a repository cannot choose which globally installed packages get project-aware shims.</p>
</li>
<li>
<p><code>pnpm config set --location=project</code> refuses a machine-level setting such as <code>stateDir</code> or <code>scope</code> with <code>ERR_PNPM_CONFIG_SET_NOT_A_PROJECT_SETTING</code>, which names where the setting belongs. <code>pnpm config delete</code> still clears such a key from a project's <code>pnpm-workspace.yaml</code>.</p>
</li>
<li>
<p><code>pnpm deploy</code> no longer fails with <code>ERR_PNPM_DEPLOY_AMBIGUOUS_PEER</code> in a workspace with <code>injectWorkspacePackages: true</code> when a workspace package lists its peer dependency as a dev dependency too <a href="https://github.com/pnpm/pnpm/issues/16375">#16375</a>.</p>
</li>
<li>
<p><code>pnpm deploy</code> no longer copies the workspace root's <code>packageManager</code> and <code>devEngines.packageManager</code> fields into the deployed <code>package.json</code> <a href="https://github.com/pnpm/pnpm/issues/16403">#16403</a>.</p>
</li>
<li>
<p><code>pnpm publish</code> now includes bare <code>README</code> files and README files with Markdown extensions such as <code>readme.markdown</code> in registry metadata <a href="https://github.com/pnpm/pnpm/issues/12704">#12704</a>.</p>
</li>
<li>
<p><code>pnpm store prune</code> now removes the packages that only expired <code>pnpm dlx</code> cache entries used. They were left in the store until the next <code>pnpm store prune</code> <a href="https://github.com/pnpm/pnpm/discussions/16383">#16383</a>.</p>
</li>
</ul>
<h3 id="performance">Performance<a class="hash-link" href="#performance" aria-label="Direct link to Performance">#</a></h3>
<ul>
<li>
<p>Sped up dependency resolution in large workspaces, and when many dependencies request different ranges of the same package. Resolution also uses less memory.</p>
</li>
<li>
<p>Sped up <code>pnpm install</code> with <code>nodeLinker: hoisted</code> on macOS when the lockfile is re-resolved, such as with <code>autoDedupe</code> enabled <a href="https://github.com/pnpm/pnpm/issues/16397">#16397</a>.</p>
</li>
<li>
<p>Sped up extracting package tarballs.</p>
</li>
<li>
<p><code>pnpm install</code> without <code>--frozen-lockfile</code> is faster on some machines in projects with a <code>pnpm-workspace.yaml</code>. Those installs linked with one worker thread per core, half of what a frozen install uses.</p>
</li>
<li>
<p>On Windows, warm <code>pnpm install --frozen-lockfile</code> runs 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 a <code>pnpm-workspace.yaml</code> on machines with 3 to 15 cores.</p>
</li>
</ul>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.8]]></title>
      <link>https://pnpm.io/blog/releases/12.8</link>
      <guid>https://pnpm.io/blog/releases/12.8</guid>
      <pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 12.8 warns when pnpm pack or pnpm publish would ship a .env file, installs sharedWorkspaceLockfile: false workspaces concurrently, applies every setting passed as --config.<name>=<value>, and no longer leaves the Windows terminal stuck after Ctrl+C in a script. This release also carries a...]]></description>
      <content:encoded><![CDATA[<p>pnpm 12.8 warns when <code>pnpm pack</code> or <code>pnpm publish</code> would ship a <code>.env</code> file,
installs <code>sharedWorkspaceLockfile: false</code> workspaces concurrently, applies every
setting passed as <code>--config.&#x3C;name>=&#x3C;value></code>, and no longer leaves the Windows
terminal stuck after Ctrl+C in a script. This release also carries a long list
of bug fixes.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<h3 id="warning-about-env-files-in-packages">Warning about <code>.env</code> files in packages<a class="hash-link" href="#warning-about-env-files-in-packages" aria-label="Direct link to Warning about .env files in packages">#</a></h3>
<p><code>pnpm pack</code> and <code>pnpm publish</code> now warn when the tarball includes a <code>.env</code> or
<code>.env.*</code> file that the <code>files</code> field of <code>package.json</code> does not list
(<a href="https://github.com/pnpm/pnpm/issues/7826">#7826</a>). Templates such as
<code>.env.example</code> are not reported. List the file in <code>files</code> to publish it on
purpose, or exclude it in <code>.npmignore</code> or <code>.gitignore</code>.</p>
<h3 id="concurrent-installs-without-a-shared-lockfile">Concurrent installs without a shared lockfile<a class="hash-link" href="#concurrent-installs-without-a-shared-lockfile" aria-label="Direct link to Concurrent installs without a shared lockfile">#</a></h3>
<p>In a workspace with
<a href="https://pnpm.io/workspaces#sharedworkspacelockfile"><code>sharedWorkspaceLockfile: false</code></a>,
<code>pnpm install</code> now installs projects concurrently, up to
<a href="https://pnpm.io/workspaces#workspaceconcurrency"><code>workspaceConcurrency</code></a> at a time
(<a href="https://github.com/pnpm/pnpm/issues/14480">#14480</a>). 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 a <code>preinstall</code> or <code>pnpm:devPreinstall</code> script, or with an injected or
<code>file:</code> workspace dependency, waits for its workspace dependencies before it
starts.</p>
<p>The installs also share package metadata, lockfile verification, and store
caches, so they use less CPU and memory when several projects depend on the
same packages.</p>
<p>Related fixes for such workspaces:</p>
<ul>
<li>With <code>enableGlobalVirtualStore</code>, each project keeps its current lockfile and
its hidden hoisted dependencies in its own <code>node_modules/.pnpm</code>, so a repeat
install no longer relinks the other projects' packages.</li>
<li><code>pnpm rebuild</code>, <code>pnpm approve-builds</code>, and <code>pnpm ignored-builds</code> work on the
current project's <code>node_modules</code>
(<a href="https://github.com/pnpm/pnpm/issues/9402">#9402</a>).</li>
<li>An injected workspace package that has lifecycle scripts is hard linked into
the projects that depend on it, so later edits reach them
(<a href="https://github.com/pnpm/pnpm/issues/9828">#9828</a>).</li>
</ul>
<h3 id="every-setting-accepted-as---configname">Every setting accepted as <code>--config.&#x3C;name></code><a class="hash-link" href="#every-setting-accepted-as---configname" aria-label="Direct link to Every setting accepted as --config.<name>">#</a></h3>
<p>Every setting pnpm supports can now be set with <code>--config.&#x3C;name>=&#x3C;value></code> on the
command line, not only the ones whose command also carries a matching flag
(<a href="https://github.com/pnpm/pnpm/issues/16276">#16276</a>). Before,
<code>pnpm install --config.frozen-lockfile=true</code> dropped the setting and rewrote
<code>pnpm-lock.yaml</code> as though the install had not been frozen.</p>
<p>Settings given on the command line, such as <code>--registry</code> and <code>--store-dir</code>, now
also take precedence over the values a pnpmfile <code>updateConfig</code> hook sets
(<a href="https://github.com/pnpm/pnpm/issues/14063">#14063</a>).</p>
<h3 id="ctrlc-on-windows">Ctrl+C on Windows<a class="hash-link" href="#ctrlc-on-windows" aria-label="Direct link to Ctrl+C on Windows">#</a></h3>
<p>Interrupting a script with Ctrl+C on Windows no longer leaves the terminal stuck
(<a href="https://github.com/pnpm/pnpm/issues/14860">#14860</a>). A script that runs
through a batch shim, as <code>vite dev</code> does through <code>vite.CMD</code>, 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, and a second Ctrl+C ends
the script's shell at once.</p>
<h3 id="other-changes">Other changes<a class="hash-link" href="#other-changes" aria-label="Direct link to Other changes">#</a></h3>
<ul>
<li><code>pnpm pack</code> honors <code>--silent</code>, <code>--reporter=silent</code>, and <code>--loglevel=silent</code> to
hide the tarball contents and summary
(<a href="https://github.com/pnpm/pnpm/issues/10297">#10297</a>).</li>
<li>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
<code>packageImportMethod</code> is set
(<a href="https://github.com/pnpm/pnpm/issues/4410">#4410</a>). Scripts listed in
<a href="https://pnpm.io/workspaces#syncinjecteddepsafterscripts"><code>syncInjectedDepsAfterScripts</code></a>
now update injected dependencies while they run, so a dev server watching the
injected package sees each change before the script exits.</li>
<li><code>pnpm update --global</code> reinstalls the global packages that pnpm 10 installed
into <code>&#x3C;global-dir>/5</code>, so their commands are linked again and
<code>pnpm list --global</code> lists them. Once every package is migrated, pnpm deletes
the previous directory (<a href="https://github.com/pnpm/pnpm/issues/11528">#11528</a>).</li>
<li><code>pnpm install</code> runs the install hooks of a config dependency plugin's
pnpmfile, including <code>readPackage</code>, <code>afterAllResolved</code>, and custom resolvers.
Before, only its <code>updateConfig</code> hook ran.</li>
<li>Scripts see the <code>npm_command</code> environment variable
(<a href="https://github.com/pnpm/pnpm/issues/16265">#16265</a>) and an
<code>npm_config_node_gyp</code> that points at the bundled <code>node-gyp</code>
(<a href="https://github.com/pnpm/pnpm/issues/16270">#16270</a>).</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="installing">Installing<a class="hash-link" href="#installing" aria-label="Direct link to Installing">#</a></h3>
<ul>
<li><code>pnpm install --dev</code> and <code>pnpm fetch --dev</code> install the optional dependencies
of devDependencies, such as the platform binaries of Biome and oxlint
(<a href="https://github.com/pnpm/pnpm/issues/9678">#9678</a>).</li>
<li><code>pnpm install --offline</code> and <code>pnpm add --offline</code> resolve a range to the
newest matching version whose tarball is already in the store
(<a href="https://github.com/pnpm/pnpm/issues/10715">#10715</a>).</li>
<li>An offline install that fails on a metadata cache from before pnpm 12.4
explains that one online install repopulates it, with the
<code>ERR_PNPM_NO_OFFLINE_META</code> code
(<a href="https://github.com/pnpm/pnpm/issues/15656">#15656</a>).</li>
<li>Installing a git-hosted dependency that has to be built no longer fails on
unapproved build scripts of its own dependencies
(<a href="https://github.com/pnpm/pnpm/issues/9764">#9764</a>), and a git-hosted pnpm
workspace without a committed lockfile is detected as a pnpm project
(<a href="https://github.com/pnpm/pnpm/issues/14011">#14011</a>).</li>
<li><code>pnpm install</code> refreshes dependencies when a local <code>file:</code> directory changes
its dependencies (<a href="https://github.com/pnpm/pnpm/issues/4623">#4623</a>).</li>
<li>An optional dependency whose install script fails is removed from
<code>node_modules</code> (<a href="https://github.com/pnpm/pnpm/issues/8756">#8756</a>).</li>
<li>With <code>nodeLinker: hoisted</code>, <code>pnpm install</code> restores a deleted workspace
project's <code>node_modules</code>, and clears orphaned package directories that an
interrupted install left behind
(<a href="https://github.com/pnpm/pnpm/issues/13676">#13676</a>).</li>
<li>Installing through a <code>pnpr</code> server records the pnpmfile checksum in the
lockfile (<a href="https://github.com/pnpm/pnpm/issues/14460">#14460</a>) and links a
workspace project at its <code>publishConfig.directory</code>.</li>
</ul>
<h3 id="resolving-and-linking">Resolving and linking<a class="hash-link" href="#resolving-and-linking" aria-label="Direct link to Resolving and linking">#</a></h3>
<ul>
<li><code>pnpm install</code> no longer aborts on a huge allocation when peer ranges combine
overlapping <code>||</code> alternatives
(<a href="https://github.com/pnpm/pnpm/issues/15867">#15867</a>).</li>
<li>A registry package with a <code>file:</code> dependency on a directory inside itself,
such as <code>"@types/css-tree": "file:./typings/css-tree"</code>, installs as it does
with npm and Yarn (<a href="https://github.com/pnpm/pnpm/issues/9141">#9141</a>).</li>
<li>An <code>npm:</code> alias written by <code>overrides</code> stays in place when pnpm re-resolves
the aliased dependency (<a href="https://github.com/pnpm/pnpm/issues/16309">#16309</a>).</li>
<li>A peer dependency no longer resolves to two different versions for one
package (<a href="https://github.com/pnpm/pnpm/issues/12098">#12098</a>), and an
optional peer is no longer resolved from another workspace project's package
when that causes bogus unmet peer errors
(<a href="https://github.com/pnpm/pnpm/issues/13989">#13989</a>).</li>
<li><code>pnpm dedupe</code> no longer changes the lockfile on every run when a nested peer
is provided through an npm alias
(<a href="https://github.com/pnpm/pnpm/issues/15709">#15709</a>).</li>
<li>With <code>resolutionMode: time-based</code> and <code>minimumReleaseAge</code> both set, a
subdependency is no longer reported as too new when only the time-based
cutoff excludes it (<a href="https://github.com/pnpm/pnpm/issues/13569">#13569</a>,
<a href="https://github.com/pnpm/pnpm/issues/16298">#16298</a>).</li>
<li>A transient metadata fetch failure is retried and no longer reported as
<code>TRUST_DOWNGRADE</code> or <code>MINIMUM_RELEASE_AGE_VIOLATION</code>
(<a href="https://github.com/pnpm/pnpm/issues/12031">#12031</a>).</li>
<li>pnpm's built-in package compatibility database no longer applies to a
project's own manifest, so a project named like <code>vue-loader</code> no longer gains
dependencies (<a href="https://github.com/pnpm/pnpm/issues/11700">#11700</a>).</li>
<li>Packages in an external <code>virtualStoreDir</code> can resolve the project's hoisted
direct dependencies. Run <code>pnpm install --force</code> to repair an existing
installation (<a href="https://github.com/pnpm/pnpm/issues/5652">#5652</a>).</li>
<li>The bins of auto-installed peer dependencies are linked into the workspace
root's <code>node_modules/.bin</code>
(<a href="https://github.com/pnpm/pnpm/issues/8511">#8511</a>).</li>
</ul>
<h3 id="lockfiles">Lockfiles<a class="hash-link" href="#lockfiles" aria-label="Direct link to Lockfiles">#</a></h3>
<ul>
<li><code>pnpm install --frozen-lockfile</code> works on a detached HEAD with
<a href="https://pnpm.io/settings/store#gitbranchlockfile"><code>gitBranchLockfile</code></a>
(<a href="https://github.com/pnpm/pnpm/issues/7672">#7672</a>), and accepts a lockfile
without an importer entry for a workspace package that has no dependencies
(<a href="https://github.com/pnpm/pnpm/issues/15875">#15875</a>).</li>
<li><code>pnpm install</code> fails with <code>ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY</code> when an
importer references a dependency version with no snapshot entry
(<a href="https://github.com/pnpm/pnpm/issues/14764">#14764</a>).</li>
<li>On CI, an explicit
<a href="https://pnpm.io/settings/store#preferfrozenlockfile"><code>preferFrozenLockfile: true</code></a> no longer
lets the install update an outdated lockfile
(<a href="https://github.com/pnpm/pnpm/pull/9072">#9072</a>).</li>
</ul>
<h3 id="workspaces-and-injected-packages">Workspaces and injected packages<a class="hash-link" href="#workspaces-and-injected-packages" aria-label="Direct link to Workspaces and injected packages">#</a></h3>
<ul>
<li><code>pnpm install</code> no longer creates a <code>node_modules</code> symlink inside the
<code>publishConfig.directory</code> of a package linked with <code>linkDirectory</code>, where a
build tool cleaning its output could delete dependency files
(<a href="https://github.com/pnpm/pnpm/issues/16226">#16226</a>).</li>
<li>An injected workspace dependency that publishes from a
<code>publishConfig.directory</code> built by its own <code>prepare</code> script installs
correctly (<a href="https://github.com/pnpm/pnpm/issues/7811">#7811</a>).</li>
<li><code>injectWorkspacePackages</code> treats a dependency declared with a relative path,
such as <code>workspace:../foo</code>, the same way as a <code>workspace:*</code> one
(<a href="https://github.com/pnpm/pnpm/issues/10446">#10446</a>).</li>
<li>Workspace discovery skips dot-prefixed directories, so <code>**</code> no longer matches
projects inside <code>.cache</code> (<a href="https://github.com/pnpm/pnpm/issues/16250">#16250</a>).</li>
<li><code>pnpm import</code> keeps the versions pinned by a <code>yarn.lock</code> inside a workspace
project (<a href="https://github.com/pnpm/pnpm/issues/4385">#4385</a>).</li>
</ul>
<h3 id="store-and-caches">Store and caches<a class="hash-link" href="#store-and-caches" aria-label="Direct link to Store and caches">#</a></h3>
<ul>
<li>Files imported from the store follow the umask of the install
(<a href="https://github.com/pnpm/pnpm/issues/3807">#3807</a>), and files already in a
shared store keep their owner, group, and mode
(<a href="https://github.com/pnpm/pnpm/issues/12765">#12765</a>).</li>
<li>Repairing a store file modified through a hard link keeps its inode on Linux
and macOS, so other projects are healed at the same time
(<a href="https://github.com/pnpm/pnpm/issues/3445">#3445</a>).</li>
<li>pnpm warns when it cannot hard link from the store in the pnpm home and falls
back to a store on the project's filesystem
(<a href="https://github.com/pnpm/pnpm/issues/14505">#14505</a>).</li>
<li>The side-effects cache restores symlinks that a build script creates. After
upgrading, every package with a build script is built once more
(<a href="https://github.com/pnpm/pnpm/issues/12859">#12859</a>).</li>
<li>Concurrent installs that share a global virtual store build a package in its
slot one at a time (<a href="https://github.com/pnpm/pnpm/issues/15568">#15568</a>).</li>
<li>A warm install reuses on-disk metadata for five minutes when the registry
sends no ETag (<a href="https://github.com/pnpm/pnpm/issues/13976">#13976</a>), and
honors <code>Cache-Control</code> for registry metadata
(<a href="https://github.com/pnpm/pnpm/issues/13487">#13487</a>) and tarball URL
dependencies (<a href="https://github.com/pnpm/pnpm/issues/15648">#15648</a>).</li>
</ul>
<h3 id="patched-dependencies">Patched dependencies<a class="hash-link" href="#patched-dependencies" aria-label="Direct link to Patched dependencies">#</a></h3>
<ul>
<li><code>pnpm install</code> repairs a lockfile whose <code>patch_hash</code> paths disagree with
<code>patchedDependencies</code>, and <code>--frozen-lockfile</code> fails on it with
<code>ERR_PNPM_INCONSISTENT_PATCH_HASH</code>
(<a href="https://github.com/pnpm/pnpm/pull/15336">#15336</a>).</li>
<li>A missing patch file fails the install with <code>ERR_PNPM_PATCH_NOT_FOUND</code>
(<a href="https://github.com/pnpm/pnpm/issues/5268">#5268</a>).</li>
<li>With <code>nodeLinker: hoisted</code>, a shared copy of a patched dependency is patched
only once (<a href="https://github.com/pnpm/pnpm/issues/7565">#7565</a>).</li>
<li><code>engineStrict</code> checks the patched <code>package.json</code>
(<a href="https://github.com/pnpm/pnpm/issues/9603">#9603</a>).</li>
<li><code>pnpm patch</code> applies the existing patch to a git-hosted dependency
(<a href="https://github.com/pnpm/pnpm/issues/9699">#9699</a>).</li>
</ul>
<h3 id="adding-and-updating">Adding and updating<a class="hash-link" href="#adding-and-updating" aria-label="Direct link to Adding and updating">#</a></h3>
<ul>
<li><code>pnpm add &#x3C;dir></code> warns when the directory declares peer dependencies
(<a href="https://github.com/pnpm/pnpm/issues/5523">#5523</a>).</li>
<li><code>pnpm add --save-types</code> skips deprecated <code>@types/*</code> stubs such as
<code>@types/typescript</code> (<a href="https://github.com/pnpm/pnpm/issues/15636">#15636</a>).</li>
<li><code>pnpm version</code>, <code>pnpm add</code>, and <code>pnpm pkg set</code> keep JSON5 style in
<code>package.json5</code> (<a href="https://github.com/pnpm/pnpm/issues/15717">#15717</a>).</li>
</ul>
<h3 id="running-scripts">Running scripts<a class="hash-link" href="#running-scripts" aria-label="Direct link to Running scripts">#</a></h3>
<ul>
<li><code>pnpm run</code> and <code>pnpm exec</code> no longer auto-install when the root
<code>package.json</code> still keeps <code>overrides</code> or similar settings in its <code>pnpm</code>
field. They fail and ask to move the settings to <code>pnpm-workspace.yaml</code>
(<a href="https://github.com/pnpm/pnpm/issues/16278">#16278</a>).</li>
<li>When <a href="https://pnpm.io/settings/build#verifydepsbeforerun"><code>verifyDepsBeforeRun</code></a> installs
before a filtered command, it installs only the selected projects
(<a href="https://github.com/pnpm/pnpm/issues/11865">#11865</a>).</li>
<li><code>pnpm -r run /regexp/</code> honors <code>tasks</code> <code>dependsOn</code>
(<a href="https://github.com/pnpm/pnpm/issues/15596">#15596</a>).</li>
<li><code>pnpm run</code> exits with the code of a script that handles Ctrl+C
(<a href="https://github.com/pnpm/pnpm/issues/9945">#9945</a>), and no longer hangs when
a background process keeps a finished script's output open
(<a href="https://github.com/pnpm/pnpm/issues/5730">#5730</a>).</li>
<li><a href="https://pnpm.io/settings/other#scriptshell"><code>scriptShell</code></a> is used even when
<code>shellEmulator</code> is enabled
(<a href="https://github.com/pnpm/pnpm/issues/14719">#14719</a>).</li>
<li>With <code>enableGlobalVirtualStore</code>, dependency build scripts see the workspace
root's <code>node_modules/.bin</code>
(<a href="https://github.com/pnpm/pnpm/issues/15652">#15652</a>).</li>
<li>Install scripts find the bundled node-gyp when pnpm runs through a symlink
(<a href="https://github.com/pnpm/pnpm/issues/15694">#15694</a>).</li>
<li>Commands run through a dependency's own <code>node_modules/.bin</code> no longer fail
with <code>MODULE_NOT_FOUND</code> (<a href="https://github.com/pnpm/pnpm/issues/10189">#10189</a>).</li>
<li><code>pnpx --version</code> and <code>pnpm dlx --version</code> print the pnpm version
(<a href="https://github.com/pnpm/pnpm/issues/16259">#16259</a>).</li>
</ul>
<h3 id="publishing-and-deploying">Publishing and deploying<a class="hash-link" href="#publishing-and-deploying" aria-label="Direct link to Publishing and deploying">#</a></h3>
<ul>
<li><code>pnpm pack</code> and <code>pnpm publish</code> ship a file that <code>files</code> names even when
another entry excludes its directory
(<a href="https://github.com/pnpm/pnpm/issues/16213">#16213</a>), and prune directories
that a <code>files</code> exclusion names
(<a href="https://github.com/pnpm/pnpm/issues/15738">#15738</a>).</li>
<li><code>pnpm publish</code> waits at least 5 minutes for the registry to answer, fixing
"409 Conflict - Failed to save packument" errors
(<a href="https://github.com/pnpm/pnpm/issues/11454">#11454</a>).</li>
<li><code>pnpm deploy --prod</code> works with a <code>devEngines.runtime</code> that uses
<code>onFail: download</code> (<a href="https://github.com/pnpm/pnpm/issues/15703">#15703</a>).</li>
<li><code>pnpm deploy</code> copies workspace dependencies instead of hard linking them to
their sources (<a href="https://github.com/pnpm/pnpm/issues/12176">#12176</a>), and
<code>pnpm deploy --legacy</code> no longer leaves broken links
(<a href="https://github.com/pnpm/pnpm/issues/9575">#9575</a>).</li>
</ul>
<h3 id="configuration">Configuration<a class="hash-link" href="#configuration" aria-label="Direct link to Configuration">#</a></h3>
<ul>
<li><code>pnpm config set --location=project</code> inside a workspace package writes to the
workspace root's <code>pnpm-workspace.yaml</code>
(<a href="https://github.com/pnpm/pnpm/issues/13757">#13757</a>).</li>
<li><code>PNPM_CONFIG_WORKSPACE_DIR</code> is read like other settings
(<a href="https://github.com/pnpm/pnpm/issues/16275">#16275</a>).</li>
<li>An invalid base64 <code>_password</code> fails with <code>ERR_PNPM_AUTH_INVALID_BASE64</code>
(<a href="https://github.com/pnpm/pnpm/issues/16273">#16273</a>).</li>
<li><code>proxy=false</code> turns proxying off even when proxy environment variables are
set.</li>
<li>A pnpmfile <code>fetchers</code> hook runs once per package
(<a href="https://github.com/pnpm/pnpm/issues/15584">#15584</a>,
<a href="https://github.com/pnpm/pnpm/issues/15025">#15025</a>), a custom resolver's
package is re-fetched when its <code>integrity</code> changes
(<a href="https://github.com/pnpm/pnpm/issues/15670">#15670</a>), and invalid
<code>readPackage</code> results are rejected
(<a href="https://github.com/pnpm/pnpm/issues/15730">#15730</a>,
<a href="https://github.com/pnpm/pnpm/issues/15705">#15705</a>).</li>
</ul>
<h3 id="global-packages-and-pnpm-versions">Global packages and pnpm versions<a class="hash-link" href="#global-packages-and-pnpm-versions" aria-label="Direct link to Global packages and pnpm versions">#</a></h3>
<ul>
<li>Signals such as <code>SIGTERM</code> reach the pnpm version pnpm switches to and the one
<code>pnpm with</code> runs (<a href="https://github.com/pnpm/pnpm/issues/9948">#9948</a>).</li>
<li>On arm64 musl Linux, switching to a pinned pnpm older than 12 runs the
JavaScript package (<a href="https://github.com/pnpm/pnpm/issues/10443">#10443</a>).</li>
<li>Global shims work when pnpm runs through a relative symlink, as with Homebrew
(<a href="https://github.com/pnpm/pnpm/issues/15691">#15691</a>).</li>
<li><code>pnpm env remove --global</code> deletes Node.js versions in pnpm's store
(<a href="https://github.com/pnpm/pnpm/issues/8357">#8357</a>).</li>
<li><code>pnpm self-update</code> no longer suggests a downgrade when <code>minimumReleaseAge</code>
holds back <code>latest</code> (<a href="https://github.com/pnpm/pnpm/issues/12006">#12006</a>).</li>
</ul>
<h3 id="windows">Windows<a class="hash-link" href="#windows" aria-label="Direct link to Windows">#</a></h3>
<ul>
<li><code>pnpm run</code> passes arguments to the script as typed, without <code>%VAR%</code>
expansion or doubled backslashes
(<a href="https://github.com/pnpm/pnpm/issues/16257">#16257</a>).</li>
<li><code>pnpm.exe</code> runs without the Visual C++ Redistributable
(<a href="https://github.com/pnpm/pnpm/issues/15723">#15723</a>).</li>
<li><code>.cmd</code> shims keep a <code>%</code> in the project path
(<a href="https://github.com/pnpm/pnpm/issues/15716">#15716</a>) and run tools with
non-ASCII paths (<a href="https://github.com/pnpm/pnpm/issues/6999">#6999</a>,
<a href="https://github.com/pnpm/pnpm/issues/16217">#16217</a>). Bin shims run from
Cygwin again (<a href="https://github.com/pnpm/pnpm/issues/12845">#12845</a>).</li>
<li>Installing pnpm with npm writes <code>node_modules/.bin</code> shims that run <code>pnpm.exe</code>
(<a href="https://github.com/pnpm/pnpm/issues/15688">#15688</a>).</li>
<li>Wildcard workspace patterns work when the workspace is on a different drive
than the pnpm cache (<a href="https://github.com/pnpm/pnpm/issues/16239">#16239</a>).</li>
<li><code>pnpm setup</code> no longer writes <code>pn.ps1</code>, <code>pnpx.ps1</code>, and <code>pnx.ps1</code>, which
failed under restrictive execution policies
(<a href="https://github.com/pnpm/pnpm/issues/8444">#8444</a>), no longer panics on
non-ASCII environment variable names
(<a href="https://github.com/pnpm/pnpm/issues/15684">#15684</a>), and expands nested
<code>%VAR%</code> references in <code>PNPM_HOME</code>
(<a href="https://github.com/pnpm/pnpm/issues/13236">#13236</a>).</li>
</ul>
<h3 id="inspecting-dependencies-and-output">Inspecting dependencies and output<a class="hash-link" href="#inspecting-dependencies-and-output" aria-label="Direct link to Inspecting dependencies and output">#</a></h3>
<ul>
<li><code>pnpm audit</code> fails on unresolvable dependency references
(<a href="https://github.com/pnpm/pnpm/issues/13638">#13638</a>).</li>
<li><code>pnpm licenses list</code> reports actual on-disk locations with hoisting
(<a href="https://github.com/pnpm/pnpm/issues/8589">#8589</a>).</li>
<li><code>pnpm root</code> prints the configured <code>modulesDir</code>
(<a href="https://github.com/pnpm/pnpm/issues/9113">#9113</a>).</li>
<li>With <code>--loglevel warn</code> or <code>error</code>, the full output of a failed install script
is printed.</li>
<li>Resolution errors show their cause, such as
<code>invalid peer certificate: UnknownIssuer</code>
(<a href="https://github.com/pnpm/pnpm/issues/9556">#9556</a>), and SSH
<code>Permission denied (publickey)</code> errors suggest <code>ssh-add -l</code>
(<a href="https://github.com/pnpm/pnpm/issues/13743">#13743</a>).</li>
<li>Lockfile verification errors carry specific codes and suggest relaxing a
policy only when that could help
(<a href="https://github.com/pnpm/pnpm/issues/14411">#14411</a>).</li>
</ul>
<p>See the <a href="https://github.com/pnpm/pnpm/releases/tag/v12.8.0">v12.8.0 release notes</a>
for the complete list of changes.</p>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.7]]></title>
      <link>https://pnpm.io/blog/releases/12.7</link>
      <guid>https://pnpm.io/blog/releases/12.7</guid>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 12.7 lets the global node shim follow .nvmrc and .node-version, adds pnpm install --allow-build and pnpm publish --publish-wait-timeout, and creates pnpm-workspace.yaml from the workspaces field of package.json. pnpm install --force no longer installs optional dependencies built for other...]]></description>
      <content:encoded><![CDATA[<p>pnpm 12.7 lets the global <code>node</code> shim follow <code>.nvmrc</code> and <code>.node-version</code>,
adds <code>pnpm install --allow-build</code> and <code>pnpm publish --publish-wait-timeout</code>,
and creates <code>pnpm-workspace.yaml</code> from the <code>workspaces</code> field of <code>package.json</code>.
<code>pnpm install --force</code> no longer installs optional dependencies built for other
platforms. This release also carries security fixes and a long list of bug
fixes.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<h3 id="nvmrc-and-node-version-in-the-node-shim"><code>.nvmrc</code> and <code>.node-version</code> in the <code>node</code> shim<a class="hash-link" href="#nvmrc-and-node-version-in-the-node-shim" aria-label="Direct link to .nvmrc and .node-version in the node shim">#</a></h3>
<p>The <a href="https://pnpm.io/global-packages#project-aware-global-bins">project-aware</a> global <code>node</code>
shim now uses the Node.js version from the nearest <code>.nvmrc</code> or <code>.node-version</code>
file when the project does not declare a Node.js runtime in
<code>devEngines.runtime</code> or <code>engines.runtime</code>
(<a href="https://github.com/pnpm/pnpm/issues/4471">#4471</a>). A project set up for nvm,
fnm, or n gets the right <code>node</code> without further setup:</p>
<pre><code class="hljs language-sh"><span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">cd</span> ~/projects/legacy-app
<span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">cat</span> .nvmrc       <span class="hljs-comment"># 20</span>
<span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">node</span> <span class="hljs-flag">--version</span>   <span class="hljs-comment"># v20.x.x</span>
</code></pre>
<p>The nearest directory with a Node.js runtime declaration decides the version.
Within one directory, <code>package.json</code> takes precedence over <code>.node-version</code>,
which takes precedence over <code>.nvmrc</code>. An <code>.nvmrc</code> value that only nvm can act
on, such as <code>system</code> or a custom alias, is ignored. See
<a href="https://pnpm.io/global-packages#nvmrc-and-node-version"><code>.nvmrc</code> and <code>.node-version</code></a>.</p>
<h3 id="pnpm-install---allow-build"><code>pnpm install --allow-build</code><a class="hash-link" href="#pnpm-install---allow-build" aria-label="Direct link to pnpm install --allow-build">#</a></h3>
<p><a href="https://pnpm.io/cli/install#--allow-build"><code>pnpm install --allow-build</code></a> allows or denies a
package's lifecycle scripts and records the decision in <code>allowBuilds</code>, as
<code>pnpm add --allow-build</code> already did
(<a href="https://github.com/pnpm/pnpm/issues/15388">#15388</a>):</p>
<pre><code class="hljs language-sh"><span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">pnpm</span> install <span class="hljs-flag">--allow-build</span>=esbuild <span class="hljs-flag">--allow-build</span>=<span class="hljs-string">'!core-js'</span>
</code></pre>
<h3 id="waiting-for-published-packages">Waiting for published packages<a class="hash-link" href="#waiting-for-published-packages" aria-label="Direct link to Waiting for published packages">#</a></h3>
<p><a href="https://pnpm.io/cli/publish#--publish-wait-timeout-milliseconds"><code>pnpm publish --publish-wait-timeout &#x3C;milliseconds></code></a>
waits for the published versions and their tarballs to become available from
the registry. A recursive publish confirms each package before it publishes the
packages that depend on it, so a dependent never points at a version its
consumers cannot fetch yet. If confirmation times out, the command fails. Set
a default in <code>pnpm-workspace.yaml</code>:</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">publishWaitTimeout:</span> <span class="hljs-number">60000</span>
</code></pre></div>
<p>When <code>pnpm publish -r --report-summary</code> fails after some uploads were accepted,
the summary file now lists those packages.</p>
<h3 id="pnpm-workspaceyaml-from-the-workspaces-field"><code>pnpm-workspace.yaml</code> from the <code>workspaces</code> field<a class="hash-link" href="#pnpm-workspaceyaml-from-the-workspaces-field" aria-label="Direct link to pnpm-workspace.yaml from the workspaces field">#</a></h3>
<p>In a repository with no <code>pnpm-workspace.yaml</code>, <code>pnpm install</code> now creates one
from the <code>workspaces</code> field of the root <code>package.json</code> and links the listed
projects on that same install
(<a href="https://github.com/pnpm/pnpm/issues/2255">#2255</a>). An existing
<code>pnpm-workspace.yaml</code> is never changed, and nothing is created with
<code>--ignore-workspace</code>. If the <code>workspaces</code> field later differs from <code>packages</code>
in <code>pnpm-workspace.yaml</code>, pnpm prints a warning. See
<a href="https://pnpm.io/workspaces#converting-from-the-workspaces-field">Converting from the <code>workspaces</code> field</a>.</p>
<h3 id="--force-keeps-skipping-foreign-optional-dependencies"><code>--force</code> keeps skipping foreign optional dependencies<a class="hash-link" href="#--force-keeps-skipping-foreign-optional-dependencies" aria-label="Direct link to --force keeps skipping foreign optional dependencies">#</a></h3>
<p><code>pnpm install --force</code> now keeps skipping optional dependencies whose <code>os</code>,
<code>cpu</code>, or <code>libc</code> do not match the host
(<a href="https://github.com/pnpm/pnpm/issues/6133">#6133</a>). It still refetches every
package and lifts <code>engineStrict</code>. The new
<a href="https://pnpm.io/settings/dependency-resolution#forceignoresplatform"><code>forceIgnoresPlatform</code></a>
setting restores the previous behaviour:</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">forceIgnoresPlatform:</span> <span class="hljs-literal">true</span>
</code></pre></div>
<h3 id="other-changes">Other changes<a class="hash-link" href="#other-changes" aria-label="Direct link to Other changes">#</a></h3>
<ul>
<li>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. It used to wait up to five minutes and then use the
shared install directory without the lock. <code>pnpm store prune</code> removes any
private copy a killed process left behind
(<a href="https://github.com/pnpm/pnpm/issues/15413">#15413</a>).</li>
<li>pnpm now keeps the blank lines between entries of <code>package.json</code> when it
updates the file (<a href="https://github.com/pnpm/pnpm/issues/5602">#5602</a>).</li>
<li>pnpm now reads and updates <code>package.json5</code> manifests, keeping their comments.
Workspace discovery prefers <code>package.json</code>, then <code>package.json5</code>, then
<code>package.yaml</code> (<a href="https://github.com/pnpm/pnpm/issues/15129">#15129</a>).</li>
<li><code>pnpm init --bare</code> creates a <code>package.json</code> with only the required fields
(<a href="https://github.com/pnpm/pnpm/issues/15538">#15538</a>).</li>
<li>The <code>reporter</code> setting is now honored from <code>pnpm-workspace.yaml</code>, the global
configuration, and <code>PNPM_CONFIG_REPORTER</code>
(<a href="https://github.com/pnpm/pnpm/issues/4879">#4879</a>).</li>
<li>Configuration files support npm's <code>${VAR?}</code> placeholder, which expands to an
empty string without a warning when <code>VAR</code> is unset
(<a href="https://github.com/pnpm/pnpm/issues/14404">#14404</a>).</li>
<li><code>publishConfig["@scope:registry"]</code> now sets the publish registry of a scoped
package (<a href="https://github.com/pnpm/pnpm/issues/12071">#12071</a>).</li>
<li>Node.js runtime resolution supports Windows ARM64
(<a href="https://github.com/pnpm/pnpm/issues/7123">#7123</a>).</li>
</ul>
<h2 id="security">Security<a class="hash-link" href="#security" aria-label="Direct link to Security">#</a></h2>
<ul>
<li>pnpm no longer expands environment variables in a <code>userAgent</code> set in a
project's <code>pnpm-workspace.yaml</code>. Such a <code>userAgent</code> is ignored. Before, pnpm
sent the variable's value to the configured registry
(<a href="https://github.com/pnpm/pnpm/issues/15415">#15415</a>).</li>
<li>On Nix, a dependency's bin named like a system utility such as <code>sed</code> can no
longer redirect a POSIX bin shim or the <code>pnpm</code>, <code>pn</code>, <code>pnpx</code>, and <code>pnx</code>
launchers. Installing again replaces the shims already in <code>node_modules</code>
(<a href="https://github.com/pnpm/pnpm/issues/14883">#14883</a>).</li>
<li>pnpm no longer treats manifests inside its store, cache, state, or modules
directories as workspace projects. A <code>storeDir</code> inside the workspace could
let lifecycle scripts of packages in the store run without <code>allowBuilds</code>
approval (<a href="https://github.com/pnpm/pnpm/issues/15033">#15033</a>).</li>
<li>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
(<a href="https://github.com/pnpm/pnpm/issues/15483">#15483</a>).</li>
</ul>
<h2 id="patch-changes">Patch Changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch Changes">#</a></h2>
<h3 id="installing">Installing<a class="hash-link" href="#installing" aria-label="Direct link to Installing">#</a></h3>
<ul>
<li>Fixed running out of memory when many packages share a missing peer
dependency, mostly with <code>autoInstallPeers: false</code>
(<a href="https://github.com/pnpm/pnpm/issues/15362">#15362</a>).</li>
<li>pnpm no longer hangs for up to 5 minutes after a pnpm process was killed
while setting up the pinned pnpm version. A lock whose owner is gone is taken
over at once (<a href="https://github.com/pnpm/pnpm/issues/15360">#15360</a>).</li>
<li>Requests to a server whose TLS certificate fails verification now fail at
once instead of being retried for more than a minute
(<a href="https://github.com/pnpm/pnpm/issues/9134">#9134</a>).</li>
<li>On macOS, pnpm falls back to its bundled CA roots when system trust
evaluation is unavailable, such as in a sandbox
(<a href="https://github.com/pnpm/pnpm/issues/15329">#15329</a>).</li>
<li>A warm install uses less CPU while linking packages. On Windows, it could
take several times longer than with pnpm 11
(<a href="https://github.com/pnpm/pnpm/issues/15439">#15439</a>).</li>
<li><code>pnpm install</code> now fetches committed submodules of git dependencies
(<a href="https://github.com/pnpm/pnpm/issues/1470">#1470</a>).</li>
<li>A local tarball replaced at the same path is reinstalled, and
<code>--frozen-lockfile</code> rejects it
(<a href="https://github.com/pnpm/pnpm/issues/2437">#2437</a>,
<a href="https://github.com/pnpm/pnpm/issues/1889">#1889</a>).</li>
<li>Installs that skip <code>devDependencies</code> no longer run <code>pnpm:devPreinstall</code> or
<code>prepare</code> scripts (<a href="https://github.com/pnpm/pnpm/issues/7065">#7065</a>).</li>
<li><code>--prod</code> installs no longer install a devDependency that only satisfies an
optional peer of a production dependency
(<a href="https://github.com/pnpm/pnpm/issues/15344">#15344</a>).</li>
<li><code>pnpm fetch</code> also installs the pnpm version <code>pnpm-lock.yaml</code> pins, so a later
<code>pnpm install --offline</code> can switch to it
(<a href="https://github.com/pnpm/pnpm/issues/11808">#11808</a>).</li>
<li><code>pnpm install</code> no longer adds <code>allowBuilds</code> placeholders to
<code>pnpm-workspace.yaml</code> in CI or without a terminal
(<a href="https://github.com/pnpm/pnpm/issues/11574">#11574</a>).</li>
<li>pnpm detects the same CI environments as pnpm 11, including AWS CodeBuild.</li>
</ul>
<h3 id="resolving-and-linking">Resolving and linking<a class="hash-link" href="#resolving-and-linking" aria-label="Direct link to Resolving and linking">#</a></h3>
<ul>
<li>A dependency that a package also declares as an optional peer dependency is
installed again, for example <code>lightningcss</code> in some vite builds
(<a href="https://github.com/pnpm/pnpm/issues/8912">#8912</a>).</li>
<li>Removing an entry from <code>overrides</code> now re-resolves the packages it targeted
(<a href="https://github.com/pnpm/pnpm/issues/4587">#4587</a>).</li>
<li>With <code>trustPolicy: no-downgrade</code>, pnpm resolves the newest matching version
that is not a trust downgrade instead of failing
(<a href="https://github.com/pnpm/pnpm/issues/14176">#14176</a>).</li>
<li><code>autoDedupe</code> and <code>pnpm dedupe</code> move transitive dependencies to the version a
<code>catalog:</code> dependency pins, and <code>pnpm dedupe</code> no longer alternates between two
lockfiles when an <code>npm:</code> alias is involved
(<a href="https://github.com/pnpm/pnpm/issues/15588">#15588</a>).</li>
<li><code>pnpm install --ignore-pnpmfile</code> keeps <code>pnpmfileChecksum</code> in an up-to-date
lockfile (<a href="https://github.com/pnpm/pnpm/issues/10944">#10944</a>).</li>
<li>With <code>nodeLinker: hoisted</code>, <code>hoistWorkspacePackages</code> links selected workspace
projects and their bins into the root <code>node_modules</code>
(<a href="https://github.com/pnpm/pnpm/issues/7553">#7553</a>), and removed packages'
commands are removed from <code>node_modules/.bin</code>
(<a href="https://github.com/pnpm/pnpm/issues/7568">#7568</a>).</li>
<li>Installing the <code>node</code> package on Windows works again
(<a href="https://github.com/pnpm/pnpm/issues/15501">#15501</a>).</li>
</ul>
<h3 id="workspaces-and-filtering">Workspaces and filtering<a class="hash-link" href="#workspaces-and-filtering" aria-label="Direct link to Workspaces and filtering">#</a></h3>
<ul>
<li>Workspace projects reached through a symlink are installed
(<a href="https://github.com/pnpm/pnpm/issues/1044">#1044</a>).</li>
<li>A <code>catalog:</code> dependency whose entry points at a workspace project counts as a
workspace dependency for ordering and filtering
(<a href="https://github.com/pnpm/pnpm/issues/15587">#15587</a>).</li>
<li><code>workspace:</code> ranges match projects with non-semver versions such as <code>1.0</code>
(<a href="https://github.com/pnpm/pnpm/issues/4567">#4567</a>) and with build metadata
(<a href="https://github.com/pnpm/pnpm/issues/6483">#6483</a>).</li>
<li><code>--frozen-lockfile</code> fails when the lockfile lists a workspace project that is
missing (<a href="https://github.com/pnpm/pnpm/issues/7667">#7667</a>).</li>
<li>Injected workspace packages get the output of their own <code>prepare</code>,
<code>install</code>, and <code>postinstall</code> scripts
(<a href="https://github.com/pnpm/pnpm/issues/9464">#9464</a>).</li>
<li>A custom <code>modulesDir</code> works with several path segments, with
<code>nodeLinker: hoisted</code>, and on the up-to-date fast path
(<a href="https://github.com/pnpm/pnpm/issues/15484">#15484</a>).</li>
<li>pnpm warns when a workspace install covers a project that has its own
<code>pnpm-workspace.yaml</code> (<a href="https://github.com/pnpm/pnpm/issues/11724">#11724</a>).</li>
<li><a href="https://pnpm.io/filtering#--filter-since"><code>[&#x3C;since>]</code></a> compares against the merge base,
and selects projects whose catalog versions changed
(<a href="https://github.com/pnpm/pnpm/issues/9907">#9907</a>,
<a href="https://github.com/pnpm/pnpm/issues/8718">#8718</a>).</li>
<li><a href="https://pnpm.io/filtering#excluding"><code>--filter</code> selectors</a> are evaluated in order, so a
later inclusion can re-include an excluded project
(<a href="https://github.com/pnpm/pnpm/issues/9354">#9354</a>).</li>
</ul>
<h3 id="adding-updating-and-removing">Adding, updating, and removing<a class="hash-link" href="#adding-updating-and-removing" aria-label="Direct link to Adding, updating, and removing">#</a></h3>
<ul>
<li><code>pnpm add</code> saves <code>package.json</code> before running lifecycle scripts
(<a href="https://github.com/pnpm/pnpm/issues/8627">#8627</a>) and saves a requested
exact version (<a href="https://github.com/pnpm/pnpm/issues/6040">#6040</a>).</li>
<li><code>pnpm add &#x3C;pkg>@&#x3C;version></code> and <code>pnpm update &#x3C;pkg>@&#x3C;version></code> move a catalog
entry onto the named version
(<a href="https://github.com/pnpm/pnpm/issues/13715">#13715</a>).</li>
<li><code>pnpm update</code> keeps a range without a save prefix, such as <code>&#x3C;= 3.0.0</code>, when
the new version still satisfies it
(<a href="https://github.com/pnpm/pnpm/issues/6714">#6714</a>).</li>
<li><code>pnpm update &#x3C;pkg></code> can move off a locked version the registry no longer
serves (<a href="https://github.com/pnpm/pnpm/issues/9953">#9953</a>).</li>
<li><a href="https://pnpm.io/cli/unlink"><code>pnpm unlink</code></a> removes the <code>link:</code> dependency that
<code>pnpm link &#x3C;dir></code> added (<a href="https://github.com/pnpm/pnpm/issues/4219">#4219</a>).</li>
<li><code>pnpm install</code> prunes unreferenced catalog entries when <code>catalogPrune: true</code>
is set (<a href="https://github.com/pnpm/pnpm/issues/15273">#15273</a>).</li>
<li><code>pnpm import</code> converts Yarn's <code>patch:</code> protocol
(<a href="https://github.com/pnpm/pnpm/issues/10278">#10278</a>).</li>
<li><code>pnpm patch</code> and <code>pnpm patch-commit</code> work with
<code>sharedWorkspaceLockfile: false</code>
(<a href="https://github.com/pnpm/pnpm/issues/9926">#9926</a>).</li>
</ul>
<h3 id="running-scripts">Running scripts<a class="hash-link" href="#running-scripts" aria-label="Direct link to Running scripts">#</a></h3>
<ul>
<li>A script run without a terminal ends when pnpm is killed, for example by
Playwright's <code>webServer</code>
(<a href="https://github.com/pnpm/pnpm/issues/15555">#15555</a>).</li>
<li><code>pnpm --filter &#x3C;project> &#x3C;command></code> and <code>pnpm -r &#x3C;command></code> run a command
from the projects' dependencies when no script has that name
(<a href="https://github.com/pnpm/pnpm/issues/10151">#10151</a>).</li>
<li><code>pnpm exec</code> and <code>pnpm dlx</code> set <code>npm_execpath</code>, <code>INIT_CWD</code>,
<code>npm_node_execpath</code>, and <code>NODE</code>
(<a href="https://github.com/pnpm/pnpm/issues/7037">#7037</a>), and <code>pnpm exec</code> sets
<code>PWD</code> (<a href="https://github.com/pnpm/pnpm/issues/1550">#1550</a>).</li>
<li><code>pnpm restart</code> runs <code>stop</code> and <code>start</code> when there is no <code>restart</code> script
(<a href="https://github.com/pnpm/pnpm/issues/4750">#4750</a>).</li>
<li><code>pnpm dlx</code> keeps a separate cache entry per Node.js major version
(<a href="https://github.com/pnpm/pnpm/issues/8611">#8611</a>).</li>
<li><code>pnpm pipeline</code> runs outside a Git work tree, without caching
(<a href="https://github.com/pnpm/pnpm/issues/15601">#15601</a>).</li>
<li>A <code>runtime:</code> range such as <code>^22.18.0 || ^24.0.0</code> installs the runtime
instead of the <code>node</code> npm package
(<a href="https://github.com/pnpm/pnpm/issues/14817">#14817</a>).</li>
</ul>
<h3 id="publishing-and-deploying">Publishing and deploying<a class="hash-link" href="#publishing-and-deploying" aria-label="Direct link to Publishing and deploying">#</a></h3>
<ul>
<li><code>pnpm publish</code> resolves <code>workspace:</code> dependencies without <code>node_modules</code>
(<a href="https://github.com/pnpm/pnpm/issues/6567">#6567</a>).</li>
<li><code>pnpm pack</code> and <code>pnpm publish</code> include bundled dependencies with the isolated
linker (<a href="https://github.com/pnpm/pnpm/issues/1643">#1643</a>), keep symlinks
inside the package (<a href="https://github.com/pnpm/pnpm/issues/8208">#8208</a>), and
preserve executable permissions.</li>
<li><a href="https://pnpm.io/cli/deploy#what-the-deployed-project-gets"><code>pnpm deploy</code></a> copies the root
<code>packageManager</code> field (<a href="https://github.com/pnpm/pnpm/issues/9079">#9079</a>),
honors <code>virtualStoreDir</code> (<a href="https://github.com/pnpm/pnpm/issues/8787">#8787</a>),
and skips the deployed project's <code>prepare</code> script
(<a href="https://github.com/pnpm/pnpm/issues/7282">#7282</a>).</li>
</ul>
<h3 id="global-packages-and-pnpm-versions">Global packages and pnpm versions<a class="hash-link" href="#global-packages-and-pnpm-versions" aria-label="Direct link to Global packages and pnpm versions">#</a></h3>
<ul>
<li>Global commands run with the pnpm you invoked, even in a project that pins
another version (<a href="https://github.com/pnpm/pnpm/issues/14531">#14531</a>).</li>
<li><code>pnpm self-update</code> inside a project that pins pnpm also updates the global
pnpm (<a href="https://github.com/pnpm/pnpm/issues/14747">#14747</a>) and no longer
leaves the previous <code>@pnpm/exe</code> behind
(<a href="https://github.com/pnpm/pnpm/issues/14709">#14709</a>).</li>
<li><code>pnpm setup</code> no longer fails with <code>Text file busy</code>
(<a href="https://github.com/pnpm/pnpm/issues/15494">#15494</a>) and no longer deletes
lines near the pnpm block of a shell startup file
(<a href="https://github.com/pnpm/pnpm/issues/7067">#7067</a>).</li>
</ul>
<h3 id="windows-and-wsl">Windows and WSL<a class="hash-link" href="#windows-and-wsl" aria-label="Direct link to Windows and WSL">#</a></h3>
<ul>
<li>Installs and <code>pnpm clean</code> wait for a file held open by another process
instead of failing at once, on Windows
(<a href="https://github.com/pnpm/pnpm/issues/15081">#15081</a>) and on Windows drives
under WSL (<a href="https://github.com/pnpm/pnpm/issues/6155">#6155</a>).</li>
<li>Saving <code>pnpm-lock.yaml</code> is retried while another process holds it open
(<a href="https://github.com/pnpm/pnpm/issues/9461">#9461</a>).</li>
<li>Trailing dots and spaces are escaped in <code>node_modules/.pnpm</code> directory names
(<a href="https://github.com/pnpm/pnpm/issues/8101">#8101</a>).</li>
<li>Bin shims run from Git Bash, MSYS2, or Cygwin pass <code>NODE_PATH</code> as Windows
paths (<a href="https://github.com/pnpm/pnpm/issues/3360">#3360</a>).</li>
</ul>
<h3 id="inspecting-dependencies">Inspecting dependencies<a class="hash-link" href="#inspecting-dependencies" aria-label="Direct link to Inspecting dependencies">#</a></h3>
<ul>
<li><code>pnpm audit</code> honors <code>--filter</code>
(<a href="https://github.com/pnpm/pnpm/issues/10982">#10982</a>) and lists a dependency
path from every affected workspace project
(<a href="https://github.com/pnpm/pnpm/issues/12200">#12200</a>).</li>
<li><code>pnpm list</code> and <code>pnpm licenses list</code> inside a workspace project list only
that project (<a href="https://github.com/pnpm/pnpm/issues/14494">#14494</a>,
<a href="https://github.com/pnpm/pnpm/issues/5689">#5689</a>).</li>
<li><code>pnpm list --only-projects</code> prints every selected project
(<a href="https://github.com/pnpm/pnpm/issues/9770">#9770</a>).</li>
<li><code>pnpm licenses list</code> works with <code>sharedWorkspaceLockfile: false</code>
(<a href="https://github.com/pnpm/pnpm/issues/10140">#10140</a>) and with
<code>nodeLinker: hoisted</code> (<a href="https://github.com/pnpm/pnpm/issues/8589">#8589</a>).</li>
<li><code>pnpm store status</code> no longer reports packages with build scripts as modified
(<a href="https://github.com/pnpm/pnpm/issues/15383">#15383</a>).</li>
</ul>
<p>See the <a href="https://github.com/pnpm/pnpm/releases/tag/v12.7.0">v12.7.0 release notes</a>
for the complete list of changes.</p>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 11.28]]></title>
      <link>https://pnpm.io/blog/releases/11.28</link>
      <guid>https://pnpm.io/blog/releases/11.28</guid>
      <pubDate>Fri, 25 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 11.28 adds the forceIgnoresPlatform setting and pnpm update --peer, and brings a large batch of fixes from pnpm 12 to the JavaScript CLI: pnpm deploy, --filter, nodeLinker: hoisted, and custom modulesDir setups all behave better. It also carries security fixes for shell completion, bin...]]></description>
      <content:encoded><![CDATA[<p>pnpm 11.28 adds the <code>forceIgnoresPlatform</code> setting and <code>pnpm update --peer</code>,
and brings a large batch of fixes from pnpm 12 to the JavaScript CLI: <code>pnpm deploy</code>, <code>--filter</code>, <code>nodeLinker: hoisted</code>, and custom <code>modulesDir</code> setups all
behave better. It also carries security fixes for shell completion, bin shims
on Nix, lifecycle scripts inside a custom <code>modulesDir</code>, and <code>userAgent</code>
placeholders in <code>pnpm-workspace.yaml</code>.</p>
<h2 id="minor-changes">Minor Changes<a class="hash-link" href="#minor-changes" aria-label="Direct link to Minor Changes">#</a></h2>
<h3 id="forceignoresplatform"><code>forceIgnoresPlatform</code><a class="hash-link" href="#forceignoresplatform" aria-label="Direct link to forceIgnoresPlatform">#</a></h3>
<p><code>pnpm install --force</code> installs every optional dependency, including those
built for another <code>os</code>, <code>cpu</code>, or <code>libc</code>. The new
<a href="https://pnpm.io/11.x/settings/dependency-resolution#forceignoresplatform"><code>forceIgnoresPlatform</code></a>
setting turns that part off, so <code>--force</code> still refetches packages and lifts
<code>engineStrict</code> but skips optional dependencies for other platforms
(<a href="https://github.com/pnpm/pnpm/issues/6133">#6133</a>):</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">forceIgnoresPlatform:</span> <span class="hljs-literal">false</span>
</code></pre></div>
<p>The default stays <code>true</code> in pnpm 11. In pnpm 12, <code>--force</code> skips such
dependencies by default.</p>
<h3 id="pnpm-update---peer"><code>pnpm update --peer</code><a class="hash-link" href="#pnpm-update---peer" aria-label="Direct link to pnpm update --peer">#</a></h3>
<p><a href="https://pnpm.io/11.x/cli/update#--peer"><code>pnpm update --peer</code></a> also updates the ranges in
<code>peerDependencies</code>, which <code>pnpm update</code> otherwise leaves alone
(<a href="https://github.com/pnpm/pnpm/issues/8081">#8081</a>):</p>
<pre><code class="hljs language-sh"><span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">pnpm</span> update <span class="hljs-flag">--peer</span> react
</code></pre>
<h2 id="security">Security<a class="hash-link" href="#security" aria-label="Direct link to Security">#</a></h2>
<ul>
<li>pnpm no longer expands environment variables in a <code>userAgent</code> set in a
project's <code>pnpm-workspace.yaml</code>. Such a <code>userAgent</code> is ignored. Before, pnpm
sent the variable's value to the configured registry
(<a href="https://github.com/pnpm/pnpm/issues/15415">#15415</a>).</li>
<li>Packages inside a custom <code>modulesDir</code> are no longer treated as workspace
projects. With a <code>modulesDir</code> such as <code>vendor</code> and a <code>packages</code> pattern such
as <code>**</code>, a repeat install ran lifecycle scripts that <code>allowBuilds</code> had not
approved (<a href="https://github.com/pnpm/pnpm/pull/15412">#15412</a>).</li>
<li>On Nix, a dependency's bin named like a system utility such as <code>sed</code> can no
longer redirect a POSIX bin shim or the <code>pnpm</code>, <code>pn</code>, <code>pnpx</code>, and <code>pnx</code>
launchers. Install again to replace the shims already in <code>node_modules</code>
(<a href="https://github.com/pnpm/pnpm/issues/14883">#14883</a>).</li>
<li>Shell completion omits candidates containing control or invisible formatting
characters, so package and script names can no longer inject completion
records or terminal escape sequences.</li>
<li><code>pnpm runtime set</code> and <code>pnpm env use</code> no longer run a script that another
package installed under the name <code>pnpm</code> or <code>pn</code> as though it were pnpm.</li>
<li><code>pnpm store prune</code> leaves a <code>dlx</code> cache root that is a symlink or Windows
junction untouched.</li>
</ul>
<h2 id="notable-fixes">Notable fixes<a class="hash-link" href="#notable-fixes" aria-label="Direct link to Notable fixes">#</a></h2>
<h3 id="installing">Installing<a class="hash-link" href="#installing" aria-label="Direct link to Installing">#</a></h3>
<ul>
<li>Requests to a server whose TLS certificate fails verification now fail at
once, naming the certificate problem, instead of retrying for more than a
minute (<a href="https://github.com/pnpm/pnpm/issues/9134">#9134</a>).</li>
<li>Git dependencies fetched over SSH no longer appear to hang on a hidden
passphrase or host key prompt: pnpm runs ssh in batch mode, so load a key that
needs a passphrase into an SSH agent first
(<a href="https://github.com/pnpm/pnpm/issues/2227">#2227</a>). Committed submodules of
git dependencies are now fetched
(<a href="https://github.com/pnpm/pnpm/issues/1470">#1470</a>).</li>
<li>bzip2-compressed tarballs can be installed
(<a href="https://github.com/pnpm/pnpm/issues/6761">#6761</a>).</li>
<li>The root project's <code>preinstall</code> script now runs before dependencies are
resolved and linked, so a guard such as <code>npx only-allow pnpm</code> stops the
install before <code>node_modules</code> is populated
(<a href="https://github.com/pnpm/pnpm/issues/3760">#3760</a>).</li>
<li>Installs that skip <code>devDependencies</code>, such as <code>pnpm install --prod</code>, no
longer run <code>pnpm:devPreinstall</code> or <code>prepare</code>
(<a href="https://github.com/pnpm/pnpm/issues/7065">#7065</a>), and they no longer
install a devDependency that only satisfies an optional peer of a production
dependency (<a href="https://github.com/pnpm/pnpm/issues/15344">#15344</a>).</li>
<li><code>pnpm install</code> no longer adds <code>allowBuilds</code> placeholders to
<code>pnpm-workspace.yaml</code> in CI or without a terminal
(<a href="https://github.com/pnpm/pnpm/issues/11574">#11574</a>).</li>
<li><code>pnpm install --engine-strict</code> respects <code>engines</code> relaxed by <code>readPackage</code>
hooks (<a href="https://github.com/pnpm/pnpm/issues/15482">#15482</a>), and the project's
pnpmfile now runs after the pnpmfiles of config dependency plugins
(<a href="https://github.com/pnpm/pnpm/issues/9891">#9891</a>).</li>
<li><code>pnpm install</code> and <code>pnpm run</code> reinstall a project that was moved together with
its <code>node_modules</code> (<a href="https://github.com/pnpm/pnpm/issues/9512">#9512</a>), and
relink a direct dependency whose link points to a missing target
(<a href="https://github.com/pnpm/pnpm/issues/9758">#9758</a>).</li>
<li><code>pnpm fetch</code> also installs the pnpm version that <code>pnpm-lock.yaml</code> pins, so a
later offline install can switch to it
(<a href="https://github.com/pnpm/pnpm/issues/11808">#11808</a>).</li>
<li>pnpm no longer crashes when <code>TMPDIR</code>, <code>TEMP</code>, or <code>TMP</code> points to a missing
directory (<a href="https://github.com/pnpm/pnpm/issues/4960">#4960</a>).</li>
</ul>
<h3 id="resolving-and-linking">Resolving and linking<a class="hash-link" href="#resolving-and-linking" aria-label="Direct link to Resolving and linking">#</a></h3>
<ul>
<li>A dependency that a package also declares as an optional peer, such as
<code>lightningcss</code> in some vite builds, is now installed
(<a href="https://github.com/pnpm/pnpm/issues/8912">#8912</a>).</li>
<li>Removing an <code>overrides</code> entry re-resolves the packages it targeted
(<a href="https://github.com/pnpm/pnpm/issues/4587">#4587</a>).</li>
<li>With <code>trustPolicy: no-downgrade</code>, pnpm resolves the newest matching version
that is not a trust downgrade instead of failing
(<a href="https://github.com/pnpm/pnpm/issues/14176">#14176</a>).</li>
<li>Ranges resolve to the newest matching version that is not deprecated
(<a href="https://github.com/pnpm/pnpm/issues/15128">#15128</a>).</li>
<li><code>pnpm add</code> followed by <code>pnpm remove</code> of the same package leaves
<code>pnpm-lock.yaml</code> unchanged; unrelated transitive dependencies no longer move
(<a href="https://github.com/pnpm/pnpm/issues/11859">#11859</a>).</li>
<li><code>pnpm install --frozen-lockfile</code> succeeds when an optional dependency was
unresolvable and skipped by the install that wrote the lockfile
(<a href="https://github.com/pnpm/pnpm/issues/3960">#3960</a>).</li>
<li><code>hoistWorkspacePackages</code> works with <code>nodeLinker: hoisted</code>
(<a href="https://github.com/pnpm/pnpm/issues/7553">#7553</a>), and hoisted workspace
projects are hoisted on every install, not only after <code>node_modules</code> is
deleted (<a href="https://github.com/pnpm/pnpm/issues/3642">#3642</a>).</li>
<li>A workspace package's bin that a build script creates after install is now
linked (<a href="https://github.com/pnpm/pnpm/issues/10007">#10007</a>).</li>
</ul>
<h3 id="workspaces-and-filtering">Workspaces and filtering<a class="hash-link" href="#workspaces-and-filtering" aria-label="Direct link to Workspaces and filtering">#</a></h3>
<ul>
<li><a href="https://pnpm.io/11.x/filtering#--filter-since"><code>--filter "[&#x3C;since>]"</code></a> compares against the
merge base with <code>&#x3C;since></code> (<a href="https://github.com/pnpm/pnpm/issues/9907">#9907</a>),
and selects projects that use a changed catalog entry
(<a href="https://github.com/pnpm/pnpm/issues/8718">#8718</a>).</li>
<li><code>--filter</code> selectors are applied in order, so a later inclusion can re-include
a project that an earlier exclusion removed
(<a href="https://github.com/pnpm/pnpm/issues/9354">#9354</a>).</li>
<li><code>pnpm --filter &#x3C;project> &#x3C;command></code> and <code>pnpm -r &#x3C;command></code> run a command
installed in the projects' dependencies when none of them has a script by
that name (<a href="https://github.com/pnpm/pnpm/issues/10151">#10151</a>).</li>
<li>Workspace projects reached through a symlink are installed
(<a href="https://github.com/pnpm/pnpm/issues/1044">#1044</a>).</li>
<li>A <code>catalog:</code> dependency that points at a workspace project counts as a
workspace dependency for ordering and filtering
(<a href="https://github.com/pnpm/pnpm/issues/15587">#15587</a>).</li>
<li><code>workspace:</code> ranges match projects whose version is not valid semver, such as
<code>1.0</code> (<a href="https://github.com/pnpm/pnpm/issues/4567">#4567</a>), or carries build
metadata (<a href="https://github.com/pnpm/pnpm/issues/6483">#6483</a>).</li>
<li><code>pnpm install --filter</code> with <code>nodeLinker: hoisted</code> installs only the selected
projects' dependencies (<a href="https://github.com/pnpm/pnpm/issues/8882">#8882</a>).</li>
<li>pnpm warns when a workspace install covers a project with its own
<code>pnpm-workspace.yaml</code>, whose settings do not apply
(<a href="https://github.com/pnpm/pnpm/issues/11724">#11724</a>).</li>
</ul>
<h3 id="custom-modulesdir">Custom <code>modulesDir</code><a class="hash-link" href="#custom-modulesdir" aria-label="Direct link to Custom modulesDir">#</a></h3>
<ul>
<li><code>pnpm run</code>, <code>pnpm exec</code>, <code>pnpm version</code> hooks, and install-time lifecycle
scripts find commands in a custom <code>modulesDir</code>, and tools installed there can
load CommonJS plugins installed next to them
(<a href="https://github.com/pnpm/pnpm/issues/3604">#3604</a>). See
<a href="https://pnpm.io/11.x/settings/other#extendnodepath"><code>extendNodePath</code></a>.</li>
<li>A repeat install in a workspace with a custom <code>modulesDir</code> takes the
up-to-date fast path.</li>
</ul>
<h3 id="adding-updating-and-removing">Adding, updating, and removing<a class="hash-link" href="#adding-updating-and-removing" aria-label="Direct link to Adding, updating, and removing">#</a></h3>
<ul>
<li><a href="https://pnpm.io/11.x/scripts#uninstall-scripts"><code>pnpm remove</code></a> runs the project's own
<code>preuninstall</code>, <code>uninstall</code>, and <code>postuninstall</code> scripts
(<a href="https://github.com/pnpm/pnpm/issues/3276">#3276</a>), and <code>pnpm remove -r</code>
fails before changing any manifest if a dependency is absent from every
selected project (<a href="https://github.com/pnpm/pnpm/issues/2319">#2319</a>).</li>
<li><a href="https://pnpm.io/11.x/cli/unlink"><code>pnpm unlink</code></a> removes the <code>link:</code> dependency that
<code>pnpm link &#x3C;dir></code> added (<a href="https://github.com/pnpm/pnpm/issues/4219">#4219</a>).</li>
<li><code>pnpm add</code> saves the requested exact version
(<a href="https://github.com/pnpm/pnpm/issues/6040">#6040</a>), writes <code>package.json</code>
before lifecycle scripts run
(<a href="https://github.com/pnpm/pnpm/issues/8627">#8627</a>), and keeps a specifier a
<code>readPackage</code> hook rewrote (<a href="https://github.com/pnpm/pnpm/issues/15156">#15156</a>).</li>
<li><code>pnpm update</code> keeps ranges such as <code>&#x3C;= 3.0.0</code> that the new version still
satisfies (<a href="https://github.com/pnpm/pnpm/issues/6714">#6714</a>), updates
auto-installed peers by name
(<a href="https://github.com/pnpm/pnpm/issues/10486">#10486</a>), and moves a package off
a locked version the registry no longer serves
(<a href="https://github.com/pnpm/pnpm/issues/9953">#9953</a>).</li>
<li><code>minimumReleaseAgeExcludePrune</code> and <code>trustPolicyExcludePrune</code> work with
<code>sharedWorkspaceLockfile: false</code>
(<a href="https://github.com/pnpm/pnpm/issues/14612">#14612</a>).</li>
<li><code>pnpm import</code> converts Yarn <code>patch:</code> dependencies into <code>patchedDependencies</code>
(<a href="https://github.com/pnpm/pnpm/issues/10278">#10278</a>).</li>
</ul>
<h3 id="scripts">Scripts<a class="hash-link" href="#scripts" aria-label="Direct link to Scripts">#</a></h3>
<ul>
<li>A script run without a terminal ends when pnpm is killed, as Playwright's
<code>webServer</code> does (<a href="https://github.com/pnpm/pnpm/issues/15555">#15555</a>).</li>
<li><code>pnpm exec</code> and <code>pnpm dlx</code> set <code>npm_execpath</code>, <code>INIT_CWD</code>,
<code>npm_node_execpath</code>, and <code>NODE</code> (<a href="https://github.com/pnpm/pnpm/issues/7037">#7037</a>),
and <code>pnpm exec</code> sets <code>PWD</code> (<a href="https://github.com/pnpm/pnpm/issues/1550">#1550</a>).</li>
<li><code>pnpm restart</code> runs <code>stop</code> and <code>start</code> when there is no <code>restart</code> script
(<a href="https://github.com/pnpm/pnpm/issues/4750">#4750</a>).</li>
<li><a href="https://pnpm.io/11.x/cli/install-test"><code>pnpm install-test</code></a> accepts <code>--no-bail</code>
(<a href="https://github.com/pnpm/pnpm/issues/3777">#3777</a>).</li>
<li><code>pnpm dlx</code> keeps a separate cache per Node.js major version
(<a href="https://github.com/pnpm/pnpm/issues/8611">#8611</a>).</li>
<li>A <code>runtime:</code> range containing <code>||</code> or a space installs the runtime instead of
the npm package of the same name
(<a href="https://github.com/pnpm/pnpm/issues/14817">#14817</a>).</li>
</ul>
<h3 id="publishing-packing-and-deploying">Publishing, packing, and deploying<a class="hash-link" href="#publishing-packing-and-deploying" aria-label="Direct link to Publishing, packing, and deploying">#</a></h3>
<ul>
<li><code>pnpm publish</code> resolves <code>workspace:</code> dependencies without <code>node_modules</code>
(<a href="https://github.com/pnpm/pnpm/issues/6567">#6567</a>) and honors
<a href="https://pnpm.io/11.x/cli/publish#registry"><code>publishConfig["@scope:registry"]</code></a>
(<a href="https://github.com/pnpm/pnpm/issues/12071">#12071</a>).</li>
<li><a href="https://pnpm.io/11.x/cli/pack"><code>pnpm pack</code></a> includes bundled dependencies with the isolated
linker (<a href="https://github.com/pnpm/pnpm/issues/1643">#1643</a>), keeps symlinks
inside the package (<a href="https://github.com/pnpm/pnpm/issues/8208">#8208</a>), and
preserves executable permissions.</li>
<li><a href="https://pnpm.io/11.x/cli/deploy#the-deployed-project"><code>pnpm deploy</code></a> copies the root
<code>packageManager</code> field (<a href="https://github.com/pnpm/pnpm/issues/9079">#9079</a>),
puts the virtual store at <code>virtualStoreDir</code>
(<a href="https://github.com/pnpm/pnpm/issues/8787">#8787</a>), and no longer runs the
deployed project's <code>prepare</code> script
(<a href="https://github.com/pnpm/pnpm/issues/7282">#7282</a>).</li>
</ul>
<h3 id="configuration-and-registries">Configuration and registries<a class="hash-link" href="#configuration-and-registries" aria-label="Direct link to Configuration and registries">#</a></h3>
<ul>
<li><code>.npmrc</code> files support npm's <code>${VAR?}</code> placeholder
(<a href="https://github.com/pnpm/pnpm/issues/14404">#14404</a>), and <code>_auth</code> tokens
from the global <code>config.yaml</code> or <code>pnpm_config__auth</code> expand environment
variables (<a href="https://github.com/pnpm/pnpm/issues/12828">#12828</a>).</li>
<li><code>pnpm login</code> logs back in to an existing user on registries without web
login, such as verdaccio (<a href="https://github.com/pnpm/pnpm/issues/12055">#12055</a>).</li>
<li><code>pnpm doctor</code> checks the configured default registry
(<a href="https://github.com/pnpm/pnpm/issues/15618">#15618</a>).</li>
<li>pnpm preserves CRLF line endings when it modifies manifests.</li>
</ul>
<h3 id="windows-and-wsl">Windows and WSL<a class="hash-link" href="#windows-and-wsl" aria-label="Direct link to Windows and WSL">#</a></h3>
<ul>
<li><code>pnpm install</code> in WSL waits out Windows file locks on <code>/mnt/c</code> and similar
drives (<a href="https://github.com/pnpm/pnpm/issues/6155">#6155</a>), and saving
<code>pnpm-lock.yaml</code> on Windows retries while another process holds it
(<a href="https://github.com/pnpm/pnpm/issues/9461">#9461</a>).</li>
<li><code>pnpm add</code> and <code>pnpm update</code> write <code>file:</code> and <code>link:</code> specifiers with forward
slashes on Windows (<a href="https://github.com/pnpm/pnpm/issues/7497">#7497</a>).</li>
<li>Node.js runtime resolution supports Windows ARM64
(<a href="https://github.com/pnpm/pnpm/issues/7123">#7123</a>).</li>
<li>Bin shims run from Git Bash, MSYS2, or Cygwin pass <code>NODE_PATH</code> as Windows
paths (<a href="https://github.com/pnpm/pnpm/issues/3360">#3360</a>).</li>
</ul>
<h3 id="inspecting-dependencies">Inspecting dependencies<a class="hash-link" href="#inspecting-dependencies" aria-label="Direct link to Inspecting dependencies">#</a></h3>
<ul>
<li><code>pnpm audit</code> honors <code>--filter</code>
(<a href="https://github.com/pnpm/pnpm/issues/10982">#10982</a>), lists a path from every
affected project (<a href="https://github.com/pnpm/pnpm/issues/12200">#12200</a>), and
<code>--fix=update</code> fixes npm-aliased dependencies
(<a href="https://github.com/pnpm/pnpm/issues/15155">#15155</a>).</li>
<li><code>pnpm list</code> and <code>pnpm licenses list</code> inside a workspace package list only that
package unless <code>--recursive</code> or <code>--filter</code> is given
(<a href="https://github.com/pnpm/pnpm/issues/14494">#14494</a>,
<a href="https://github.com/pnpm/pnpm/issues/5689">#5689</a>).</li>
<li><code>pnpm -r outdated --json</code> includes every outdated dependency when projects use
different versions of the same package
(<a href="https://github.com/pnpm/pnpm/issues/7693">#7693</a>).</li>
<li>The ignored build scripts warning and the update notice print as plain lines
in CI and non-terminal output
(<a href="https://github.com/pnpm/pnpm/issues/9421">#9421</a>).</li>
</ul>
<p>For the complete list of changes, see the <a href="https://github.com/pnpm/pnpm/releases/tag/v11.28.0">v11.28.0 release
notes</a>.</p>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.6]]></title>
      <link>https://pnpm.io/blog/releases/12.6</link>
      <guid>https://pnpm.io/blog/releases/12.6</guid>
      <pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 12.6 ships with automatic dependency deduplication, relocatable node_modules, --save-types for installing @types/* packages alongside their counterparts, package.yaml manifest editing, and file: / link: protocols in catalogs.]]></description>
      <content:encoded><![CDATA[<p>pnpm 12.6 ships with automatic dependency deduplication, relocatable
<code>node_modules</code>, <code>--save-types</code> for installing <code>@types/*</code> packages alongside
their counterparts, <code>package.yaml</code> manifest editing, and <code>file:</code> / <code>link:</code>
protocols in catalogs.</p>
<h2 id="automatic-deduplication">Automatic deduplication<a class="hash-link" href="#automatic-deduplication" aria-label="Direct link to Automatic deduplication">#</a></h2>
<p><a href="https://pnpm.io/settings/dependency-resolution#autodedupe"><code>autoDedupe</code></a> deduplicates
compatible dependency versions during installation
(<a href="https://github.com/pnpm/pnpm/issues/7258">#7258</a>). Enable it in
<code>pnpm-workspace.yaml</code>:</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">autoDedupe:</span> <span class="hljs-literal">true</span>
</code></pre></div>
<p>When a dependency appears at multiple versions and one satisfies every range,
pnpm picks that version for the whole workspace. Frozen installs leave the
lockfile unchanged.</p>
<h2 id="relocatable-node_modules">Relocatable <code>node_modules</code><a class="hash-link" href="#relocatable-node_modules" aria-label="Direct link to Relocatable node_modules">#</a></h2>
<p><code>pnpm install</code>, <code>pnpm run</code>, and <code>pnpm exec</code> on macOS and Linux now reuse a
<code>node_modules</code> directory and bin shims that moved or were copied together with
their project (<a href="https://github.com/pnpm/pnpm/issues/6937">#6937</a>). The first
command after the move checks the tree and records the new location, so project
commands in <code>node_modules/.bin</code> keep working.</p>
<h2 id="--save-types"><code>--save-types</code><a class="hash-link" href="#--save-types" aria-label="Direct link to --save-types">#</a></h2>
<p><a href="https://pnpm.io/cli/add#--save-types"><code>pnpm add --save-types</code></a> saves available <code>@types/*</code>
packages in <code>devDependencies</code> alongside registry dependencies
(<a href="https://github.com/pnpm/pnpm/issues/3868">#3868</a>):</p>
<pre><code class="hljs language-sh"><span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">pnpm</span> add express <span class="hljs-flag">--save-types</span>
<span class="hljs-comment"># saves express to dependencies, @types/express to devDependencies</span>
</code></pre>
<p>Packages that declare bundled TypeScript types are skipped. Set <code>saveTypes: true</code> in <code>pnpm-workspace.yaml</code> to enable this by default.</p>
<h2 id="packageyaml-manifests"><code>package.yaml</code> manifests<a class="hash-link" href="#packageyaml-manifests" aria-label="Direct link to package.yaml manifests">#</a></h2>
<p><code>package.yaml</code> manifests can now be updated by <code>pnpm add</code>, <code>pnpm update</code>,
<code>pnpm remove</code>, <code>pnpm pkg</code>, <code>pnpm link</code>, <code>pnpm set-script</code>, and <code>pnpm version</code>
(<a href="https://github.com/pnpm/pnpm/issues/2008">#2008</a>). Existing comments and key
order are preserved.</p>
<h2 id="file-and-link-catalogs"><code>file:</code> and <code>link:</code> catalogs<a class="hash-link" href="#file-and-link-catalogs" aria-label="Direct link to file: and link: catalogs">#</a></h2>
<p><a href="https://pnpm.io/catalogs">Catalog</a> entries can now use the <code>file:</code> and <code>link:</code> protocols
(<a href="https://github.com/pnpm/pnpm/issues/8642">#8642</a>). A relative path or bare
path in a catalog entry is measured from the directory holding
<code>pnpm-workspace.yaml</code>:</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">catalog:</span>
  <span class="hljs-attr">my-lib:</span> <span class="hljs-string">link:./packages/my-lib</span>
  <span class="hljs-attr">my-tarball:</span> <span class="hljs-string">file:./tarballs/foo.tgz</span>
</code></pre></div>
<h2 id="pnpm-tasks-status"><code>pnpm tasks status</code><a class="hash-link" href="#pnpm-tasks-status" aria-label="Direct link to pnpm tasks status">#</a></h2>
<p><a href="https://pnpm.io/cli/tasks"><code>pnpm tasks status</code></a> lists running and waiting tasks in each <a href="https://pnpm.io/workspace-task-orchestration#concurrencygroups">concurrency group</a>,
and waiting tasks now take available slots in order of descending <code>priority</code>,
with arrival order used only to break ties
(<a href="https://github.com/pnpm/pnpm/issues/15208">#15208</a>). 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 named <code>tasks</code> takes precedence; use
<code>pnpm pm tasks status</code> when that script exists.</p>
<h2 id="macos-time-machine-exclusions">macOS Time Machine exclusions<a class="hash-link" href="#macos-time-machine-exclusions" aria-label="Direct link to macOS Time Machine exclusions">#</a></h2>
<p><a href="https://pnpm.io/settings/other#macosbackupexcludemodulesdir"><code>macosBackup.excludeModulesDir</code></a>
and
<a href="https://pnpm.io/settings/other#macosbackupexcludestoredir"><code>macosBackup.excludeStoreDir</code></a> on
macOS can now exclude newly created modules, virtual-store, and package-store
directories from Time Machine
(<a href="https://github.com/pnpm/pnpm/issues/6440">#6440</a>). Set either to <code>true</code> in
global configuration or through the
<code>PNPM_CONFIG_MACOS_BACKUP_EXCLUDE_MODULES_DIR</code> and
<code>PNPM_CONFIG_MACOS_BACKUP_EXCLUDE_STORE_DIR</code> environment variables.</p>
<h2 id="smaller-additions">Smaller additions<a class="hash-link" href="#smaller-additions" aria-label="Direct link to Smaller additions">#</a></h2>
<ul>
<li><code>pnpm add --tilde</code> is now an alias for <code>--save-prefix=~</code>
(<a href="https://github.com/pnpm/pnpm/issues/12863">#12863</a>). The Yarn <code>-T</code>
shorthand is not supported.</li>
<li><a href="https://pnpm.io/settings/cli#progress"><code>progress</code></a> setting and <code>--no-progress</code> option turn
off dependency and download progress lines
(<a href="https://github.com/pnpm/pnpm/issues/14065">#14065</a>). Warnings, lifecycle
output, and the dependency summary are still printed.</li>
<li><code>pnpm cache prune</code> now also deletes registry metadata cache directories that
this version of pnpm can no longer read
(<a href="https://github.com/pnpm/pnpm/issues/15046">#15046</a>). <code>pnpm cache prune --dry-run</code> lists what it would delete without removing anything.</li>
</ul>
<h2 id="patch-changes">Patch changes<a class="hash-link" href="#patch-changes" aria-label="Direct link to Patch changes">#</a></h2>
<h3 id="security">Security<a class="hash-link" href="#security" aria-label="Direct link to Security">#</a></h3>
<ul>
<li>POSIX bin shims now take <code>cygpath</code> and <code>wslpath</code> from the system default path
on Cygwin, MSYS2, and WSL2 so a dependency cannot redirect another package's
shim (<a href="https://github.com/pnpm/pnpm/issues/14866">#14866</a>).</li>
<li><code>pnpm install</code> deprecation warnings no longer carry the text of a package's
deprecation notice, naming only the deprecated package and version
(<a href="https://github.com/pnpm/pnpm/issues/15099">#15099</a>).</li>
<li><code>pnpm install</code> and other commands now warn when environment variables in
project <code>.npmrc</code> credentials are ignored
(<a href="https://github.com/pnpm/pnpm/issues/15051">#15051</a>).</li>
</ul>
<h3 id="installing-packages">Installing packages<a class="hash-link" href="#installing-packages" aria-label="Direct link to Installing packages">#</a></h3>
<ul>
<li><code>pnpm install --frozen-lockfile</code> now succeeds when an optional dependency was
unresolvable and skipped by the install that wrote the lockfile
(<a href="https://github.com/pnpm/pnpm/issues/3960">#3960</a>).</li>
<li><code>pnpm install --frozen-lockfile</code> no longer installs dependencies of projects
removed from <code>pnpm-workspace.yaml</code>
(<a href="https://github.com/pnpm/pnpm/issues/15248">#15248</a>).</li>
<li><code>pnpm ci</code> now empties <code>node_modules</code> before installing in a project that
declares a <code>clean</code> script
(<a href="https://github.com/pnpm/pnpm/issues/15276">#15276</a>).</li>
<li><code>pnpm install --force</code> now re-imports every package into the virtual store
(<a href="https://github.com/pnpm/pnpm/issues/15030">#15030</a>) and removes obsolete
dependency links inside virtual-store packages when their dependencies change
(<a href="https://github.com/pnpm/pnpm/issues/15039">#15039</a>).</li>
<li><code>preinstall</code> script for the root project now runs before dependencies are
resolved and linked
(<a href="https://github.com/pnpm/pnpm/issues/3760">#3760</a>).</li>
<li><code>pnpm install --prod</code> no longer downloads registry packages that only a
devDependency reaches
(<a href="https://github.com/pnpm/pnpm/issues/881">#881</a>).</li>
<li><code>pnpm install</code> no longer hangs when a git dependency is fetched over SSH and
ssh prompts for a passphrase or host key confirmation
(<a href="https://github.com/pnpm/pnpm/issues/2227">#2227</a>).</li>
<li><code>pnpm install</code> now reuses an in-flight tarball download when another
resolution of the same archive still needs its <code>package.json</code>
(<a href="https://github.com/pnpm/pnpm/issues/15037">#15037</a>).</li>
</ul>
<h3 id="resolving-and-linking">Resolving and linking<a class="hash-link" href="#resolving-and-linking" aria-label="Direct link to Resolving and linking">#</a></h3>
<ul>
<li><code>pnpm install</code> and <code>pnpm update</code> now resolve a dependency range to the newest
matching version that is not deprecated
(<a href="https://github.com/pnpm/pnpm/issues/15128">#15128</a>).</li>
<li><code>pnpm add &#x3C;pkg></code> without a version now uses the catalog entry when the
workspace already catalogs that package
(<a href="https://github.com/pnpm/pnpm/issues/14865">#14865</a>).</li>
<li><code>pnpm.overrides</code> entries written as a bare path are now measured from the
directory holding <code>pnpm-workspace.yaml</code>
(<a href="https://github.com/pnpm/pnpm/issues/11131">#11131</a>).</li>
<li><code>pnpm peers check</code> and strict peer dependency checks no longer reject
compatible versions from named registries
(<a href="https://github.com/pnpm/pnpm/issues/15225">#15225</a>).</li>
<li><code>pnpm outdated</code> and <code>pnpm update --interactive --latest</code> now include
named-registry dependencies and preserve their registry prefix
(<a href="https://github.com/pnpm/pnpm/issues/15226">#15226</a>).</li>
<li>Sped up <code>pnpm dedupe</code> and <code>pnpm install</code> in projects with many convergence
overrides by checking overrides concurrently
(<a href="https://github.com/pnpm/pnpm/issues/15175">#15175</a>).</li>
</ul>
<h3 id="running-scripts">Running scripts<a class="hash-link" href="#running-scripts" aria-label="Direct link to Running scripts">#</a></h3>
<ul>
<li><code>pnpm run</code> signal handling no longer delivers a redundant second <code>SIGINT</code> on
<code>Ctrl+C</code>, and properly forwards termination signals without a terminal
(<a href="https://github.com/pnpm/pnpm/issues/7374">#7374</a>).</li>
<li><code>pnpm test</code> now forwards <code>--filter</code> arguments to the test script when the
option follows the shortcut
(<a href="https://github.com/pnpm/pnpm/issues/15217">#15217</a>).</li>
<li><code>pnpm deploy</code>, <code>pnpm rebuild</code>, <code>pnpm rb</code>, and <code>pnpm setup</code> now prefer a
<code>package.json</code> script of the same name
(<a href="https://github.com/pnpm/pnpm/issues/14976">#14976</a>).</li>
</ul>
<h3 id="configuration">Configuration<a class="hash-link" href="#configuration" aria-label="Direct link to Configuration">#</a></h3>
<ul>
<li><code>pnpm-workspace.yaml</code> edits now preserve scalar YAML anchors and aliases
(<a href="https://github.com/pnpm/pnpm/issues/8245">#8245</a>).</li>
<li><code>pnpmfile</code> configuration now loads a <code>.js</code> file as CommonJS or an ES module,
following the nearest <code>package.json</code>
(<a href="https://github.com/pnpm/pnpm/issues/15141">#15141</a>).</li>
<li><code>readPackage</code> hook changes now take added dependencies out of
<code>pnpm-lock.yaml</code> and update dependencies when an existing lockfile is present
(<a href="https://github.com/pnpm/pnpm/issues/3735">#3735</a>,
<a href="https://github.com/pnpm/pnpm/issues/15136">#15136</a>).</li>
<li>pnpm now preserves CRLF line endings when modifying project manifests
(<a href="https://github.com/pnpm/pnpm/issues/3529">#3529</a>).</li>
<li><code>storeDir</code> values loaded from global configuration now expand a leading <code>~/</code>
to the user's home directory
(<a href="https://github.com/pnpm/pnpm/issues/6560">#6560</a>).</li>
</ul>
<h3 id="windows">Windows<a class="hash-link" href="#windows" aria-label="Direct link to Windows">#</a></h3>
<ul>
<li><code>pnpm install</code> on Windows now runs dependency build scripts from long global
virtual store paths
(<a href="https://github.com/pnpm/pnpm/issues/15111">#15111</a>).</li>
<li><code>pnpm install</code> across projects sharing a global virtual store on Windows no
longer fails with <code>Access is denied</code> or file-exists errors
(<a href="https://github.com/pnpm/pnpm/issues/15114">#15114</a>,
<a href="https://github.com/pnpm/pnpm/issues/15176">#15176</a>).</li>
<li><code>pn</code>, <code>pnpx</code>, <code>pnx</code>, and <code>pnpm</code> now run when Git Bash, MSYS2, or Cygwin
launches them through a Windows path
(<a href="https://github.com/pnpm/pnpm/issues/14884">#14884</a>).</li>
<li><code>pnpm pipeline --watch</code> now resolves Windows short paths so multiple path
representations share the build cache
(<a href="https://github.com/pnpm/pnpm/issues/15105">#15105</a>).</li>
</ul>
<h3 id="other-fixes">Other fixes<a class="hash-link" href="#other-fixes" aria-label="Direct link to Other fixes">#</a></h3>
<ul>
<li><code>pnpm remove</code> now runs <code>preuninstall</code>, <code>uninstall</code>, and <code>postuninstall</code>
scripts (<a href="https://github.com/pnpm/pnpm/issues/3276">#3276</a>).</li>
<li><code>pnpm remove -r</code> now fails before modifying manifests if any requested
dependency is absent from all selected projects
(<a href="https://github.com/pnpm/pnpm/issues/2319">#2319</a>).</li>
<li><code>pnpm update --peer</code> now updates ranges in <code>peerDependencies</code>
(<a href="https://github.com/pnpm/pnpm/issues/8081">#8081</a>).</li>
<li><code>pnpm update -g</code> no longer reinstalls unchanged packages
(<a href="https://github.com/pnpm/pnpm/issues/12002">#12002</a>).</li>
<li><code>pnpm publish</code> now allows a detached Git HEAD in CI environments
(<a href="https://github.com/pnpm/pnpm/issues/5894">#5894</a>).</li>
<li><code>pnpm store prune</code> now removes unreferenced files from the content-addressable
store (<a href="https://github.com/pnpm/pnpm/issues/3635">#3635</a>) and expired
<code>pnpm dlx</code> cache data
(<a href="https://github.com/pnpm/pnpm/issues/15171">#15171</a>).</li>
<li><code>pnpm deploy</code> no longer triggers an install when running scripts in a
read-only deployed filesystem
(<a href="https://github.com/pnpm/pnpm/issues/11617">#11617</a>).</li>
<li><code>pnpm sbom</code> now validates SPDX identifiers before emitting them as CycloneDX
license IDs (<a href="https://github.com/pnpm/pnpm/issues/14786">#14786</a>).</li>
<li><code>pnpm --filter</code> and <code>pnpm -F</code> shell completion now suggests workspace package
names (<a href="https://github.com/pnpm/pnpm/issues/15216">#15216</a>).</li>
<li><code>pnpm run</code> shell completion now suggests package scripts
(<a href="https://github.com/pnpm/pnpm/issues/15034">#15034</a>).</li>
<li><code>pnpm --version</code> no longer creates a temporary file in the project directory
(<a href="https://github.com/pnpm/pnpm/issues/15264">#15264</a>).</li>
</ul>
<p>See the <a href="https://github.com/pnpm/pnpm/releases/tag/v12.6.0">v12.6.0 release notes</a>
for the complete list of changes.</p>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
    <item>
      <title><![CDATA[pnpm 12.5]]></title>
      <link>https://pnpm.io/blog/releases/12.5</link>
      <guid>https://pnpm.io/blog/releases/12.5</guid>
      <pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[pnpm 12.5 expands Python support with editable project packages, shared workspace environments, automatic interpreter downloads, and lockfiles for multiple platforms and Python versions. It also accepts Package URLs in pnpm add, adds machine-wide task concurrency groups, and cleans up obsolete...]]></description>
      <content:encoded><![CDATA[<p>pnpm 12.5 expands Python support with editable project packages, shared workspace
environments, automatic interpreter downloads, and lockfiles for multiple
platforms and Python versions. It also accepts Package URLs in <code>pnpm add</code>, adds
machine-wide task concurrency groups, and cleans up obsolete registry metadata
with <code>pnpm cache prune</code>.</p>
<h2 id="python-projects-and-environments">Python projects and environments<a class="hash-link" href="#python-projects-and-environments" aria-label="Direct link to Python projects and environments">#</a></h2>
<h3 id="interpreters-per-project">Interpreters per project<a class="hash-link" href="#interpreters-per-project" aria-label="Direct link to Interpreters per project">#</a></h3>
<p><a href="https://pnpm.io/python#pythonexecutable"><code>pnpm install</code></a> chooses an interpreter that satisfies
each project's <code>requires-python</code>, preferring <code>.python-version</code> when present.
Different projects can use different interpreters. Set <code>python.executable</code> to
choose one interpreter for every project.</p>
<p>When no installed interpreter fits, pnpm downloads a shared
<a href="https://github.com/astral-sh/python-build-standalone">python-build-standalone</a>
interpreter and reuses it on later installs. <a href="https://pnpm.io/settings/cli#runtimeonfail"><code>runtimeOnFail</code></a>
controls this behavior: <code>download</code> permits downloads, <code>error</code> fails, and <code>warn</code>
or <code>ignore</code> use an available interpreter despite the version mismatch.</p>
<p>Environments now live under <code>python-envs</code> in the pnpm store. Each project keeps
its <code>.venv</code> link, which the next install migrates from the old project-local
layout. Old <code>.pnpm/python-envs</code> directories remain until you delete them after
running programs stop using them. With <code>frozenStore</code>, environments remain local.</p>
<p>Wheel imports use <a href="https://pnpm.io/python#importing-wheel-files"><code>packageImportMethod</code></a>.
Choose <code>clone-or-copy</code> or <code>copy</code> when installed files may be modified. Isolated
build environments use clones or copies to keep backend writes private.</p>
<h3 id="editable-packages-and-workspace-sources">Editable packages and workspace sources<a class="hash-link" href="#editable-packages-and-workspace-sources" aria-label="Direct link to Editable packages and workspace sources">#</a></h3>
<p>pnpm now <a href="https://pnpm.io/python#project-packages-and-local-sources">installs a Python project's own package</a>
editable when it declares <code>[build-system]</code>, so its imports and <code>[project.scripts]</code>
commands work immediately. <code>[tool.uv].package</code> can override whether it is packaged.
Dynamic metadata comes from the build backend, and projects with only
<code>requirements.txt</code> can receive an environment and lockfile too.</p>
<p>Declare local dependencies through <code>[tool.uv.sources]</code>:</p>
<div class="code-block"><div class="code-block-title">pyproject.toml</div><pre><code class="hljs language-toml"><span class="hljs-section">[tool.uv.sources]</span>
<span class="hljs-attr">shared</span> = { workspace = <span class="hljs-literal">true</span> }
<span class="hljs-comment"># Alternatively: shared = { path = "../shared", editable = true }</span>
</code></pre></div>
<p>Build backends need approval through <code>allowBuilds</code>, using keys such as
<code>'pkg:pypi/hatchling': true</code>. Git dependencies and source distributions are also
supported and require distribution approval. Direct wheel URLs are supported.
A workspace dependency without a source declaration is refused instead of
silently fetched from an index.</p>
<h3 id="one-environment-for-a-uv-workspace">One environment for a uv workspace<a class="hash-link" href="#one-environment-for-a-uv-workspace" aria-label="Direct link to One environment for a uv workspace">#</a></h3>
<p>Members can <a href="https://pnpm.io/python#sharing-one-environment">share one environment</a>:</p>
<div class="code-block"><div class="code-block-title">pyproject.toml</div><pre><code class="hljs language-toml"><span class="hljs-section">[tool.uv.workspace]</span>
<span class="hljs-attr">members</span> = [<span class="hljs-string">"packages/*"</span>]

<span class="hljs-section">[tool.pnpm.python]</span>
<span class="hljs-attr">shared-environment</span> = <span class="hljs-literal">true</span>
</code></pre></div>
<p>pnpm resolves every member into one <code>pylock.toml</code> and one <code>.venv</code> at the root.
Conflicting dependency requirements produce an error naming the members.
Independent environments remain the default.</p>
<p>Each project can also <a href="https://pnpm.io/python#selecting-extras-and-dependency-groups-per-project">select its own extras and dependency groups</a>
under <code>[tool.pnpm.python]</code>. Workspace defaults skip names a project does not
define; explicit project selections must exist.</p>
<h3 id="one-lockfile-for-multiple-environments">One lockfile for multiple environments<a class="hash-link" href="#one-lockfile-for-multiple-environments" aria-label="Direct link to One lockfile for multiple environments">#</a></h3>
<p>Combine platform targets with Python versions:</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">supportedArchitectures:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">linux-x64-manylinux_2_28</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">darwin-arm64</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">win32-x64</span>
<span class="hljs-attr">python:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">versions:</span> [<span class="hljs-string">'3.12'</span>, <span class="hljs-string">'3.13'</span>]
</code></pre></div>
<p>Every platform is paired with every version. One <a href="https://pnpm.io/python#locking-for-multiple-environments"><code>pylock.toml</code></a>
pins wheels and conditional dependencies for all of them. Installs select their
matching environment and reject interpreters outside the declared environments.</p>
<p><a href="https://pnpm.io/python#pythonoverrides"><code>python.overrides</code> and <code>python.constraints</code></a> control
versions throughout the graph. uv overrides and constraints are read too.
Python filtering now selects projects by name, path, and local-source dependency
relationships; <code>pnpm add --filter &#x3C;selector> pypi:&#x3C;package></code> updates every selected
project.</p>
<h2 id="package-urls-and-ecosystem-registries">Package URLs and ecosystem registries<a class="hash-link" href="#package-urls-and-ecosystem-registries" aria-label="Direct link to Package URLs and ecosystem registries">#</a></h2>
<p><a href="https://pnpm.io/cli/add#package-urls"><code>pnpm add</code></a> accepts Package URLs:</p>
<pre><code class="hljs language-sh"><span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">pnpm</span> add pkg:npm/express@4.18.2
<span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">pnpm</span> add pkg:cargo/serde@1.0.188
<span class="hljs-prompt" aria-hidden="true"></span><span class="hljs-command">pnpm</span> add pkg:pypi/requests@2.31.0
</code></pre>
<p>Each writes to its ecosystem's manifest. <code>pkg</code> is now a reserved registry alias,
regardless of case.</p>
<p><a href="https://pnpm.io/registries#ecosystem"><code>registries</code></a> entries can name <code>ecosystem: npm</code>, <code>cargo</code>,
or <code>pypi</code>. Each Python index declares the names it serves with
<a href="https://pnpm.io/registries#packages"><code>packages</code></a>, and a package resolves only from the index
that claims it — declaration order carries no meaning, and a missing package or
a registry error never falls back to another index. Cargo accepts one sparse
index. Credentials come from <code>.npmrc</code>, matched by origin; registry URL keys
cannot contain credentials.</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">registries:</span>
  <span class="hljs-attr">https://packages.example.org/simple/:</span>
    <span class="hljs-attr">ecosystem:</span> <span class="hljs-string">pypi</span>
    <span class="hljs-attr">packages:</span> [<span class="hljs-string">"company-*"</span>]
  <span class="hljs-attr">https://pypi.org/simple/:</span>
    <span class="hljs-attr">ecosystem:</span> <span class="hljs-string">pypi</span>
    <span class="hljs-attr">packages:</span> [<span class="hljs-string">"*"</span>]
</code></pre></div>
<p>Name-based routing arrived in 12.5.1; 12.5.0 searched the indexes in
declaration order.</p>
<p>This replaces <code>python.indexUrl</code>, <code>python.extraIndexUrls</code>, and <code>cargo.indexUrl</code>.
Without ecosystem declarations, PyPI and crates.io remain the defaults.</p>
<h2 id="platforms-tasks-and-tool-mirrors">Platforms, tasks, and tool mirrors<a class="hash-link" href="#platforms-tasks-and-tool-mirrors" aria-label="Direct link to Platforms, tasks, and tool mirrors">#</a></h2>
<p><a href="https://pnpm.io/settings/dependency-resolution#platform-lists"><code>supportedArchitectures</code></a>
accepts a list of exact platforms, such as <code>linux-x64</code>, <code>linux-x64-musl</code>, and
<code>darwin-arm64</code>, or Rust target triples. <code>current</code> names the install's platform.
The existing <code>os</code>, <code>cpu</code>, and <code>libc</code> mapping still works.</p>
<p><a href="https://pnpm.io/workspace-task-orchestration#concurrencygroups"><code>concurrencyGroups</code></a> limits
tasks across pnpm processes on the same machine, including pipelines:</p>
<div class="code-block"><div class="code-block-title">pnpm-workspace.yaml</div><pre><code class="hljs language-yaml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-attr">test:rust:</span>
    <span class="hljs-attr">concurrencyGroup:</span> <span class="hljs-string">cargo</span>
<span class="hljs-attr">concurrencyGroups:</span>
  <span class="hljs-attr">cargo:</span> <span class="hljs-number">2</span>
</code></pre></div>
<p>A nested <code>pnpm run</code> in the same group reuses its parent's slot.</p>
<p><a href="https://pnpm.io/settings/cli#tools"><code>tools</code></a> configures mirrors for Node.js, Bun, and Python
in global <code>config.yaml</code> or <code>PNPM_CONFIG_TOOLS</code>. Node.js also supports per-channel
mirrors. Workspace tool mirrors are ignored, and <code>pnpm pack-app</code> uses <code>tools.node</code>
for its embedded runtime.</p>
<h2 id="cache-cleanup-and-other-fixes">Cache cleanup and other fixes<a class="hash-link" href="#cache-cleanup-and-other-fixes" aria-label="Direct link to Cache cleanup and other fixes">#</a></h2>
<ul>
<li><a href="https://pnpm.io/cli/cache-prune"><code>pnpm cache prune</code></a> removes obsolete metadata directories
left by the registry cache naming change. Use <code>--dry-run</code> to preview deletions.
<code>pnpm cache list-registries</code> now prints full URLs instead of encoded names.</li>
<li>Downloads no longer reuse a tarball for another package whose resolution pins
a different integrity hash to the same URL
(<a href="https://github.com/pnpm/pnpm/issues/15021">#15021</a>).</li>
<li>Production and development install filters keep the complete dependency graph
in <code>pnpm-lock.yaml</code>, so a later frozen install accepts it
(<a href="https://github.com/pnpm/pnpm/issues/14912">#14912</a>).</li>
<li>Lockfile Git conflict markers are merged automatically
(<a href="https://github.com/pnpm/pnpm/issues/14880">#14880</a>).</li>
<li>Cargo lockfile generation supports <a href="https://pnpm.io/cargo#source-overrides">path and Git source overrides</a>,
and vendoring includes recursive Git submodules at their pinned commits.</li>
<li><a href="https://pnpm.io/cli/pnx#--allow-build"><code>pnx</code> and <code>pnpm dlx</code></a> prompt for dependency build
approval in interactive terminals, including cached installs with pending builds.</li>
<li>Python projects prepare concurrently, and identical registry requirements share
fresh resolutions. <code>pnpm audit</code> also avoids hangs on graphs with many shared
dependencies.</li>
</ul>
<p>See the <a href="https://github.com/pnpm/pnpm/releases/tag/v12.5.0">v12.5.0 release notes</a>
for the complete list of changes.</p>]]></content:encoded>
      <dc:creator>Zoltan Kochan</dc:creator>
    </item>
  </channel>
</rss>
