mainのnode_modules symlinkをやめ、worktreeごとに依存を入れる

  • #worktree
  • #npm
  • #再現性
  • #自動処理

今回やったこと

Git worktreeで自動処理を実行するとき、mainリポジトリのnode_modulesをsymlinkで共有する構成をやめ、各worktreeの中で依存をインストールする共通処理へ寄せた。対象は8本の自動処理で、依存の再利用による短縮より、実行単位ごとの再現性を優先した。

symlinkは一見すると速い。既にインストール済みの依存をそのまま参照できるからだ。しかし、あるサイトで依存が追加されたとき、別のworktreeが想定している状態とmain側の実体がずれる。実行側は新しい依存を要求しているのに、参照先は古いまま、という組み合わせが起きると、エラーは原因ではなく別の場所を指す。自動ジョブでは、失敗を再現できないこと自体が大きな障害になる。

依存をworktreeの中へ閉じ込める

共通シェル関数npm_install_worktreeに、依存を用意する手順を集約した。引数は対象ディレクトリとログ用タグ。まずnpmの存在とpackage.jsonを確認し、lockfileがあればnpm ciを使う。ciが失敗した場合だけnpm installで再試行し、最後にnode_modulesディレクトリの存在まで確認する。

. "$REPO/scripts/lib/npm_worktree.sh"
npm_install_worktree "$WT/sites/site-directory" "site-publish" \
  || { echo "dependency setup failed"; exit 1; }

lockfileがある場合にnpm ciを優先するのは、package.jsonの現在値を解決し直すのではなく、固定された依存関係を再現するためだ。lockfileが無い場合はnpm installを使う。どちらもグローバルなnpmキャッシュを--prefer-offlineで活用するが、参照先の実体はworktree内になる。

エラーを黙らせない

依存インストールのログは一時ファイルへ保存し、--silentは使わない。無人ジョブでは終了コードだけが残っても、原因が分からなければ復旧できない。失敗したときはタグ、対象ディレクトリ、終了コード、ログ末尾を出す。npm ciからnpm installへ切り替えた場合も、その事実を警告として残す。

処理の結果は次のように分かれる。

確認失敗時の扱い
npmがPATHにあるか終了コード127で中止
package.jsonがあるか終了コード2で中止
依存インストールが成功したかログ末尾を出して中止
node_modulesが生成されたか成功扱いにしない

依存が用意できないままビルドへ進めると、後段で別のエラーになる。そこで、この関数が非ゼロを返したら呼び出し側は自動処理を中止する。原稿やデプロイ物を作るジョブでは、検証できない状態の成果物を後へ流さないことが優先される。

共有より再現性を選ぶ場面

worktreeは同じリポジトリの別作業場所だが、実行時の依存まで共有する必要はない。コードの差分と依存の解決結果を同じ作業単位に置くことで、「どのworktreeで、どのpackage-lock.jsonを使い、何をインストールしたか」を追跡できる。ディスク容量やインストール時間は増えるが、キャッシュを使えば毎回すべてをネットワークから取り直す必要はない。

この判断は、すべての環境でsymlinkが悪いという話ではない。依存が頻繁に変わる複数レーンを無人で動かし、失敗時に原因を追跡する必要がある場合に、共有の便利さより隔離の価値が上回る。自動処理では、速く開始できることより、同じ入力から同じ前提で起動できることが重要になる。

実装ステータスと更新履歴

実装済み・稼働中。worktree内の依存分離を共通処理として運用している。

  • 更新履歴: 2026-09-11 初稿。
  • 訂正履歴: なし。