[GitHub Actions] モノリポ環境の node_modules をキャッシュする際にはパスの指定に気をつけよう

2023.10.28

こんにちは、CX 事業本部 Delivery 部の若槻です。

npm workspaces を使用すると、モノリポ環境の依存関係(node_modules)を容易に管理可能になります。

今回は、GitHub Actions でモノリポ環境の node_modules をキャッシュする際のパス指定でハマった事象について紹介します。

最初に結論

GitHub Actions のワークフローで actions/cache を使って node_modules をキャッシュする際には、path**/node_modules と指定する必要がありました。

on:
  pull_request:

jobs:
  integrate:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Cache Dependency
        uses: actions/cache@v3
        id: cache_dependency
        env:
          cache-name: cache-dependency
        with:
          path: "**/node_modules" # 正しいパス指定
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}

事象

当初、ワークフローでの actions/cache でのパスを以下のように node_modules と指定していました。

.github/workflows/build.yml(誤った記述)

on:
  pull_request:

jobs:
  integrate:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Cache Dependency
        uses: actions/cache@v3
        id: cache_dependency
        env:
          cache-name: cache-dependency
        with:
          path: node_modules # 誤ったパス指定
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}

      - name: Install Dependency
        if: ${{ steps.cache_dependency.outputs.cache-hit != 'true' }}
        run: npm ci

      - name: Lint & Format
        run: |
          npm run lint
          npm run format

      - name: tsc
        run: npm run type-check-all

      - name: Cdk Synth
        run: npm run synth:dev

      - name: Unit Test
        run: npm run test-unit -- run

上記のワークフローは 1 回目の実行では正常にパスしました。

しかし 2 回目以降のワークフロー実行では、tsc による型チェックで Cannot find module '@aws-sdk/lib-dynamodb' or its corresponding type declarations. のようなエラーが発生するようになりました。

原因

事象が発生したワークフローではルートの node_modules のみをキャッシュ対象としていました。しかし npm workspaces 環境では次のようにルート以外の各ワークスペースのパスにも node_modules が作成されて管理されます。

$ find . -name "node_modules" -type d -not -path "./node_modules/*"
./node_modules
./packages/server/node_modules
./packages/e2e/node_modules

そのため、各ワークスペースのパスの node_modules がキャッシュおよび復元されない場合には、ルートの node_modules には存在しないモジュールのインポートが試行された際に前述のようなエラーが発生します。

対処

よって、冒頭で示した通り、actions/cache でのパスを以下のように **/node_modules と指定することで、ルートに加えて各ワークスペースの node_modules がキャッシュされるようになります。

.github/workflows/build.yml

on:
  pull_request:

jobs:
  integrate:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Cache Dependency
        uses: actions/cache@v3
        id: cache_dependency
        env:
          cache-name: cache-dependency
        with:
          path: "**/node_modules" # 正しいパス指定
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}

      - name: Install Dependency
        if: ${{ steps.cache_dependency.outputs.cache-hit != 'true' }}
        run: npm ci

      - name: Lint & Format
        run: |
          npm run lint
          npm run format

      - name: tsc
        run: npm run type-check-all

      - name: Cdk Synth
        run: npm run synth:dev

      - name: Unit Test
        run: npm run test-unit -- run

上記対処後は、キャッシュが利用された場合でも、tsc 含めた各ステップが正常に完了するようになりました。

おわりに

GitHub Actions でモノリポ環境の node_modules をキャッシュする際のパス指定でハマった事象についてご紹介しました。

キャッシュが使われる(= package-lock.json に変更が無い)ワークフロー実行時に初めて今回の事象が発生するようになったため、若干原因究明にまごつきましたが、結論として単純な考慮忘れによるものでした。社内で助け舟を出してくださったたにもんさんには感謝。

以上