npm install 静默改了 lockfile,下次 release 的 git checkout 就挂了
发版脚本 SSH 进自托管 Windows 构建机,走 git fetch && git checkout <sha>,结果:
error: Your local changes to the following files would be overwritten by checkout:
desktop/sidecar/package-lock.json
Please commit your changes or stash them before you switch branches.
Aborting
[git checkout bd9555a...] exited with 1
Error: ssh-win[builder] exited with 1
✗ release FAILED at step "desktop-windows" (exit 1)
机器上根本没人手工改过 lockfile。git diff 打开一看,"hasInstallScript": true 多了一行——这是上次发版跑 npm install 时 npm 自己写进去的。
现象
- builder 上
git status -s长期显示M package-lock.json,多次发版都没消失 - release 日志里真正的报错被埋在 SSH stdout 深处,外层只看到
ssh exit 1,新手容易以为是网络或权限问题 - 偶发还有
Cargo.toml、Cargo.lock也被标脏
根因
自托管构建机(不同于 GitHub Actions 每次干净 container)的 git working tree 跨多次发版共享。上次发版时:
npm install在 package.json 新增了postinstall字段后,自动把"hasInstallScript": true写入package-lock.jsoncargo build偶尔更新Cargo.lock的 transitive dep 条目- 这些改动没有 commit,留在 working tree 里
等到下次发版,git checkout <new-sha> 发现本地有修改就拒绝覆盖——这是 git 的保护机制,正确行为,但在无人值守的 CI 场景下变成了拦路虎。
npm install 不是幂等的。只要 package.json 有变化(新增 script、更新 dep 范围),npm 就会更新 package-lock.json 并写入磁盘。在容器 CI 里这不是问题,每次干净开始;在自托管长驻构建机上,这份改动永久留在 working tree,直到下次 checkout 时才爆炸。
正解
立即救火
SSH 进 builder,手动还原脏文件:
git restore desktop/sidecar/package-lock.json
git restore desktop/src-tauri/Cargo.toml
# 顺手清调试残留
git clean -fd
git status -s # 应为空长期修法:checkout 前无条件清场(方案 A,最推荐)
在 release 脚本 SSH 块里,checkout 前加两行:
// 修复前
await sshWin(`
cd '${win.repoPath}'
git fetch origin
git checkout ${sha} // ❌ 遇到 dirty working tree 就挂
`)
// 修复后
await sshWin(`
cd '${win.repoPath}'
git fetch origin
git reset --hard origin/main // 丢弃所有本地 modification
git clean -fd // 删 untracked 文件(调试残留)
git checkout ${sha} // ✅ 干净 working tree,checkout 必成
`)自托管 builder 上永远不应该有手工改动,强制 reset 是最稳策略。
搭配:用 npm ci 替代 npm install
# release pipeline 里替换
npm ci # 严格按 lockfile 安装,不回写,lockfile 与 package.json 不一致时直接报错npm ci 不会修改 package-lock.json,从源头切断静默回写的路径。
两步组合:release 脚本在 git checkout 前无条件 git reset --hard && git clean -fd(builder 不存手工改动,强制覆盖零风险);依赖安装换 npm ci(严格模式,不回写 lockfile)。连跑 3 次发版验证 git status -s 全部为空即为修复。
注意:哪些构建工具会静默改 tracked 文件
| 工具 | 可能改动的文件 |
|------|---------------|
| npm install | package-lock.json |
| pnpm install | pnpm-lock.yaml |
| cargo build | Cargo.lock、偶发 Cargo.toml |
| tauri build | tauri.conf.json(version 字段) |
| 自定义 postinstall | 任意文件 |
这个问题只影响自托管持久化 builder,GitHub Actions / GitLab Runner 用 image: 的容器模式每次从干净环境起步,天然不受影响。Tauri 跨平台打包(Windows MSI 签名只能在 Windows 上做)必须走 self-hosted builder,绕不开这条路径。
一句话外卖
自托管构建机的 git working tree 是跨次发版持久的,每次 checkout 前必须
reset --hard + clean -fd,别让上次构建的副产物拦住下次发版。