pnpm 12.15.0 在 2026-10-21 正式发布(GitHub Release 时间为 UTC 11:16,对应北京时间当天 19:16)。这次次版本里,对日常锁文件影响最直接的是 lockfile.integrityTarget: tar:锁文件改记未压缩 tar 的哈希,注册表只是重压或改传 .tgz 时,lockfile 不必跟着飘。本机用同一份 is-odd@3.0.1 分别装默认模式和 tar 模式,两边的 resolution.integrity 整段都不一样。

官方说明见 pnpm 12.15.0 发布说明 与 GitHub Release。同次还带了 include 拆配置、SOURCE_DATE_EPOCH 约束部署时间戳、以及元数据包名对不上就报 ERR_PNPM_REGISTRY_NAME_MISMATCH。下面按本机实测写。

环境

本机装的是 pnpm 12.15.0,Node.js 20。演示目录放在 ~/demo 下,一个小 workspace 里放前端包,旁边留了空的 Spring Boot 模块目录,模拟常见「Java 后端 + pnpm 管前端」的仓库布局。包管理器本身跟 Spring Boot 无关,只是多数 Spring 仓库现在都会顺手带一份前端 workspace。

$ pnpm --version
12.15.0

integrityTarget: tar,同包哈希对不上默认模式

默认情况下,pnpm 把 registry 下发的压缩包(常见是 .tgz)的哈希写进 pnpm-lock.yaml。注册表如果对同一版本重新压缩,或改成直接提供未压缩 tar,锁文件里的 integrity 就可能变,CI 的 frozen lockfile 也会跟着炸。12.15.0 加了设置:

# pnpm-workspace.yaml
lockfile:
  integrityTarget: tar

打开之后,锁文件记录的是未压缩 tar 内容的哈希。官方原话是:registry 重压或改用 tar 分发时,lockfile 不再跟着变;pnpm 会从本地 store 或下载一次里学到该版本的 tar 哈希。

本机建了两个几乎一样的小项目,都只依赖 is-odd@3.0.1。一边不设 integrityTarget,一边写成 tar,分别 pnpm install。

默认模式的锁文件片段:

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false

packages:
  is-odd@3.0.1:
    resolution: {integrity: sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA==}
    engines: {node: '>=4'}

tar 模式:

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false
  integrityTarget: tar

packages:
  is-odd@3.0.1:
    resolution: {integrity: sha512-SElt+mvaeh56ryoEOFNnkjUJgi+DDqsHkkF5zlEr9h2setH9b+s3zKscWsa1ppKEgeUeSSs14f1421Z+K/doCQ==}
    engines: {node: '>=4'}

版本号完全一样,integrity 字符串却整段不同。settings 里也会多出一行 integrityTarget: tar,方便一眼看出这份锁文件按哪套口径算的。依赖图没变,is-number@6.0.0 作为传递依赖同样换了一套哈希。

安装过程本身很平常,tar 模式还能复用已有 store:

dependencies:
+ is-odd 3.0.1

Done in 39ms using pnpm v12.15.0

如果仓库已经按默认口径锁过一批包,切到 tar 等于换哈希体系,需要重新生成 lockfile,不能指望旧 integrity 继续对上。

include:配置拆到多个文件,越权就报错

同一版还加了 include。根 pnpm-workspace.yaml 声明每个附属 YAML 能管哪些设置,每个设置只能有一个归属方。本地 include 文件路径必须以 ./ 或 ../ 开头。文档在 Splitting Settings Across Files。

本机 workspace 这样写:

# pnpm-workspace.yaml
packages:
  - 'packages/*'

include:
  ./settings/lockfile.yaml:
    - lockfile
  ./settings/catalog.yaml:
    - catalog
# settings/lockfile.yaml
lockfile:
  integrityTarget: tar
# settings/catalog.yaml
catalog:
  lodash: ^4.17.21

pnpm install 正常完成,生成的锁文件 settings 段带上了 integrityTarget: tar,catalog 也生效,workspace 里的 @demo/web 能跑通:

$ pnpm -C packages/web run build
web build ok { a: 1 }

故意在只委托了 catalog 的文件里塞 minimumReleaseAge,立刻失败:

Error: ERR_PNPM_SETTING_NOT_DELEGATED

  × load configuration
  ╰─▶ "minimumReleaseAge" is defined in ./settings/catalog.yaml, which is not
      delegated that setting
  help: Add "minimumReleaseAge" to the settings "include" delegates to ./
        settings/catalog.yaml in pnpm-workspace.yaml, or move it to the source
        that owns it.

这不是警告,是加载配置阶段就停。适合跟 CODEOWNERS 一起用:安全相关 settings 单独文件、单独审批,日常改 catalog 的人碰不到。

SOURCE_DATE_EPOCH 会压住 modules 时间戳

发布说明写明:pnpm deploy 和 pnpm install 会把环境变量 SOURCE_DATE_EPOCH 当作 node_modules/.modules.yaml 与 .pnpm-workspace-state-v1.json 里时间戳的上限。本机在 tar 模式项目里设 SOURCE_DATE_EPOCH=1700000000 再装一遍:

# node_modules/.modules.yaml
"prunedAt": "Tue, 14 Nov 2023 22:13:20 GMT"

# node_modules/.pnpm-workspace-state-v1.json
"lastValidatedTimestamp": 1700000000000

1700000000 秒对应的就是 2023-11-14 22:13:20 UTC。不做可复现发布的话感知不强;要做镜像层或制品比对时,这行环境变量现在真会进 pnpm 写出来的状态文件。

其余值得知道、但本机没单独搭环境的

同次还有:能装只提供未压缩 tar、并用 zstd/gzip 内容编码的 registry;元数据里的包名和请求名不一致时直接 ERR_PNPM_REGISTRY_NAME_MISMATCH(以前会按元数据里的名字装,导致 allowBuilds 规则对不上);大 workspace 安装和 macOS 链接有性能优化。这些需要自建 registry 或大仓库才好量化,文中不展开成实测。

升级方式官方仍是常规渠道。本机用的是 get.pnpm.io 安装脚本指定 12.15.0;已有环境也可以 pnpm self-update 或让项目 packageManager 字段钉住版本。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。