Claude Code 活用による npm から pnpm 移行で遭遇したハマりポイントと解決策

Claude Code 活用による npm から pnpm 移行で遭遇したハマりポイントと解決策

2025.12.04

こんにちは。アノテーションの及川です。

クラスメソッドではAI駆動開発を推進するための取り組みを行っており、クラスメソッド社員も実際に日常的にAIを使って開発を行っています。

弊社メンバーが日常的に実践するAI駆動開発のナレッジやTipsを共有するために、AI駆動開発 Advent Calendar 2025を開催しています。

本ブログは、この企画の4日目の記事になります。

もしAI駆動開発の最先端を知りたい方は、この1ヶ月間、ぜひ本アドベントカレンダーをチェックしてみてください。

https://adventar.org/calendars/11778

はじめに

先日、モノレポ構成のプロジェクト(以前こちらの記事でご紹介した作成した TODO アプリ)を npm から pnpm に移行する機会がありました。

今回は Claude Code と共に、計画・参考資料・進捗・手順のドキュメントを作成しながら、ステップに分けて移行作業を進めました。

基本的な移行手順は公式ドキュメント通りに進められた一方で、GitHub Actions での CI/CD 対応で予想外のハマりポイントがいくつかありました。

今回は、その経験を共有したいと思います。

この記事で紹介すること

  • npm から pnpm への基本的な移行手順
  • phantom dependencies 問題と対処法
  • GitHub Actions での CI/CD 対応ポイント
  • Husky フック対応での注意点

Claude Code との共創

今回の移行作業は、Claude Code と対話しながら進めました。

依頼時のプロンプト

Claude Code には、以下のような依頼を行いました。

  • 本プロジェクトを npm から pnpm に安全に移行すること
  • 既存のソースコード、テストコードに影響を与えないこと
  • 各ステップについて 1 ステップごとに確実に実施すること
  • 移行手順を漏れなくドキュメントに記載すること

コンテキストとして提示したファイル

Claude Code がプロジェクトを円滑に理解して進めれるように、以下のファイルを事前に用意しコンテキストとして提示しました。

ファイル 内容
計画.md 移行の目的と依頼事項
参考.md 参考 URL(pnpm 公式ドキュメント、移行ブログ記事など)
進捗.md 各 Step の進捗状況を記録
手順.md 実施した内容、背景、実行コマンド、修正ファイルを詳細に記録

以下は、計画.md の抜粋です。

## 目的

- 本プロジェクトを npm から pnpm に安全に移行すること
- 既存のソースコード、テストコードに影響を与えないこと

## 依頼事項

- ステップ毎に確実に実施

1. 現状の実装コードの調査
2. 参考資料の参照・確認
3. 安全な npm→pnpm 移行のための手順を計画
4. 計画に従い、ステップバイステップで実施
5. 移行手順を手順.md ファイルに漏れなく記載

以下は、手順.md の抜粋です。各ステップを以下のフォーマットで記録しました。

## Step N: ステップのタイトル

### 目的

このステップで達成すること

### 公式ドキュメント参照

- **pnpm xxx**: https://pnpm.io/xxx

### 実施内容

#### N.1 作業項目

**背景・理由**:

- なぜこの変更が必要なのか
- 公式ドキュメントからの引用

**修正ファイル**: `対象ファイル名`

**変更前**:
(変更前のコード)

**変更後**:
(変更後のコード)

**結果**:

- ✅ 完了

このフォーマットで記録することで、後から「なぜこの変更をしたのか」が明確になり、振り返りなどの整理に役立ちました。

ポイント

  • ステップバイステップ: 一度に全部やらせず、1 ステップごとに確認しながら進める
  • ドキュメント化: 移行手順を記録させることで、後から振り返りやすくなる
  • 公式ドキュメントの提示: 公式ドキュメントの設定例を参照させることで、正しい設定方法を学習させる

pnpm とは

https://pnpm.io/ja/

pnpm は npm や yarn の代替となるパッケージマネージャーです。主な特徴は以下の通りです(詳細は公式ドキュメントをご参照ください)。

  • 高速なインストール: ハードリンクを活用した効率的なパッケージ管理
  • ディスク容量の節約: 同じパッケージを複数プロジェクトで共有
  • 厳格な依存関係解決: phantom dependencies を許容しない

