Release Please Action v4 でリリースを自動管理する

開発

CHANGELOG 生成やバージョン管理など、release-please-action v4 でリリースを自動化する手順を紹介します。

目次

Release Please とは

Release Please はコミットの履歴を解析して

  • CHANGELOG の生成
  • バージョン書き換え
  • リリースノート

を自動化してくれる CLI ツールです。npm i -g release-please でインストールして npx release-please <コマンド> で使うことができます。

例:

リリースノートの例
リリースノートの例
CHANGELOG の例
CHANGELOG の例

Release Please Action とは

Release Please Action はコミット毎に Release Please を自動実行するための GitHub Action です。

例:

プルリクエストの例
プルリクエストの例

Release Please はそれが直接使われることはあまりなく、CI/CD パイプラインの一部として活用されることが多いです。Release Please Action は次のような挙動をします:

  1. コミットとプルリクエストの監視 コードがコミットされる度に、Release Please がその内容を確認します。コミットメッセージを元に、次のバージョンがどのように更新されるべきか(patch, minor, major)を判断します。
  2. リリースブランチの作成 重要なコミットがあった時、Release Please は自動的にリリースブランチを作成し、その中でバージョン番号を更新します。
  3. リリースノートの生成 コミットメッセージの内容に基づいて、自動でリリースノートを生成します。これにより、手動でリリース内容をまとめる手間が省けます。
  4. GitHub リリースの作成 リリースブランチがマージされると、GitHub 上で新しいリリースタグを自動的に作成します。このタグにはリリースノートが含まれ、バージョンも更新されています。
  5. パッケージの公開(任意, 要設定) 例えば npm などのパッケージマネージャーと連携している場合、新しいバージョンが自動的にパッケージリポジトリーに公開されます。

今回やること

  • JavaScript 製の単一のパッケージ (モノレポではない) のリリースを管理する。
  • v0 から始めて、アルファ版のリリースを行ったうえで、安定版である v1 リリースまでの一連の流れを確認する。

リポジトリを用意する

GitHub リポジトリを作成します。今回は「rp-test」という名前で進めて行きます。リポジトリを作成したら、Settings > Actions > General > Workflow permissions から「Allow GitHub Actions to create and approve pull requests」にチェックを入れて Save します。

GitHub Actions の権限設定
GitHub Actions の権限設定

今回は簡単のために設定から権限を変更していますが、別途アクセストークンを作成して、それを使用しても大丈夫です。Release Please は「contents: write」と「pull-requests: write」の権限を必要とします。

最初のコミット

必要なファイルを準備します:

rp-test/
├── .config/
│   ├── rp-manifest.json
│   └── rp.json
├── .github/
│   └── workflows/
│       └── release-please.yml
└── package.json
.config/rp-manifest.json
{
  ".": "0.0.1"
}
.config/rp.json
{
  "packages": {
    ".": {
      "release-type": "node",
      "changelog-sections": [
        { "type": "change", "section": "Changes" },
        { "type": "feat", "section": "Features" },
        { "type": "fix", "section": "Bug Fixes" },
        { "type": "revert", "section": "Reverts" },
        { "type": "docs", "section": "Documentation", "hidden": true },
        { "type": "perf", "section": "Performance Improvements", "hidden": true },
        { "type": "chore", "section": "Miscellaneous Chores", "hidden": true },
        { "type": "test", "section": "Tests", "hidden": true },
        { "type": "style", "section": "Styles", "hidden": true },
        { "type": "refactor", "section": "Code Refactoring", "hidden": true },
        { "type": "ci", "section": "Continuous Integration", "hidden": true }
      ],
      "include-component-in-tag": false
    }
  },
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
}
.github/workflows/release-please.yml
name: Release Please

on:
  push:
    branches:
      - main

jobs:
  release-please:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          config-file: .config/rp.json
          manifest-file: .config/rp-manifest.json
          target-branch: ${{ github.ref_name }}
        id: rp

      - name: outputs
        run: |
          echo "${{ steps.rp.outputs.releases_created }}"
          echo "${{ steps.rp.outputs.paths_released }}"
          echo "${{ steps.rp.outputs.prs_created }}"
pakcage.json
{
  "name": "rp-test",
  "version": "0.0.1"
}

.config/rp-manifest.json は現在のパッケージ (".") のバージョンが v0.0.1 であることを記録しています。Release Please はこの値に基づいて次のバージョンを決定します。

