Dependabotで依存パッケージの更新確認と自動マージ

## 先に結論だけ

Dependabotと自動マージを併用することで、依存パッケージの更新作業を簡略化でき、手作業によるミスを防げます。パッケージ更新作業を負担に感じる方はぜひ導入を検討してください。

マージの権限をワークフローに与えるのは、抵抗感があるかもしれません。例えば、開発環境にしか影響を与えないパッケージのパッチバージョンの更新のみ許可する設定から始めてみましょう。

## はじめに

この記事は、Dependabotを使って依存パッケージの更新を自動化する方法を共有するためのものです。

### 対象とする読者

- GitHubのリポジトリを運用しているエンジニア
- すでにテスト自動化は導入している
- GitHub Actionsを使ったことがある
- 依存関係の更新や管理が作業負担に感じる人

テスト自動化に関する内容はこの記事では取り扱いません。

### 対象とする環境

- 2024/08/09時点のGitHubおよびDependabot
- node.js v20.16.0
- npm v10.8.2

この記事ではパッケージマネージャーにnpmを利用しています。他のパッケージマネージャーを利用している場合は、適宜読み替えてください。

## Dependabotとは

Dependabotは、GitHubが提供する依存関係スキャニングツールです。この記事で対象となるのはDependabotのバージョンアップデートですが、Dependabotには他にも以下の機能があります。

### Dependabotアラート

Dependabotアラートは、脆弱性のある依存関係を検出し、リポジトリの所有者に通知するツールです。

https://docs.github.com/ja/code-security/dependabot/dependabot-alerts/about-dependabot-alerts

Dependabotアラートはすべてのリポジトリで有効になっています。

#### Dependabotセキュリティアップデート

Dependabotセキュリティアップデートは、脆弱性を修正するプルリクエストを作成するツールです。

https://docs.github.com/ja/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates

