1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

npm ci で Missing: typescript@5.9.3 from lock file が出たとき、npm ls と npm why で犯人を特定する

1
Posted at

はじめに

Cloudflare Workers へのデプロイ時、ビルドが npm ci の段階で失敗しました。

npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error Missing: typescript@5.9.3 from lock file

ローカルでは npm installnpm run build も通っているのに、CI だけが止まる状態です。

この記事は解決方法の紹介ではなく、原因を突き止めるまでに使ったコマンドと、その出力の読み方をまとめたものです。同種のエラーは原因がプロジェクトごとに違うため、「何を打てば犯人が分かるか」を知っておくほうが応用が利くと考えました。

対象読者は、npm ci 関連のエラーに遭遇したが、どこから調べればいいか分からない方です。

問題

環境

  • Windows 11 / PowerShell
  • Node.js 22.16.0 / npm 10.9.2
  • Vite + React + Vitest
  • デプロイ先:Cloudflare Workers

エラー全文

2026-07-28T12:26:24.770929Z	Installing project dependencies: npm clean-install --progress=false
2026-07-28T12:26:27.048687Z	npm error code EUSAGE
2026-07-28T12:26:27.049104Z	npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync.
2026-07-28T12:26:27.049196Z	npm error Missing: typescript@5.9.3 from lock file

何が分からなかったか

package.json の TypeScript 指定はこうなっています。

"typescript": "~6.0.2"

~6.0.2>=6.0.2 <6.1.0 の意味で、5.9.3 はこの範囲に入りません。自分が書いた指定からは絶対に出てこない数字が要求されている、という点が最初の謎でした。

「Node のバージョンが違う環境で作った lock を別環境で npm ci したせいか」とも考えましたが、これも後述の調査で否定されます。

解決方法

※前述の通り、この章では原因特定に使ったコマンドと出力の読み方を扱います。

前提:npm cinpm install の違い

なぜローカルは通って CI だけ落ちるのか。両者は挙動が根本的に違います。

npm install npm ci
lock ファイル 必要なら書き換える 読むだけ。絶対の正とする
不整合時 解決してインストール エラーで停止
node_modules 差分更新 最初に全削除してから再構築

npm install は不整合を裏で吸収するため問題が表面化しません。npm ci は妥協しないので、そこで初めて露呈します。「ローカルで動く」は CI が通る保証にならない、というのが出発点になります。


コマンド1:npm ls typescript

依存ツリーの中で、TypeScript がどこに・どのバージョンで存在するかを表示します。今回はこのコマンドの出力に答えが全部書かれていました。

npm ls typescript

出力(抜粋)

mahjong-scoring@0.0.0 C:\work\20260702_mahjong_scoring
├─┬ typescript-eslint@8.63.0
│ ├─┬ @typescript-eslint/eslint-plugin@8.63.0
│ │ ├─┬ @typescript-eslint/type-utils@8.63.0
│ │ │ └── typescript@6.0.3 deduped invalid: "^5.0.0" from node_modules/vite-tsconfig-paths/node_modules/tsconfck
│ │ ├─┬ ts-api-utils@2.5.0
│ │ │ └── typescript@6.0.3 deduped invalid: "^5.0.0" from node_modules/vite-tsconfig-paths/node_modules/tsconfck
│ │ └── typescript@6.0.3 deduped
...
├── typescript@6.0.3
└─┬ vite-tsconfig-paths@6.1.1
  └─┬ tsconfck@3.1.6
    └── typescript@6.0.3 deduped invalid: "^5.0.0" from node_modules/vite-tsconfig-paths/node_modules/tsconfck
npm error code ELSPROBLEMS
npm error invalid: typescript@6.0.3 C:\work\20260702_mahjong_scoring\node_modules\typescript

見方

出力が長いので圧倒されますが、注目すべきキーワードは3つだけです。

deduped
同じパッケージを上位(node_modules/typescript)で共有している、という意味。実体は1つで、そこを参照しているだけ。正常な状態を示す語なので、これ単体は問題ではありません。

invalid: "^5.0.0" from <パス> ← ここが本命
分解するとこうなります。

  • "^5.0.0" … 要求されているバージョン範囲
  • from node_modules/vite-tsconfig-paths/node_modules/tsconfck … その要求を出している者
  • invalid … 実際に置かれている 6.0.3 がその範囲外

^5.0.0>=5.0.0 <6.0.0 なので、6.0.3 は満たしません。**「tsconfck が TypeScript 5 系を要求しているのに、共有されている実体は 6.0.3」**と npm が明示してくれています。

