ローカルで通るのに CI で npm ci が落ちる時は、 .npmrc もチェックする
ローカルで npm ci が通るのにGitHub に push すると Cloudflare のビルド落ちる。ログを見ると、「Missing from lock file」出てくるのですが、ローカルも CI も同じ pa […]
目次
ローカルで npm ci が通るのにGitHub に push すると Cloudflare のビルド落ちる。ログを見ると、「Missing from lock file」出てくるのですが、ローカルも CI も同じ package-lock.json を使っています。コードも同じ、ロックファイルも同じ、それなのになぜ結果が違うのでしょうか。
このような場面に遭遇したので、簡単にまとめました。
エラーの正体
Cloudflare のビルドログには、こう書かれていました。
npm error `npm ci` can only install packages when your package.json
and package-lock.json or npm-shrinkwrap.json are in sync.
npm error Missing: [email protected] from lock file
npm error Missing: @types/[email protected] from lock file
npm ci は、package-lock.json を厳密に検証します。package.json と package-lock.json に不一致があれば、エラーで止まります。それが npm ci の仕事です。
しかし、ローカルでは同じコマンドが成功しています。同じ package-lock.json を使っているのに、ローカルと CI で結果が違います。
環境の違いを調べるために、ローカルの npm 設定を確認しました。
$ npm config list
; "user" config from /Users/sandbox/.npmrc
//registry.npmjs.org/:_authToken = (protected)
init-author-name = "hideokamoto"
legacy-peer-deps = true
; node version = v22.13.0
; npm version = 10.9.2
legacy-peer-deps = true が設定されています。これが問題の原因でした。
.npmrc は npm の設定ファイルで、グローバル(ユーザーレベル)とプロジェクトレベルの両方に配置できます。グローバルの ~/.npmrc に設定した内容は、すべてのプロジェクトに影響します。
私のローカル環境では、過去のどこかで legacy-peer-deps = true をグローバルに設定していました。そしてそれを忘れていたために、ローカルとビルドで環境差が発生していたということです。
グローバル or プロジェクトで .npmrcを調整する
原因がわかったので、あとは設定を揃えるだけです。グローバルで設定してもいいのですが、プロジェクトルートに .npmrc を作成する形でも対応できます。
$ echo "legacy-peer-deps=false" > .npmrc
あとはnode_modules を削除して、npm ci を実行してみましょう。
$ rm -rf node_modules
$ npm ci
npm error code EUSAGE
npm error Missing: [email protected] from lock file
npm error Missing: @types/[email protected] from lock file
CI と同じエラーが出ました。
プロジェクトレベルの .npmrc は、ユーザーレベル(グローバル)の設定を上書きします。legacy-peer-deps=false を明示することで、グローバル設定の影響を排除し、CI と同じ条件をローカルで再現できました。
問題が再現できれば、あとは解決するだけです。
まとめ
グローバルの .npmrc は便利です。すべてのプロジェクトに適用される設定を一箇所で管理できます。しかし、それは予期しない動作の違いを引き起こす可能性があります。そして何より厄介なのが、「なんでそういう設定にしたかを思い出せないこと」です。
念の為PJ単位での設定として対応しましたが、将来的にはCIと同じようになるよう調整したいところです。