この機能は、[ユーザーが設定](https://docs.github.com/ja/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates)しない限り有効になりません。

リポジトリの`Setting`タブ→`Code security and analysis`→`Dependabot`セクション→`Dependabot security updates`→`Enable`ボタンで設定できます。

### Dependabotバージョンアップデート

Dependabotバージョンアップデートは、依存パッケージの最新バージョンを確認し、プルリクエストを作成するツールです。セキュリティアップデートとは異なり、脆弱性の有無には関係なく更新のたびにプルリクエストが作成されます。

https://docs.github.com/ja/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates

Dependabotバージョンアップデートは、ユーザーが`dependabot.yml`という設定ファイルを設置すると有効化されます。設定した期間ごとにパッケージの更新を確認し、プルリクエストを発行します。

## GitHubのAuto Merge機能

Auto mergeは、ブランチプロテクションルールのチェックをパスしたプルリクエストを自動でマージする機能です。

https://docs.github.com/ja/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request

Auto merge機能を有効化する手順は[自動マージを有効化する](#自動マージを有効化する)のセクションで解説します。

## Dependabotで自動マージ

Dependabotバージョンアップデートと、GitHubの自動マージ機能を組み合わせることで、依存パッケージの更新を自動化できます。

### 自動テスト環境を整える

まずは、依存パッケージの更新による影響を確認するための自動テスト環境を整えます。node.jsには、以下のようなテストランナーがあります。

- [Node.js Test Runner](https://nodejs.org/api/test.html)
- [Jest](https://jestjs.io/ja/)
- [Vitest](https://vitest.dev/)

この記事ではテストそのものの実装は割愛します。依存パッケージの更新によって機能が破壊されないか、自動テストで検出できるようになるのが理想です。

### 自動テストをGitHub Actionsで実行する

GitHub Actionsを使って、プルリクエストが作成されたときに自動テストを実行するワークフローを設定します。

```yaml
name: Run tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [20.x, 22.x]

    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - run: npm ci
      - run: npx test
```

このワークフローは、デフォルトブランチにプルリクエストが作成されたときに`npm test`コマンドで自動テストを実行します。

より詳細な設定方法は以下のリンクを参照してください。

https://docs.github.com/ja/actions/automating-builds-and-tests/building-and-testing-nodejs

### ブランチプロテクションルールを有効化する

テストが失敗した場合、デフォルトブランチへのマージを阻止する設定を追加します。

GitHubの`Settings`タブ→`Rules`→`Rulesets`→`New ruleset`→`New branch ruleset`でブランチプロテクションルールを作成します。

![add new ruleset.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9556/c2335df2-0580-a82c-913f-9588fb644dbb.png)

以下のスクリーンショットの手順にそって、プロテクションルールを設定します。

![set protection rule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9556/18d3e40f-cbab-1ed7-2097-18c5b855aa40.png)

ポイントになるのは`Require status checks to pass`と`Require branches to be up to date before merging`の設定です。この設定で、テストが失敗した場合、デフォルトブランチへのマージが阻止されるようになります。

この設定は、Auto mergeを機能させるための最低限のものです。皆さんのプロジェクトに合わせて、`Require linear history`（線形履歴の強制）や`Require a pull request before merging`（プルリクエストを経由しないマージを禁止）などより厳密な設定を検討してください。

### Dependabotバージョンアップデートを有効化する

Dependabotのバージョンアップデート機能を有効化します。リポジトリに`/.github/dependabot.yml`というファイルを作成します。

```yaml 
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
```

Dependabotはこの設定ファイルを読み込んで、依存パッケージの監視を開始します。

### 自動マージを有効化する

GitHubリポジトリのAuto merge機能を有効化します。リポジトリの`Settings`タブ→`General`→`Pull Requests`セクション→`Allow auto-merge`をチェックします。

![enable auto merge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9556/9aa086d3-0014-24ce-05e9-57e76019ef0a.png)

この時点でGitHubのWeb画面から自動マージが可能です。プルリクエストを作成して、テストが完了する前に`Enable auto-merge`ボタンをクリックすると自動マージが予約されます。テストにパスした時点で、そのプルリクエストがデフォルトブランチにマージされます。

### Pull Requestを自動マージする

マージをGitHub Actionsで自動化します。以下のワークフローを設定します。

▼ `.github/workflows/dependabot-auto-merge.yml`

```yaml
name: Dependabot auto-merge
on: 
  pull_request:
    branches:
      - main
      
permissions:
  contents: write
  pull-requests: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
      - name: Enable auto-merge for Dependabot PRs
        if: steps.metadata.outputs.dependency-type == 'direct:development' && steps.metadata.outputs.update-type == 'version-update:semver-patch'
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
```

https://docs.github.com/ja/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request

このワークフローでは、package.jsonの`devDependencies`に登録されているモジュールが、パッチ更新された場合に自動マージを行います。マイナー以上のバージョンアップデートや、`dependencies`に登録されているモジュールのアップデートはユーザーの判断を待ちます。

Dependabotが作成したプルリクエストの内容は、`dependabot/fetch-metadata`というアクションで取得できます。

- `dependency-type` : `dependencies`か`devDependencies`のどちらの更新か
- `update-type` : セマンティックバージョンの種類

などの情報がメタデータに格納されます。

メタデータのより詳細な情報は公式サイトを参照してください。

https://github.com/dependabot/fetch-metadata

### 自動マージ後のトリガーに関する問題と対処方法

#### 問題 : 自動マージ後にワークフローがトリガーされない

Dependabotが作成したプルリクエストを、`rebase and merge`でデフォルトブランチにマージすると、GitHub Actionsの`push`や`pull_request:closed`イベントがトリガーされません。

https://github.com/dependabot/dependabot-core/issues/1150

#### 問題の原因

Dependabotはプルリクエストが解決された場合、即座に自分自身が作ったブランチを削除します。私の現在の理解では、自動マージとブランチ削除が同時に行われた場合`pull_request:closed`イベントはトリガーされないようです。また、Dependabotが`rebase and merge`でマージした場合、デフォルトブランチの`push`イベントもトリガーされませんでした。

#### 影響

このトリガーの取りこぼしは、たとえばデフォルトブランチが更新されるたびにデモページを更新し、GitHub Pagesにデプロイするというユースケースで問題を引き起こします。

- ドキュメントが最新状態に維持されていない
- 依存パッケージの更新によってドキュメントのビルドに失敗しても気づけない

#### 対処方法

この問題に対して私は

- Dependabotが`rebase and merge`した場合、処理を取りこぼすことがある
- プルリクエストの作成イベントは正常にトリガーされるため、デフォルトブランチの`push`および`pull_request`イベントでドキュメントのビルドワークフローを実行する
- `if: ${{ github.ref == 'refs/heads/main' }}`を利用して、デフォルトブランチのheadを参照しているときだけデプロイする

という方法で対処しました。

具体的なワークフローは以下の通りです。

▼ `.github/workflows/publish-github-pages.yml`

```yaml
name: Publish github pages

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]


jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write #デプロイのための書き込み許可を与える
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
      - run: npm ci
      - run: npm run buildPages #ドキュメントビルドコマンド
      - name: Deploy Github pages
        uses: peaceiris/actions-gh-pages@v4
        if: ${{ github.ref == 'refs/heads/main' }} #デフォルトブランチのheadを参照しているときだけデプロイする
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./public
```

この対処方法が最適か、他に良い方法があればぜひコメントをお寄せください。