ELSPROBLEMS
npm ls が問題を検出して非ゼロ終了しただけのコード。別種のエラーが追加で起きたわけではないので、慌てなくて大丈夫です。

ここで分かったこと

CI が要求してきた 5.9.3 の出どころが、自分の package.json ではなく tsconfck^5.0.0 という要求だと判明しました。5.9.3^5.0.0 を満たす最新版として npm が選んだ数字だったわけです。

補足オプション

npm ls typescript --all   # 深さ制限を外して全階層を表示

コマンド2:npm why tsconfck

tsconfck は入れた覚えがないパッケージだったので、どこから来たのかを逆引きします。

npm why tsconfck

出力

tsconfck@3.1.6 dev
node_modules/vite-tsconfig-paths/node_modules/tsconfck
  tsconfck@"^3.0.3" from vite-tsconfig-paths@6.1.1
  node_modules/vite-tsconfig-paths
    dev vite-tsconfig-paths@"^6.1.1" from the root project

見方

下から上に読みます。

  • 最下行 from the root project … 自分の package.jsonvite-tsconfig-paths@"^6.1.1" が書かれている
  • その上 tsconfck@"^3.0.3" from vite-tsconfig-paths@6.1.1 … その vite-tsconfig-pathstsconfck を連れてきた
  • 最上行 tsconfck@3.1.6 dev … 結果として 3.1.6 が入った。dev は開発時のみ使う依存の意味

図にするとこうなります。

自プロジェクト
  └ vite-tsconfig-paths@6.1.1   ← 自分で入れた(直接依存)
      └ tsconfck@3.1.6          ← 上が連れてきた(推移的依存)
          └ typescript ^5.0.0 を要求  ← 衝突の原因

ここで分かったこと

犯人は1人だけと確定しました。tsconfck は自分で選んだものではなく vite-tsconfig-paths の付属品なので、直接バージョンを操作することはできません。手を入れるべきは1つ上の vite-tsconfig-paths だと分かります。

分岐が複数あると対応が複雑になりますが、今回は単線だったので判断が楽でした。


全体像の整理

ここまでの調査で、なぜ CI だけ落ちるのかがつながりました。

  • ローカルの npm は妥協した。node_modules/typescript に 6.0.3 を1つだけ置き、tsconfck の要求には invalid の印を付けて済ませた。だから動いているように見えた
  • CI の npm ci は妥協しない。「tsconfck の下に TypeScript 5 系をネストして置くべき」と理想ツリーを再計算する。ところが lock ファイルにその 5.9.3 のエントリが存在しない → Missing: typescript@5.9.3 from lock file

つまり本質は、同じ lock ファイルをローカルと CI が別々に解釈していたことでした。lock ファイルが「妥協した状態」で作られていたため、CI が求める情報が欠けていたわけです。

なお「エントリ」とは、lock ファイル内の1パッケージ分のレコードのこと。packages オブジェクトのキーと値のペアを指します。

"packages": {
  "node_modules/typescript": {
    "version": "6.0.3",
    "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
    "integrity": "sha512-...",
    "dev": true
  }
}

npm cipackage.json から理想ツリーを計算し、その各要素に対応するエントリが lock にあるかを突き合わせます。無ければ Missing: X@version from lock file となります。

おわりに

今回使ったコマンドを、目的別に整理します。

目的 コマンド 見るべき箇所
どこに何が入っているか npm ls <pkg> invalid: "範囲" from パス
なぜ入っているのか npm why <pkg> 下から上に読む依存の連鎖

得られた教訓は以下です。

  • npm lsinvalid 表示に答えが書いてある。要求バージョン範囲と要求元のパスがそのまま出力されるので、エラーメッセージより情報量が多い
  • npm why で依存の連鎖をたどる。身に覚えのないパッケージ名が出てきたら、まずこれ
  • 推移的依存の衝突は package.json を見ても分からない~6.0.2 と書いてあるのに 5.9.3 を要求される、という一見矛盾した状況が起きる
  • エラーメッセージの数字を、自分の書いた指定と突き合わせる。今回は「~6.0.2 の範囲に 5.9.3 は入らない」という気づきが、犯人が他にいると判断する最初の手がかりになった
  • デプロイ前にローカルで npm ci を通すnpm install で開発し、コミット前に npm ci で検証する流れにすれば、この種の CI 落ちは事前に検出できる

TypeScript 6 のようなメジャーバージョンが出た直後は、周辺エコシステムが追随しきっておらず、この種の衝突が起きやすくなります。--force--legacy-peer-deps で押し切るとローカルだけ動いて CI で落ちる状態を作り込むことになるので、まずは原因の特定を優先するのが結局は近道でした。

参考

JISOUのメンバー募集中!

プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?