KernelRanch
返回文章列表

本地 npm install 成功但 CI 失败:复现锁文件漂移

2026-11-13 #nodejs#npm#ci

修改 package.json 后,本机能启动,干净的 CI 却在 npm ci 报错。先检查清单和锁文件是否同步提交。npm install 可以更新依赖解析结果,npm ci 会检查锁文件与清单并使用锁定结果安装。

1. 离线模拟漏提交锁文件

把代码保存为 npm_lock_lab.py,确保 npm 在 PATH 中,再运行 python npm_lock_lab.py。脚本创建两个纯本地依赖包并生成 tarball,测试过程中不访问 registry。

import json
import os
from pathlib import Path
import subprocess
import tarfile
from tempfile import TemporaryDirectory

npm = "npm.cmd" if os.name == "nt" else "npm"

with TemporaryDirectory() as tmp:
    root = Path(tmp)
    for name in ("alpha", "beta"):
        source = root / "package"
        source.mkdir(exist_ok=True)
        (source / "package.json").write_text(json.dumps({
            "name": name, "version": "1.0.0",
        }), encoding="utf-8")
        with tarfile.open(root / f"{name}.tgz", "w:gz") as archive:
            archive.add(source, arcname="package")

    def write_manifest(names):
        (root / "package.json").write_text(json.dumps({
            "name": "lock-lab", "version": "1.0.0",
            "dependencies": {name: f"file:{name}.tgz" for name in names},
        }), encoding="utf-8")

    def run(*args):
        return subprocess.run(
            [npm, *args, "--offline", "--ignore-scripts", "--no-audit",
             "--no-fund", "--cache", str(root / "npm-cache")],
            cwd=root, capture_output=True, text=True, timeout=20,
        )

    write_manifest(["alpha"])
    run("install", "--package-lock-only").check_returncode()
    run("ci").check_returncode()
    print("initial lock: ci passed")
    write_manifest(["alpha", "beta"])
    result = run("ci")
    assert result.returncode != 0 and "beta" in result.stderr, result.stderr
    print("stale lock: ci rejected missing beta")
    run("install", "--package-lock-only").check_returncode()
    lock = root / "package-lock.json"
    original = lock.read_bytes()
    run("ci").check_returncode()
    assert lock.read_bytes() == original
    assert (root / "node_modules" / "beta" / "package.json").is_file()
    print("updated lock: ci installed beta; lock unchanged")

本地实测输出如下。

initial lock: ci passed
stale lock: ci rejected missing beta
updated lock: ci installed beta; lock unchanged

2. 依次检查三份输入

检查项常见问题
package.json 和 package-lock.jsonPR 只修改其中一个,或合并冲突后保留了错误的锁文件
Node.js、npm 和项目 .npmrc本机和 CI 用了不同工具链或影响依赖树的参数
安装发生的目录monorepo 在子目录运行,读取的清单和锁文件不是预期的一对

确实要修改依赖时,在项目约定的工具链下更新锁文件,审查间接依赖变化,把两份文件一起提交,再从干净目录跑 npm ci。生成锁文件时如果使用了影响依赖树的参数,例如 legacy-peer-deps,CI 安装也要应用相同配置。

小结

让 CI 直接报告清单与锁文件的偏差,再在开发侧更新和评审锁文件。还原问题时保留 npm 版本、运行目录和项目配置。

数据来源与检索时间

检索时间 2026-11-13。

来源URL对应内容
npm CLIhttps://docs.npmjs.com/cli/v11/commands/npm-ci/锁文件要求、干净安装与参数一致性

核验说明

  • npm 11.17.0 实测本地包初次安装、漂移失败与更新后安装。私有 registry、平台依赖和生命周期脚本属于实际项目环境。