换个版本号就能升级?可没那简单:pnpm 11 升级踩坑记

本文记录了工作中升级 PNPM 11 过程中遇到的问题。

本文并非操作手册,而是记录「升级过程中遇到的问题以及如何解决」的复盘。文中提到的问题和解决方案可能未必适用于广泛的情况,仅供参考。

突如其来的错误

pnpm 的升级始于一场意外。因为目前正在做 Node.js 24 的升级和 Monorepo 的迁移。

7 月 15 日,Pipeline 上开始报错,用于做 Audit 的 Step 出现 410 错误。同时本地执行 pnpm audit 时也出现同样的错误信息。

原因就是错误信息中提到的 Audit 的 API 退休了,而解决方法就是把 PNPM 升级到 11。

由于有 300+ 的仓库且 PNPM 的版本各不相同(8、9、10、11 都存在),并且各版本之间都有不小的改动。仅靠 corepack use pnpm@11 是不够的,整个升级的过程中有不少需要手动介入的地方。

pnpm 11 改了什么

pnpm 11 最大改动在安全模型上。为了应对供应链攻击提高安全性,默认开启 strictDepBuilds------依赖如果想跑 postinstallprepare 等脚本,必须先出现在 allowBuilds 白名单里,否则不是静默跳过,而是直接报错。

很巧的是,我们大量的 Bitbucket 私有依赖都有 prepare 或类似构建脚本。这意味着:安装能过,不等于脚本能跑;脚本能跑,也不等于原生 .node 文件已经生成。

在此前升级 Node.js 24 的任务中,因为有二进制文件(这是个重点)的编译,所以本地和 CI 的安装脚本拆分为 pnpm install --ignore-scriptspnpm rebuild 两步。升级前这套在 pnpm 8/9/10 下基本够用;但升级到 11 后,每一步的边界都变窄了。

问题 1:面对 60 多个 Bitbucket 私有依赖,白名单怎么维护?

处于安全角度,我们不会关闭 strictDepBuilds 配置而是启用 allowBuilds 白名单,

而通常在一个 Service 中就有几十个 Bitbucket 私有依赖。第一个问题便是白名单的构建,lockfile 里大约有 60 多个私有依赖,每个几乎都有 prepare 或类似构建脚本。如果全部手填,既容易漏,也很难在 code review 里看清变更。

并且在查文档发现两条硬约束:

  • pnpm 不支持 Git 依赖的通配符,不能写 *@git+ssh://git@bitbucket.org/example-org/* 了事;
  • Git 依赖的键必须是 包名@git+ssh://.../repo.git,只写包名对 Git 依赖无效。

解决方案也很简单:

  • 针对 Bitbucket 私有依赖 :私有依赖脚本的安全性是可信的。可以直接让 AI 从 package.jsonlockfile 中提取私有依赖添加到白名单。同时让 AI 编写 sync 脚本负责校验 CI 中 lockfile 与 yaml 是否一致。新增 Git 依赖后,开发者跑一遍 sync 脚本,把 yaml 和 lockfile 一起提交即可。
  • 针对 npm registry 公共包 :保留手动区。谁加了需要 postinstall 的包,走 pnpm approve-builds <包名> 或在 PR 里补条目,经 review 合并。

在解决了依赖安装问题后,下一步就到了 rebuild 步骤了。

问题 2 :CI 报二进制依赖的编译问题

配置好 allowBuilds 白名单后,在执行 pnpm rebuild 时突然报错:

text 复制代码
Error: Cannot find module './build/Release/example-native-lib'

错误信息指向某个私有依赖的间接依赖 example-native-lib。于是立刻就想:是不是白名单漏了?

检查 pnpm-workspace.yamlexample-native-lib 对应的 Git 条目已经在里面。再跑 pnpm rebuild example-native-lib,命令成功退出,但 build/Release/example-native-lib.node 依然不存在。

继续看这个依赖包本身的 package.json"gypfile": true,没有 install,也没有 prepare 这样的构建脚本。

这就对上了 pnpm 11 的行为:pnpm rebuild 只会为显式定义了 lifecycle script 的包重跑脚本。gypfile: true 只是告诉 npm/pnpm「这个包需要 node-gyp」,并不会自动触发编译。在 pnpm 8 下,全量 pnpm rebuild 有时会把这类包顺带编过去;pnpm 11 下不会。

问题 3 :CI 能过,但同事 pull 后 install 失败

处理完前面安装和编译的步骤后,CI 能过 PR 也终于 Merge 了。本以为升级已经完成,谁曾想当同事 pull 了最新的代码后发现安装失败了。出现错误信息 [ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: ...

经过排查后发现报错指向的包,是某个私有依赖升级后新带进来的传递依赖 ------例如把 example-model 从 v71 升到 v74 后,lockfile 里多出来一批 example-spec-*example-aws-utilities 之类的 Bitbucket Git 包。CI 日志里列出的正是这类名字。

第一反应很容易误判:「我这边明明没问题,是不是同事环境不对?」

但检查后才发现原来是私有依赖变更后引入的问题。解决方案也很简单,运行一下前面创建的 sync 脚本,然后再执行一下 pnpm approve-builds <包名> 即可。

这一轮的教训可以压成一句话:pnpm 11 下,lockfile 和 allowBuilds 是绑定的;你只更新了前者,就等于只把门钥匙发了一半。

总结一下

回头看,pnpm 11 升级里最容易混淆的是把三件事当成一件事:

text 复制代码
allowBuilds: true   →  允许该依赖执行 lifecycle script(门禁)
pnpm rebuild       →  为已有 install/prepare 的包重跑脚本(触发器)
gypfile: true      →  需要 node-gyp,但没有 lifecycle script(无人触发)

白名单解决的是「能不能跑」;rebuild 解决的是「谁来跑、跑哪些」;gypfile-only 包两个都不够用,必须另有显式编译步骤。

pnpm 8 到 11 的差异就是对于依赖安装变得更加严格,处于安全目的 allowBuilds、gypfile-only、rebuild 边界都要心里有数。同时也暴露了一点,跨大版本升级时,有非常多的坑要踩。针对于包管理器这类基础工具,还是要勤加升级,保持在较新的 LTS 版本上。

参考资料:


欢迎关注同名公众号:此方的手账

不止技术。科技新闻解析、编程语言史、大厂史、世界简史正在连载中。

希望成为让你了解一些奇怪知识的电子榨菜和厕所读物。

相关推荐
SelectDB1 小时前
洋钱罐基于 SelectDB 实现 Hive 数据湖透明加速:查询 P95 从 300 秒降至 20 秒的完整实践
后端
尘世中一位迷途小书童1 小时前
无人机航线里的云台角度怎么调?Cesium 做了个视锥 + 鹰眼联动
前端·javascript·数据可视化
张元清1 小时前
React useMeasure Hook:用 ResizeObserver 测量 DOM 元素 (2026)
javascript·react.js
A24207349301 小时前
Vue3 + TypeScript:后端数据在表格内渲染后进行增删改的完整实现步骤
前端·javascript·typescript
Chen_LSN1 小时前
C语言——深度理解指针(4)
后端
用户638982245891 小时前
自定义注解@SyncNeo4j实现增删改导入时自动同步数据节点和关系到Neo4j
后端
他们叫我 悦儿遥遥雨1 小时前
.NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑
前端·javascript·.net
AlunYegeer2 小时前
Spring Boot 多模块服务间 API Token 鉴权排查记录
java·spring boot·后端
沙湖遇雨2 小时前
3.客户端向服务端建立连接
后端