Pnpm 12.0
摘要
pnpm 12.0 是用 Rust 重写的包管理器的稳定版本,保持与 pnpm 11 的向后兼容性,同时引入改进,如更快的对等解析和针对循环依赖的确定性锁文件。
暂无内容
查看缓存全文
缓存时间: 2026/08/27 06:17
# pnpm 12.0
来源:https://pnpm.io/blog/releases/12.0
pnpm 12 已稳定发布。它是使用 Rust 重写的 pnpm 版本,但这并非一次迁移:pnpm 11 的命令、标志、设置和锁文件格式均得以保留,相关文档 (https://pnpm.io/motivation) 也涵盖了两个版本的说明。两者行为上确实存在差异的简要列表请参阅 pnpm 12 中的不同之处 (https://pnpm.io/blog/whats-different-in-pnpm-12)。本文将介绍 pnpm 12 新增的功能,这些功能是 pnpm 11 从未发布过的。
通过 npm 安装时,`latest` 标签仍指向 pnpm 11 系列版本,因此安装 pnpm 12 需使用 `next-12` 标签:
```bash
npm install -g pnpm@next-12
```
其他安装方式(包括无需 Node.js 的方式)请参阅安装 pnpm 12 (https://pnpm.io/installation#installing-pnpm-12)。Homebrew、winget、Scoop 和 Chocolatey 暂未提供 pnpm 12。
## 破坏性变更 (https://pnpm.io/blog/releases/12.0#breaking-changes)
### Git 依赖现在使用身份标识符 (https://pnpm.io/blog/releases/12.0#git-dependencies-are-identities)
对于 GitHub、GitLab 和 Bitbucket 上的仓库,指定符现在直接指向仓库本身,而非选择传输方式。`github:owner/repo`、`owner/repo`、`git+https://...` 和 `git+ssh://git@...` 现在都通过托管平台的规范 HTTPS URL 进行解析,且锁文件中永远不会记录这些平台的 SSH URL。
要通过 SSH 访问私有托管仓库,请使用 git 自身的 URL 重写功能配置 *本地机器*:
```bash
git config --global url."[email protected]:".insteadOf https://github.com/
```
pnpm 调用 `git` 命令,因此该重写适用于其所有 git 操作。
未知的主机会保留其精确的 URL(包括 SSH URL),包含嵌入凭证的 URL 会按原样保留,且永远不会解析为主机存档地址。
详情请参阅 Git 依赖的解析方式 (https://pnpm.io/package-sources#how-git-dependencies-are-resolved)。
### `pnpm-workspace.yaml` 中无法识别的设置现在会被报告 (https://pnpm.io/blog/releases/12.0#an-unrecognized-setting-in-pnpm-workspaceyaml-is-reported)
过去,pnpm 无法识别的设置会被静默忽略——拼写错误的 `minimumReleaseAge` 会导致其本应设置的策略失效,且不会有任何提示。现在,这些设置会被报告出来,并在键名疑似拼写错误时建议最接近的正确设置名称。
当项目固定的 pnpm 版本满足正在运行的 pnpm 要求时,命令会**失败**并返回 `ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS`:既然遵循了版本固定,该设置就不可能是为其他 pnpm 版本准备的,因此应修正错误而非忽略该键。在其他情况下仅为警告,因此尚未清理的项目仍可继续工作。`pnpm config` 子命令不会因此失败,因此仍可检查和修复损坏的文件。
### 循环依赖图的锁文件 (https://pnpm.io/blog/releases/12.0#lockfiles-of-cyclic-dependency-graphs)
在对等依赖解析期间,依赖循环现在会以规范方式打破:每个循环的成员按包 ID 排序,闭合循环的边始终在相同位置切断,无论安装从何处进入循环。
因此,锁文件成为依赖图的纯函数——重排导入者、重排依赖项以及重复安装都会产生字节完全相同的锁文件,而之前无法做到 (\#13846 (https://github.com/pnpm/pnpm/issues/13846), \#13865 (https://github.com/pnpm/pnpm/issues/13865))。
在大型且循环较多的工作区中,对等依赖解析速度快 2-3 倍,内存使用减少约 25%,且生成的锁文件体积显著减小。现有锁文件仍可工作:`--frozen-lockfile` 会原样使用它们,跳过解析的安装操作也会保持它们不变。首次实际重新解析的安装会一次性重新记录循环包的、依赖于遍历顺序的对等变体。
详情请参阅对等依赖的解析方式 (https://pnpm.io/how-peers-are-resolved#cyclic-dependencies)。
### 在 Linux 上,`packageImportMethod: auto` 现在优先使用硬链接 (https://pnpm.io/blog/releases/12.0#packageimportmethod-auto-hardlinks-first-on-linux)
Reflink 会实例化一个新的 inode,并在文件系统的元数据树中复制范围簿记信息,而硬链接只是一个目录条目——在 btrfs 上,这大约可以将安装从热缓存实例化 `node_modules` 的时间减半。
因此在 Linux 上,`auto` (https://pnpm.io/settings/node-modules#what-auto-tries-first) 现在优先尝试硬链接。ext4 没有变化(从未支持克隆,因此 `auto` 已经使用硬链接),macOS 继续优先使用克隆,因为 APFS 的 `clonefile` 是平台原生的廉价操作。
在 Linux 上,克隆成为次选方案而非首选,因此拒绝硬链接的存储仍会得到克隆;`packageImportMethod: clone` 仍然直接要求克隆。
### `engineStrict` 遵循边,而非子树 (https://pnpm.io/blog/releases/12.0#enginestrict-follows-the-edge-not-the-subtree)
在 `engineStrict` (https://pnpm.io/settings/cli#enginestrict) 下,现在当通过可安装包的常规 `dependencies` 边到达不兼容的包时,安装会失败,即使整个子树挂载在 `optionalDependencies` 条目下。pnpm 11 会安装该包并发出安装检查警告。仅通过可选边,或通过一个本身被跳过的包可达的包,在两个版本中仍会被跳过 (\#13286 (https://github.com/pnpm/pnpm/issues/13286))。
## 新功能 (https://pnpm.io/blog/releases/12.0#new-features)
并非所有功能都是 pnpm 12 独有的。**注册表修订版**、**远程副作用缓存**、**`audit.ignorePrune`**、**批量审批暂存版本**以及 **`pnpm init` 的 latest 标签固定** 功能也包含在 pnpm 11.25 中;其余功能仅属于 v12,因为它们属于 Rust 重写部分。
### 感知项目的全局二进制文件 (https://pnpm.io/blog/releases/12.0#project-aware-global-bins)
全局安装的 `node`、`deno` 或 `bun` 现在会遵循当前项目固定的版本,而非总是运行全局安装的版本——无需 shell 钩子,也无需类似 `use` 的命令。
新的 `globalShims` (https://pnpm.io/settings/other#globalshims) 设置可选择哪些全局安装的包会获得此类垫片;默认值为 `{ node: true, deno: true, bun: true }`,并按键合并,因此 `globalShims: { typescript: true }` 会添加一项而无需重申其他项。稳定的 Node.js 版本会通过 Node.js 发布团队的签名进行验证,并在无提示的情况下切换。其他所有内容——Deno、Bun、Node.js 预发布版本、你启用的普通包二进制文件——每个项目和每个候选项只会询问一次 *“你信任这个项目吗?”*,并将答案保存在本机范围。
`PNPM_SHIM_BYPASS=1` 可在一次调用中绕过此功能。
详情请参阅感知项目的全局二进制文件 (https://pnpm.io/global-packages#project-aware-global-bins)。
### pnpm 安装其他包管理器 (https://pnpm.io/blog/releases/12.0#pnpm-installs-the-other-package-managers)
pnpm 现在可以提供 npm、Yarn Classic、Yarn Berry、Yarn 6 (`yarnpkg/zpm`) 和 Bun,每个都通过受信任的包管理器注册表获取,并且每个通过 npm 发布的包在运行前都会根据其确切版本的 npm 签名进行验证。
三个功能使用此机制:
1. **Git 托管的依赖项** 会使用 *它* 所要求的包管理器进行准备,因此一个用 Yarn 构建的仓库可以在只有 pnpm 的机器上安装。
2. **`pnx` (https://pnpm.io/cli/pnx)** 为单个命令运行一个包管理器——`pnx yarn@4 install`、`pnx npm@11 ci`、`pnx node@22`。
3. **`pnpm shim add yarn` (https://pnpm.io/cli/shim)** 链接一个 `yarn`,它会运行当前项目固定的任何版本。
因此,指定包管理器名称指的是工具本身,而非与其同名的 npm 包:`pnpm add -g yarn@4` 安装 Yarn Berry,而在项目中 `pnpm add yarn@4` 会记录 `"packageManager": "[email protected]"`——这是 Corepack 读取的内容——而其他所有包管理器则记录在 `devEngines.packageManager` 中。定位包的指定符仍会安装其命名的包(`pnpm add yarn@npm:[email protected]`)。
详情请参阅其他包管理器 (https://pnpm.io/package-managers)。
### 注册表修订版 (https://pnpm.io/blog/releases/12.0#registry-revisions)
注册表可以为已发布的版本提供**替代制品**——修补了漏洞的重建版本——而无需更改版本号,也无需改写规范 `name@version` URL 始终提供的字节内容。
pnpm 将每个此类制品称为*修订版*,使用其完整的 SHA-512 摘要进行寻址,并在锁文件中将其记录为额外的一行:
```yaml
# pnpm-lock.yaml
packages:
[email protected]:
resolution: {integrity: sha512-...}
revision: 1
```
没有 `revision` 的条目是修订版 0,即原始版本——这是 pnpm 之前写入的所有条目的含义,因此采用修订版的锁文件与当今的锁文件字节完全相同。
依赖项或 override 可以显式固定修订版,格式为 `+rN`,而 `pnpm update --patches` (https://pnpm.io/cli/update#--patches) 可刷新锁定的制品而不改变单个版本。pnpr (https://pnpm.io/pnpr) 为其托管的包提供修订版,并为宣传修订版的上游注册表进行代理。
详情请参阅注册表修订版 (https://pnpm.io/registry-revisions)。
### `pnpm init` 固定最新的 pnpm (https://pnpm.io/blog/releases/12.0#pnpm-init-pins-the-latest-pnpm)
`pnpm init` 现在固定**最新**发布的 pnpm,而非运行该命令的版本,因此一个由过时 pnpm 初始化的项目不再通过其自身的固定继承该过时版本 (\#7490 (https://github.com/pnpm/pnpm/issues/7490))。
如果 `latest` 查询无法应答(无网络、注册表缓慢、`offline`、或 `latest` 被 `minimumReleaseAge` 或 `trustPolicy` 拒绝),则会像以前一样固定运行版本。查询永远不会失败或挂起命令。
### 暂存发布的批量审批 (https://pnpm.io/blog/releases/12.0#batch-approval-for-staged-publishing)
`pnpm stage approve` (https://pnpm.io/cli/stage#approve) 可一次性审批多个暂存包。不带 stage id 运行将从暂存版本中交互式选择,或传递列表。
整个批次使用**一个**一次性密码进行审批,pnpm 仅在注册表停止接受该密码时才要求提供新密码。在工作区内部,包按依赖顺序审批,无法审批的工作区依赖项对应的包会被跳过,而非在依赖项从未到达注册表的情况下进行发布。
### `audit.ignorePrune` (https://pnpm.io/blog/releases/12.0#auditignoreprune)
设置 `audit.ignorePrune: true` (https://pnpm.io/cli/audit#auditignoreprune) 后,`pnpm audit --fix` 会移除审计报告中不再出现的已忽略 GHSA 条目,从而停止累积早已不存在的依赖项的可容忍建议列表。
### 全局命令拒绝在 `sudo` 下运行 (https://pnpm.io/blog/releases/12.0#global-commands-refuse-to-run-under-sudo)
`pnpm setup`、`pnpm self-update` 以及任何修改全局安装的命令现在在 `sudo` 下运行时会失败,并返回 `ERR_PNPM_SUDO_NOT_SUPPORTED`,而非静默地操作 root 用户的主目录。
pnpm 将全局包和配置保存在调用用户的 home 目录中,因此这些命令永远不需要 root 权限。只读全局命令如 `pnpm bin --global` 仍可工作。
### 远程副作用缓存(概念验证)(https://pnpm.io/blog/releases/12.0#a-remote-side-effects-cache-proof-of-concept)
一个可选的概念验证允许安装跨机器复用依赖项的构建输出,方法是通过 pnpr (https://pnpm.io/pnpr) 发布和恢复**经过签名**的、组织范围内的制品,而非在本地运行生命周期脚本。
仓库仅需指定符合条件的 `remoteSideEffectsCache.organization` 和 `packages` (https://pnpm.io/settings/build#remotesideeffectscache);`pnpm-workspace.yaml` 中拒绝所有描述签名操作的设置,这些设置改从全局配置文件或环境变量中读取,因此克隆的仓库无法将机器的密钥变成签名预言机。每次缓存失败都会回退到普通的本地构建。
目前仅在 Linux/glibc x64 和 arm64 上进行恢复——详情请参阅共享副作用缓存 (https://pnpm.io/pnpr/shared-side-effects-cache)。
## 值得了解的修复 (https://pnpm.io/blog/releases/12.0#fixes-worth-knowing-about)
### 兼容性数据库移除了其静态分析条目 (https://pnpm.io/blog/releases/12.0#the-compatibility-database-drops-its-static-analysis-entries)
内置的兼容性数据库不再添加通过静态分析发布包检测到的依赖项。那些条目命名的包通常被依赖项仅为其*类型*而导入,因此安装它们充其量是不必要的,最坏情况会破坏依赖项:`@typescript-eslint/types` 获得了一个 `typescript` 依赖项,该依赖项被解析为最新版本,这使得 TypeScript 7 处于旧版 `@typescript-eslint` 下,并导致 ESLint 失败并报错 `Cannot read properties of undefined (reading 'Intrinsic')`。
`@yarnpkg/extensions` 条目和 pnpm 自己精心策划的条目保留不变。
### 当上方无位置可链接时,存储位于项目内 (https://pnpm.io/blog/releases/12.0#a-store-inside-the-project-when-nothing-above-it-is-linkable)
当项目上方没有任何目录接受硬链接时——例如仅授予项目写入权限的 AI 代理沙箱,或仅挂载项目为可写容器——默认存储将创建在 `/node_modules/.pnpm-store`,而非 pnpm 主目录。
在那些环境中,主存储要么是只读的,要么在另一个卷上,这迫使每个包都被复制而非硬链接 (\#13525 (https://github.com/pnpm/pnpm/issues/13525))。
### `filterLog` pnpmfile 钩子已弃用 (https://pnpm.io/blog/releases/12.0#the-filterlog-pnpmfile-hook-is-deprecated)
pnpm 12 忽略 `hooks.filterLog` (https://pnpm.io/pnpmfile#hooksfilterlog),并在 pnpmfile 定义它时发出警告。请使用 `loglevel` (https://pnpm.io/settings/cli#loglevel) 来选择 pnpm 报告的详细程度。
## 反馈 (https://pnpm.io/blog/releases/12.0#feedback)
请报告你遇到的任何问题 (https://github.com/pnpm/pnpm/issues)。
相似文章
@Diegoescalonaro: pnpm 12 已发布。它是用 Rust 重写的,但从 pnpm 11 升级不应感觉像是在迁移。有人已经……
pnpm 12 以 Rust 重写版的形式发布,旨在实现从 pnpm 11 的无缝升级,并展示了令人期待的性能基准。
NPM v12 即将到来的破坏性变更
npm v12 对 npm install 引入了安全相关的破坏性变更,默认禁用脚本、git 依赖和远程 URL 依赖的自动执行。用户可以通过升级到 npm 11.16.0+ 并查看警告信息来提前准备,显式选择信任行为。
Uv 0.12.0
uv 0.12.0 是极速 Python 包和项目管理器的一个重大版本,该管理器用 Rust 编写,旨在替代 pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv 等工具。
npm 的分阶段发布和新的安装时控制
npm 引入了分阶段发布,要求通过双因素认证(2FA)进行人工审批才能发布包,并新增了 `--allow-*` 标志(file、remote、directory)来控制安装源,从而提高了 npm CLI 11.15.0 的供应链安全性。
Show HN: Safe-install – 通过可信构建依赖实现更安全的 NPM 安装
介绍了一个名为 safe-install 的新 npm 包,旨在通过允许开发者默认禁用安装脚本并屏蔽异类子依赖来增强供应链安全,从而应对持续存在的安全漏洞。