特に 3 つ目の「厳格な依存関係解決」は、npm からの移行時に最もハマりやすいポイントだと思います。

基本的な移行手順

1. pnpm-workspace.yaml の作成

npm workspaces から pnpm workspaces へ移行するには、pnpm-workspace.yaml を作成します。

packages:
  - apps/*
  - packages/*

https://pnpm.io/pnpm-workspace_yaml

2. package.json の更新

ルートの package.json を以下のように更新します。

{
  "engines": {
    "node": ">=22.x",
    "pnpm": ">=10.x"
  },
  "packageManager": "pnpm@10.0.0"
}

packageManager フィールドは Corepack と連携し、チーム全員が同じバージョンの pnpm を使用できるようにします。

https://pnpm.io/package_json

https://pnpm.io/installation

また、workspaces フィールドは pnpm では使用しないため削除します。

3. pnpm only の制約を追加

pnpm 以外のパッケージマネージャーをブロックするため、preinstall スクリプトを追加します。

{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

https://pnpm.io/only-allow-pnpm

4. ロックファイルの変換

pnpm import コマンドで既存の package-lock.json から pnpm-lock.yaml を生成します。

# 既存の node_modules を削除
rm -rf node_modules */node_modules

# ロックファイルを変換
pnpm import

# 古いロックファイルを削除
rm package-lock.json

# 依存関係をインストール
pnpm install

https://pnpm.io/cli/import

5. npm コマンドを pnpm コマンドに変換

npm pnpm
npm run <script> --workspaces pnpm -r <script>
npm run <script> -w <workspace> pnpm --filter <workspace> <script>
npm ci pnpm install --frozen-lockfile

https://pnpm.io/cli/run

https://pnpm.io/filtering

phantom dependencies 問題

問題の概要

pnpm への移行後、以下のようなエラーが発生することがあります。

Error: Cannot find module 'ajv/dist/core'

これは phantom dependencies 問題と呼ばれるものです。

Phantom Dependencies

You’ll find that pnpm combats this problem by enforcing stricter package access rules - a package can only access its explicitly declared dependencies. This limits the risk associated with phantom dependencies, providing you with a more predictable and secure environment.

npm と pnpm の違い

npm では、依存関係がホイスティング(巻き上げ)されるため、直接依存していないパッケージにもアクセスできてしまいます(暗黙的な依存)。

一方、pnpm は厳格な依存関係解決を行うため、明示的に依存宣言されていないパッケージにはアクセスできません。

# npm の場合(ホイスティング)の例
node_modules/
  ajv/          <- 誰でもアクセス可能

# pnpm の場合(厳格)の例
node_modules/
  .pnpm/
    ajv@x.x.x/
      node_modules/
        ajv/  <- 直接依存しているパッケージのみアクセス可能

解決策

エラーで要求されているモジュールを、明示的に devDependencies に追加します。

{
  "devDependencies": {
    "ajv": "8.17.1"
  }
}

私の場合、以下のパッケージを追加する必要がありました。

  • ajv: OpenAPI バリデーターの間接依存
  • esbuild: AWS CDK の Lambda バンドリングに必要

https://pnpm.io/faq#pnpm-does-not-work-with-your-project-here

GitHub Actions での CI/CD 対応

1. pnpm のセットアップ

GitHub Actions で pnpm を使用するには、pnpm/action-setup を追加します。

- name: Install pnpm
  uses: pnpm/action-setup@v4

- name: Setup Node
  uses: actions/setup-node@v4
  with:
    node-version-file: "package.json"
    cache: pnpm # npm から pnpm に変更

https://github.com/pnpm/action-setup

2. node_modules キャッシュは使わない

ここが最大のハマりポイントでした。

当初、以下のように node_modules をキャッシュしていました。

# 問題のある設定
- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: "**/node_modules"
    key: ${{ runner.os }}-node-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install dependencies
  if: steps.cache.outputs.cache-hit != 'true'
  run: pnpm install --frozen-lockfile

この設定には問題があります。