.config/rp.json で各パッケージ (今回は単一なので "." のみ) に対する Release Please の設定を行います。今回は Node.js で JavaScript パッケージの開発を行うので、release-type は node としています。changelog-sections ではリリースノートに書くコミット一覧のグループの設定を行います。type がコミットメッセージの接頭辞で、section がリリースノートのセクションタイトルになります。例えばコミットメッセージに「fix: A を B に修正」とあれば次のバージョンは「v0.0.2」になります。「feat: C を追加」とあれば次のバージョンは「v0.1.0」になります。また「change!: C を D に改名」のように ! が付いていれば破壊的変更となり、もし現在のバージョンが「v1.2.3」なら、次のバージョンは「v2.0.0」になります。hidden が true だとリリースノートには記載されなくなります。include-component-in-tag を false にすることでパッケージ名がタグやリリースノートに記載されることを防ぎます。これがないとタグ名に「v0.1.0」と期待するところを「rp-test-v0.1.0」のようにダグ付けされてしまいます。モノレポであればパッケージ名をプレフィクスにするのが良いと思いますが、今回は単一パッケージなので、false にします。

.github/workflows/release-please.yml では Release Please Action に設定ファイルへのパスを必ず指定します。target-branch は任意です。今回はデバッグ用に outputs ステップでこの Action が出力するパラメーターを見ていますが、実際にはこれらのパラメーターを元に npm などのパッケージレジストリーに公開するかどうかを決定します (paths_released は JSON 文字列なので表示の際は要注意)。全パラメーターの詳細は Release Please Action の Outputs で見れます。なお今回は単一パッケージなので、ルートコンポーネントの出力 も使うことができます。

最初のコミットの Release Please Action のログです:

最初のコミットで実行される GitHub Actions
最初のコミットで実行される GitHub Actions

最新リリースが見つからないと警告されますが、最初なので無視して大丈夫です。.releases_created と .prs_created は共に false です。

コミットを追加してみる

README を追加する

ファイルを用意します:

rp-test/
├── .config/
│   ├── rp-manifest.json
│   └── rp.json
├── .github/
│   └── workflows/
│       └── release-please.yml
├── package.json
+└── README.md
README.md
# rp-test

コミット & プッシュ:

git add README.md
git commit -m "chore: README を追加"
git push

このコミットでは Release Please Action はコミット履歴の確認だけして何もしません。というのも、chore はバージョン番号の更新に値するコミットメッセージとはみなされないからです。したがって ${{ steps.rp.outputs.prs_created }} は false で、プルリクエストは作成されません。

add 関数を実装する

ファイルを用意します:

rp-test/
├── .config/
│   ├── rp-manifest.json
│   └── rp.json
├── .github/
│   └── workflows/
│       └── release-please.yml
+├── src/
+│   └── index.js
├── package.json
└── README.md
src/index.js
exports.add = function add(a, b) { return a + b; }

コミット & プッシュ:

git add src/index.js
git commit -m "feat: add 関数を実装"
git push

feat は新機能を表すため、Release Please がマイナーバージョンを上げる必要があると判定します。Release Please Action の ${{ steps.rp.outputs.prs_created }} は true となり、github-actions(bot) から次のようなプルリクエストが来ます:

v0.1.0 に更新するプルリクエスト
v0.1.0 に更新するプルリクエスト

このプルリクエストによる変更点には .config/rp-manifest.json と package.json のバージョン番号の更新と、CHANGELOG.md の更新 (追加) が含まれます。これをマージすると Release Please Action の ${{ steps.rp.outputs.releases_created }} は true となり、v0.1.0 のタグと、そのリリースノートが作成されます:

v0.1.0 のリリースノート
v0.1.0 のリリースノート
追加された CHANGELOG.md
追加された CHANGELOG.md

今回はコミットメッセージのタイプに feat が含まれていたのでマイナーバージョンが上がって v0.0.1 から v0.1.0 になりましたが、もし feat が無く fix があったら v0.0.2 になっていました。

プレリリース

開発を進めていき、v0.x.x から安定版の v1.0.0 をリリースする前に、v1.0.0-alpha.1 のようなプレリリースをしたい場合、Release Please の設定ファイルを変更する必要があります:

.config/rp.json
{
  "packages": {
    ".": {
      "versioning": "prerelease",
      "prerelease": true,
      "prerelease-type": "alpha.1",

      // 省略
    }
  }
}

"versioning" は Release Please の Versioning Strategies で、これを "prerelease" にします。v1.0.0-alpha.1 がリリースされた後、バージョン番号は v1.0.0-alpha.2, v1.0.0-alpha.3, … と更新されていきます。

"prerelease" を true にすることで GitHub のリリースノートをプレリリースとして作成します。

"prerelease-type" はプレリリースのバージョンの部分が存在しない場合、この値を設定します。.config/rp-manifest.json に、例えばすでに 1.0.0-alpha.2 のようなプレリリースのバージョンの部分が設定されていれば、次のバージョンは 1.0.0-alpha.3 になりますが、例えば 0.9.1 からアルファ版のリリースを有効にすると、プレリリースのバージョンの部分が設定されていないので、次のバージョンはこの "prerelease-type" が付いた 1.0.0-alpha.1 になります。

