開発秘話②: 指示厨ゲーム
czz(指示厨ゲーム)の開発で、「make evidence をCIでも通して、安心して発表に突入する」——そのはずだった。
現実は、Biome(formatter/linter)・pnpm(workspace/lockfile)・TypeScript(型)・Next.js(生成物)・Drizzle(依存関係)が順番に手を取り合って私を殴る展開になった。
Note
ローカルでは「通った」ものが、CIでは落ちる。 そして落ち方が毎回違う。 この手の事故は「何を直せば終わるのか」が見えにくいのが一番つらい。
当日の状況(ざっくり年表)
- ローカルで
make evidenceを回して、少しずつエラーを減らしていた pnpm -w check(Biome)とpnpm -w typecheckが、実行環境によって成功/失敗が揺れる- なんとかローカルで
OK: verify passedまで到達 - しかし GitHub Actions では別の地雷が爆発(lockfile / Biome schema / 型)
起きたこと:つらさの種類が多い
1) pnpm: frozen-lockfile と overrides 不整合
Actions でまず落ちたのがこれ。
Run pnpm install --frozen-lockfile
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH Cannot proceed with the frozen installation.
The current "overrides" configuration doesn't match the value found in the lockfile
Note
CI は --frozen-lockfile で「lockfileに書いてある通りにしか入れない」運用が多い。
だから package.json(overrides含む)を変えたら、lockfileも必ず更新してコミットが原則。
2) @types/react-dom の存在しないバージョンを指定していた
ローカルで直そうとして pnpm -w install --no-frozen-lockfile をやったら、今度はこれ。
ERR_PNPM_NO_MATCHING_VERSION No matching version found for @types/react-dom@19.2.8
The latest release of @types/react-dom is "19.2.3".
Warning
「存在しないバージョン」は、CIだと問答無用で止まる。 この手のズレは workspace のどこか(今回だと packages/ui)にハードコードされてることが多い。
3) TypeScript: Link / Switch が JSX として使えない(ReactNodeの二重世界)
Actions 側の typecheck で、こういうのが大量に出た。
'Link' cannot be used as a JSX component.'Switch' cannot be used as a JSX component.import(".../@types/react/index").ReactNode is not assignable to type React.ReactNode
Note
この症状は「Reactの型定義が複数入って、違う世界線のReactNodeが混在してる」時によく出る。 workspace だと 依存の置き方(依存の持ち方)で簡単に分裂する。
4) Next.js 生成物 .next/types を typecheck して壊れたTSを踏む
途中で apps/user/.next/types/validator.ts がパース不能で落ちたこともあった。
apps/user/.next/types/validator.ts:135:2 - error TS1005: '{' expected.
Tip
.next は生成物。壊れていたらまず rm -rf apps/user/.next(or clean)で切り分ける。
そして typecheck の対象から外す(include/exclude の確認)と精神が保たれる。
5) Biome: CIのCLIと biome.json のスキーマが噛み合わない
終盤の極めつけ。
CIで biome ci . が走った結果、こう言われた。
The configuration schema version does not match the CLI version 2.3.14Found an unknown key ignoreFound an unknown key include- (ローカルでは
organizeImportsが unknown と言われるケースも)
Warning
「ローカルでは動くのに、CIで設定が読めない」は本当にしんどい。 原因はだいたいこれ:
- CIが使ってる Biome のバージョン
- ルートとサブpackageで Biome の設定や導線が分裂している
biome.jsonが新旧どちらの書き方になっているか
その場で取った対処(結果として効いたやつ)
Note
ここは “私の現場判断” であって、唯一の正解ではない。 ただ、当日「終わらせる」ためには、理想よりも 収束 が優先だった。
A) 依存の整合(存在しないバージョンをやめる)
@types/react-domを実在するバージョンへ揃える(例: 19.2.3)- workspace 内の複数箇所に同じ型が入らないように集約(可能なら root へ寄せる)
B) overrides は root に置く
packages/ui の pnpm.overrides は警告通り効かない。
Tip
workspace の overrides は ルートの package.json に置いて、lockfile に反映させてコミット。
C) コマンドの実行コンテキストを揃える
vitest が command not found でも、pnpm -w exec vitest run なら動いた。
pnpm -w exec vitest run
Note
“グローバルに入ってない” のは正常。
workspace のbinを使うには pnpm exec / pnpm -w exec で呼ぶのが安全。
学び(つらいけど、次に効くやつ)
1) 「ローカルで通る」は半分しか意味がない
CIは別OS・別Node・別キャッシュ・別インストール戦略。 「CIで通る」だけが勝ち、という悲しい真理がある。
2) workspace は便利だけど、依存が分裂しやすい
React型が二重に入ると、UIコンポーネント全部が死ぬ。
特に @types/* は、雑に増えるとすぐ世界線が割れる。
3) formatter/linter は “同じバージョン” を全員に強制する
Biome のCLIと設定スキーマがズレたら、コード以前に土台が崩れる。 だから Biome自体のバージョン固定(とCI側の導線固定)が重要。
次の一手(発表に間に合わせるための現実案)
Tip
発表までの優先度は「新機能」じゃなくて「再現性」 つまり “いつでも同じ結果が出る” を先に作る。
- Node / pnpm / Biome / TypeScript の バージョンを固定(CIも同じ)
- workspace で React の型定義が増殖しない構造にする(root 集約)
make evidenceの中で呼ぶコマンドはpnpm -w exec ...に寄せる(実行経路の統一).nextの扱い(cleanとexclude)を決めて、型チェック対象を安定させる
おわりに
今日は、進捗よりも「事故対応」に時間を吸われた。 でも、こういう日があると「仕組みが回った時のありがたさ」が骨に染みる。
Note
CIは敵じゃない。 ただ、敵みたいな顔で殴ってくることがある。
次は、殴られた痕跡をドキュメントにして、未来の私のHPを守る。