問題点:

  • キャッシュがヒットすると pnpm install がスキップされる
  • 新しい依存関係がキャッシュに含まれていない
  • phantom dependencies 修正後も古いキャッシュが使われ続ける

解決策:

node_modules キャッシュを廃止し、毎回 pnpm install を実行するようにしました。

# 改善後の設定
- name: Install pnpm
  uses: pnpm/action-setup@v4

- name: Setup Node
  uses: actions/setup-node@v4
  with:
    node-version-file: "package.json"
    cache: pnpm # pnpm ストアをキャッシュ

- name: Install dependencies
  run: pnpm install --frozen-lockfile

actions/setup-nodecache: pnpm オプションにより、pnpm ストアがキャッシュされます。これにより、pnpm install は高速に実行されつつ、最新の依存関係が確実にインストールされます。

3. paths-filter への pnpm-lock.yaml 追加

CI の変更検知に dorny/paths-filter を使用している場合、pnpm-lock.yaml を追加しておくと、依存関係の変更時に CI が確実にトリガーされます。

filters: |
  app:
    - "src/**"
    - "package.json"
    - "pnpm-lock.yaml"  # 追加

Husky フック対応

npx から pnpm への変換

Husky フック内の npx コマンドも pnpm に変換する必要があります。

# 変更前
npx lint-staged

# 変更後
pnpm exec lint-staged

pnpm dlx vs pnpm exec の違い

ここで注意が必要なのは、pnpm dlxpnpm exec の違いです。

コマンド 動作
npx <pkg> ローカルにあれば使用、なければダウンロード
pnpm dlx <pkg> レジストリから取得して一時的に実行(依存関係には追加しない)
pnpm exec <pkg> プロジェクトの依存関係にあるパッケージを実行

pnpm dlx はレジストリからパッケージを取得し、依存関係としてインストールせずに一時的に実行するコマンドです。一方、pnpm exec はプロジェクトの node_modules/.bin にあるコマンドを実行します。devDependencies にインストール済みのパッケージを使いたい場合は pnpm exec を使用します。

https://pnpm.io/cli/dlx

https://pnpm.io/cli/exec

pre-commit フックで lint-staged を使う場合、pnpm exec lint-staged とすることで、devDependencies で pinned されたバージョンが確実に使用されます。

ゼロデイ攻撃対策

pnpm には、公開から一定期間が経過していないパッケージのインストールを防止する minimumReleaseAge 設定があります。

設定例

# pnpm-workspace.yaml
minimumReleaseAge: 20160 # 14 days (14 * 24 * 60 = 20160 minutes)

この設定例により、公開から 14 日未満のパッケージバージョンはインストールされなくなります。悪意のあるパッケージは公開後、通常数時間〜数日以内に検出・削除されるため、この待機期間でサプライチェーン攻撃のリスクを少しでも軽減できます。

https://pnpm.io/settings#minimumreleaseage

まとめ

npm から pnpm への移行で遭遇したハマりポイントをまとめると、以下の通りです。

移行時のチェックリスト

  • pnpm-workspace.yaml を作成
  • package.json の packageManager フィールドを追加
  • workspaces フィールドを削除
  • preinstall スクリプトで pnpm only 制約を追加
  • pnpm import でロックファイルを変換
  • npm/npx コマンドを pnpm コマンドに変換
  • GitHub Actions で pnpm/action-setup を追加
  • node_modules キャッシュを廃止
  • Husky フックの npx を pnpm exec に変更
  • minimumReleaseAge でゼロデイ攻撃対策

特に注意すべきポイント

  1. phantom dependencies: エラーが出たら明示的に依存関係を追加
  2. GitHub Actions キャッシュ: node_modules ではなく pnpm ストアをキャッシュ
  3. pnpm dlx vs pnpm exec: 用途に応じて使い分ける

pnpm の厳格な依存関係解決は、最初はハマりやすいですが、一度対応すれば依存関係の管理が明確になり、予期せぬバグを防ぐことができます。

この記事が誰かのお役に立てば幸いです。

アノテーション株式会社について

アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。

参考資料

この記事をシェアする

FacebookHatena blogX

関連記事