プレリリースの設定を反映させたところで、ファイルを更新します:

src/index.js
-exports.add = function add(a, b) { return a + b; }
+exports.add = function add(a, b) { return String(a + b); }

コミット & プッシュ:

git add src/index.js
git commit -m "change!: add 関数は文字列を返す"
git pu

change の後に ! が付いているので、Release Please から破壊的変更とみなされます。

v1.0.0-alpha.1 に更新するプルリクエスト
v1.0.0-alpha.1 に更新するプルリクエスト

このプルリクエストをマージすると、リリースノートはプレリリースとして作成されます:

v1.0.0-alpha.1 のリリースノート
v1.0.0-alpha.1 のリリースノート

安定版のリリース

例えば main ブランチの最新リリースが v0.9.1 で、プレリリースの最新版 v1.0.0-alpha.3 を v1.0.0 にして正式にリリースする場合、Release Please の設定ファイルとマニフェストファイルの 2 箇所を変更する必要があります:

.config/rp.json
{
  "packages": {
    ".": {
      "// versioning": "prerelease",
      "// prerelease": true,
      "// prerelease-type": "alpha.1",

      // 省略
    }
  }
}
.config/rp-manifest.json
{
-  ".": "1.0.0-alpha.3"
+  ".": "0.9.1"
}

.config/rp.json のプレリリースに関する設定は項目の先頭に // を付けて無効化しています。完全に消してしまっても良いともいますが、いずれ v2.0.0-alpha.1 が来ることを考えれば残しても良いと思います。

.config/rp-manifest.json のバージョン番号は main ブランチの最新リリースのバージョン番号と一致させています。少なくとも "1.0.0-alpha.3" のままでは、たとえプレリリースに関する設定項目を消したとしても、破壊的な変更が含まれていれば次のバージョン番号が v1.0.0 を飛ばして "2.0.0-alpha.3" になってしまう (単にメジャーバージョンが上げられただけ) ので、プレリリースの部分は消す必要があります。プレリリースが付いていない最新のリリースに手動で一旦戻してから Release Please にコミット履歴を解析させることで、上記の問題を回避します。また v1.0.0 のリリースノートには v0.9.1 からの変更をすべて記載されることになるため、プレリリースの変更差分まで熱心に追っていない人でもメジャーアップデートによる変更点を一覧できるという利点もあります。

v1.0.0 に更新するプルリクエスト
v1.0.0 に更新するプルリクエスト

あとはいつも通りプルリクエストをマージするだけです。

パッケージレジストリーに公開する

通常バージョン更新のプルリクエストをマージしてリリースノートを作成したら、パッケージをレジストリーに公開する必要があります。リリースノートを作成したかどうかを GitHub Actions 上で判定するには、これまでも所々出てきた ${{ steps.rp.outputs.releases_created }} を使います。

.github/workflows/release-please.yml
name: Release Please

on:
  push:
    branches:
      - main

jobs:
  release-please:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          config-file: .config/rp.json
          manifest-file: .config/rp-manifest.json
          target-branch: ${{ github.ref_name }}
        id: rp

      - uses: actions/checkout@v4
        if: ${{ steps.rp.outputs.releases_created }}

      - uses: actions/setup-node@v4
        with:
          node-version: 20.x
          registry-url: "https://registry.npmjs.org"
        if: ${{ steps.rp.outputs.releases_created }}

      - name: 依存関係をインストール
        run: npm ci
        if: ${{ steps.rp.outputs.releases_created }}

      - name: ビルド
        run: npm run build
        if: ${{ steps.rp.outputs.releases_created }}

      - name: 公開
        run: |
          ver="$(jq -r .version <package.json)"
          suf="${ver#*-}"

          if [[ "$suf" == "$ver" ]]; then
            npm publish --access=public
          else
            tag="${suf%%.*}"
            npm publish --access=public --tag="$tag"
          fi
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
        if: ${{ steps.rp.outputs.releases_created }}

パッケージのビルドと公開のプロセスを ${{ steps.rp.outputs.releases_created }} (または今回の場合は ${{ steps.rp.outputs.release_created }}) が true にセットされたときに実行するようにします。「公開」ステップでは 1.0.0 のようにプレリリースのバージョン部分が無ければデフォルトの latest タグで公開し、あればその名前 (例えば alpha.1 なら alpha の部分) をタグ名にして公開しています。

コミットメッセージのフォーマット

release-please/src/commit.ts に書いてあります。例を列挙すれば、

  • 非破壊的な変更
    • feat: メッセージ
    • feat(scope/name): メッセージ
  • 破壊的な変更
    • feat!: メッセージ
    • feat(scope/name)!: メッセージ

といったところでしょうか。スコープ付きの場合、! はスコープの後に指定するようです。