正文
升级到 npm v12 后,如果你遇到这些现象,先不要急着删 lockfile 或把所有脚本重新打开:
安装完成了,但 Prisma / esbuild / sharp 不能运行
node-gyp 没有编译,原生模块加载失败
依赖里写了 git+https 地址,npm install 却解析不到
CI 在 npm install 阶段失败,本地旧 npm 却正常这通常不是某个依赖突然坏了。npm v12 把安装时的安全默认值改成了更保守的模式:依赖的 preinstall、install、postinstall 和隐式 node-gyp 构建默认不执行;Git 依赖和远程 URL 依赖默认也不再解析。官方建议先审核要运行的脚本,再把允许列表提交到 package.json。
npm v12 改了哪几类安装行为
先判断你遇到的是哪一类阻断。它们看起来都像“安装失败”,但处理方式不同。
| 现象 | npm v12 默认行为 | 先检查什么 |
|---|---|---|
| 安装后缺少生成文件或二进制 | 生命周期脚本默认关闭 | 依赖是否需要 postinstall 或 node-gyp |
| 原生模块无法加载 | 隐式 node-gyp 构建默认不运行 | 错误栈中的包名、Node ABI、编译工具链 |
git+https / GitHub commit 依赖无法安装 | Git 依赖默认不解析 | package.json、lockfile 中的 Git 来源 |
| HTTPS tarball 依赖无法安装 | 远程 URL 依赖默认不解析 | 直接或传递依赖是否用了 URL |
这些默认行为在 npm 11.16.0 之后已经以警告方式出现;npm v12 被标记为 latest 后,原来的自动执行行为改为显式放行。
先确认是不是 npm v12 导致
不要只看 Node.js 大版本。先在报错的本机或 CI 环境中确认 npm 实际版本:
npm --version
node --version
npm config get allow-scripts
npm config get allow-git
npm config get allow-remote如果 CI 和本地的 npm 主版本不同,先统一它们。GitHub Actions 可以把 Node 版本固定在工作流中,再让每个环境从同一份 lockfile 安装:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm cinpm ci 只保证按 lockfile 安装,不会绕过 npm v12 的安全默认值。CI 能不能执行依赖脚本,仍取决于项目提交的放行配置和运行时参数。
postinstall 或 node-gyp 没跑时,先审核再放行
最容易出问题的不是项目自己的 prepare,而是传递依赖里的安装脚本。例如 esbuild 下载平台二进制、sharp 准备原生依赖、某些数据库客户端生成代码或原生模块触发 node-gyp 编译。
先在干净工作区执行 npm 提供的审核入口:
npm approve-scripts --allow-scripts-pending这一步不要直接全选。对每个待审批包,至少看三件事:
- 包是否是
package.json或 lockfile 中的预期依赖。 - 脚本是否解释了用途,例如下载对应平台二进制或编译原生绑定。
- 该包版本、维护者和来源是否符合团队当前依赖策略。
确认后,npm 会把允许列表写入项目配置。把生成的 package.json 变更和 lockfile 一起提交,确保开发机与 CI 使用同一份决定。
如果你只需要验证“是不是脚本没有执行”,可以在隔离分支中重新安装,然后检查受影响模块的实际输出:
npm ci
npm run build
npm test成功标准不是 npm install 返回 0,而是生成步骤、构建和受影响的运行时测试都通过。原生模块场景还应在与部署一致的系统和 Node 版本中运行一次。
Git 依赖安装失败时,不要先全局放开
项目里出现这类声明时,npm v12 可能会拒绝继续解析:
{
"dependencies": {
"internal-tool": "git+https://github.com/example/internal-tool.git#v2.4.0"
}
}先查它是直接依赖还是传递依赖:
npm ls internal-tool
npm explain internal-toolGit 依赖的风险不只在网络。它可能绕过 npm registry 的常规发布与审计流程,也更容易因为分支移动、提交被删除或访问令牌失效而让构建不可复现。
优先级应当是:
- 能换成 registry 已发布的固定版本时,先换掉 Git 依赖。
- 必须使用 Git 时,固定 tag 或 commit,不依赖会移动的分支名。
- 只在确实需要的项目或 CI job 中显式允许 Git 来源,并记录原因。
- 给私有仓库访问令牌最小权限,不把令牌写进
package.json或 lockfile URL。
远程 tarball 依赖也按同一思路处理。确认完整 URL、校验来源和版本固定方式后,再决定是否允许;不要为了通过一次安装,把所有远程来源永久放开。
CI 里怎样避免“本地能装、线上不能装”
把下面四项放进同一次变更里:
| 位置 | 要统一的内容 |
|---|---|
package.json | 依赖脚本允许列表与 engines |
| lockfile | 依赖版本与来源,避免 CI 重解依赖 |
| CI 工作流 | Node/npm 版本、缓存 key、安装命令 |
| 镜像或部署环境 | 与 CI 一致的 Node/npm 组合 |
如果你维护 monorepo,不同 workspace 的脚本审批也要一起复核。不要因为某个 workspace 需要编译原生模块,就让整仓库所有传递依赖的安装脚本都自动运行。
可以在 CI 加一段只读检查,把偏差暴露在发布前:
npm --version
node --version
npm ci
npm run build任何需要网络下载、编译二进制或访问私有 Git 仓库的依赖,都应有一条能在 CI 日志中识别的失败信息。这样出现故障时,能分清是 npm 规则、凭据、平台 ABI,还是依赖本身的问题。
不要用这三种方式“修好”
在所有机器上关闭安全默认值
把脚本、Git 和远程来源全部恢复成无条件执行,会让一次升级故障变成长期供应链风险。先按具体依赖最小放行。
删除 lockfile 后反复重装
这会同时改变大量传递依赖,使问题更难定位。先保留 lockfile,确认安装策略;只有要有意识升级依赖时才更新它。
只让 CI 使用旧 npm
短期回退可以作为止血措施,但要设定撤销日期。否则开发机、CI 与发布镜像会继续漂移,下一次依赖升级仍会以更难排查的方式失败。
迁移完成后,发布凭据也要一起检查
npm v12 同时开始收紧可绕过 2FA 的 granular access token。敏感账户、组织和包管理操作将不能再通过这类 token 跳过 2FA;直接发布也会继续向 trusted publishing 或 staged publishing 迁移。
如果 CI 仍依赖长期存在的发布 token,现在就应安排迁移验证:
- 使用 npm 的 trusted publishing(OIDC)或带人工 2FA 的 staged publishing。
- 把安装凭据和发布凭据分开,不让普通安装 job 拥有 publish 权限。
- 在测试包或 prerelease tag 上走完整发布链,再迁移正式包。
结论
npm v12 的变化不是让依赖安装变得不可用,而是要求项目明确声明:哪些安装脚本、Git 来源和远程来源值得信任。
遇到安装异常时,按“确认 npm 版本 -> 定位具体依赖 -> 审核并最小放行 -> 在 CI 和部署环境验证”的顺序处理。这样既能恢复 postinstall、node-gyp 和必要 Git 依赖,也不会把升级变成无边界的脚本执行入口。