📗

CopilotがGitコミット理解し始めているので、Conventional Commitsを無理なく開発フローに組み込む

2024/06/06に公開

https://github.blog/changelog/2024-05-31-github-copilot-enterprise-copilot-knows-about-repositories-releases-commits-more-may-2024-updates/

Copilot Chat in GitHub.com has leveled up 🔋. It can now summarize and answer questions about your repositories, releases, commits and more.

GitHub公式ブログのパンチラインを引用しましたが、GitHub Copilotがコミットも理解し始めているようです。コード生成に利用するとの言及はありませんが、コミットメッセージは将来確実に重要なデータソースになることは間違いなさそうです。そのため、AIフレンドリーなコミット履歴を維持することが求められます。この記事ではコミットメッセージのスタンダードであるConventional Commitsを無理なく開発フローに組み込む方法について検討していきます。

ツールベースでいうと、release-pleaseを使った開発フローの紹介になります。Conventional Commitsの基礎部分は各所で語られているので、開発フローとの統合部分に焦点を当てていきます。

目次

Conventional Commitsの簡単な基礎知識から順を追って解説していきます。
最終的に、release-pleaseを組み込んだ開発フローに着地するので、結果のみご覧になりたい方は、wrap up - リポジトリをセットアップして開発フローに組み込むまでスキップください。

Conventional Commitsとは

Conventional Commitsの公式ページで全て語られていますが、人間と機械が読みやすく、意味のあるコミットメッセージにするための仕様で、以下のフォーマットでGitコミットをしておくと、CHANGELOG自動生成、Version Bump自動化等エコシステムの恩恵が受けられます。

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

CHANGELOGの自動生成とSemantic Version

公式ページの『何故 Conventional Commits を使うのか』を引用します。

  • 変更履歴 (CHANGELOG) を自動的に生成できます。
  • semantic version 単位で自動的に履歴をまとめられます (コミットの型に基づきます)。
  • チームメイトや一般のユーザー、およびその他の利害関係者へ変更の内容を伝えることができます。
  • ビルドや公開の処理をトリガーできます。
  • より構造化されたコミット履歴を調査できるようにすることで、人々があなたのプロジェクトに貢献しやすくなります。

この章では上2つの項目、CHANGELOG、semantic versionとConventional Commitsの関係性について解説します。

semantic version

semantic versionの詳細は公式サイトをご確認いただければと思いますが、1.1.2といった形式で、ソフトウェアのバージョンを表現するフォーマットです。Conventional Commitsのヘッダーの先頭に<type>を記載しますが、いくつかのtypeはsemantic versionのメジャー、マイナー、パッチのバージョンに対応しています。

type semantic version
<type>!
またはfooterにBREAKING CHANGE:
メジャーバージョン
feat マイナーバージョン
fix パッチバージョン

例えば前回のリリースバージョンが1.0.0で、それ以降のGitコミット履歴が以下のようになっていたとします。

$ jq -r .version < package.json
1.0.0

$ git log
commit df933f197214d6c8789e2434aba325d982ce45a8 (HEAD -> main)
Author: kuritify <kuritify@example.com>
Date:   Mon Jun 3 18:06:12 2024 +0900

    feat: add new feature

commit 919f4797f3883c81768b3dc1978898dbcdc6312c
Author: kuritify <kuritify@example.com>
Date:   Mon Jun 3 18:05:36 2024 +0900

    fix: bug fix

feattypeが含まれているため、マイナーバージョンが繰り上がり、次期Versionは1.1.0に決まります。このソフトウェアのバージョンを適切にインクリメントしていくことを、Version Bump、Bump Versionといいますが、 Conventional Commitsでコミット履歴を使い、機械的にVersion Bumpしてくれるエコシステムが複数存在します。

変更履歴 (CHANGELOG)

文字通りリリース単位での変更の履歴で、vitepressはリポジトリのルートにCHANGELOG.mdファイルとして保存されていますし、nuxtではGitHubのリリースにまとめられています。リリースから、CHANGELOG.mdにリンクするパターンもあります。(Vue.js

これらのCHNAGELOGを眺めてみると、Conventional Commitsの<type>の種別毎、つまりsemantic versionのメジャー、マイナー、パッチ単位で変更内容がまとめられていることがわかります。

変更履歴(CHANGELOG)の明確なスタンダードは無いようですが、Keep a changelogといったスタンダードを作ろうという動きもあり、CHANGELOG.mdという名前でMarkdownをリポジトリに格納するか、GitHubのリリースに記載するかが推奨されています。

エコシステムの選定 - release-please

CHANGELOGの自動生成、Version Bumpに対応したエコシステム(ツール)は何がベストか考えます。

OSSを参考に、coventional-changelogVue.js)、release-itaxios)、changelogennuxt)、などのツールも比較検討してみたのですが、本記事のテーマが”無理なく”なので、オールインワンで利用でき、GitHubとの統合のしやすさ、複数プログラミング言語に転用可能などの観点からrelease-pleaseを採用することにしました。

conventional-changelog本家からも推奨されていますし、Googleが開発元であるという信頼性の高さもあります。

release-please基礎

docs/design.mdにコンセプトや、リリースフローの詳細が記載されていますが、動作検証の結果で補足しつつ要約します。release-pleaseの用語にあわせ、リリースを実行するブランチをRelease branchと呼びます。指定しない限り、デフォルトブランチがRelease branchになります。

  1. Release branchへのcommitのpush(マージ)に基づき、release-pleaseがRelease PRを作成してくれる。
  2. このRelease PRには、前回のリリース以降にpushされたcommitメッセージを辿り、Conventional Commitsのtype種別毎に変更内容がまとめられたCHANGELOGの更新、プログラミング言語固有のVersion Bump(例: package.jsonのversionフィールド)が含まれる。
  3. Release PRのBodyには、CHANGELOGの更新分と同じ内容が記載される。このPRのBody内容が、そのままGitHubリリースのリリースノートになるため、必要に応じてカスタマイズ可能。
  4. このRelease PRはRelease branchへの変更を追従する。つまりRelease branchに追加のcommitがpushされたら、CHANGELOG、リリースノート(PRのBody)が適宜更新される。
  5. Release PRがマージされると、GitのタグがPushされ、そのタグからGitHubリリースが生成される。

What's a Release PR?にも説明がありますが、Release branchへのpush(マージ)をトリガーにリリースが作成されるわけではなく、PR経由でのリリース作成が特異点かと思います。このRelease PRのおかげで、リリースの発行タイミング調整や、リリースノートのカスタマイズなどかゆいところに手が届くリリースフローを実現できます。

release-pleaseの挙動確認

最終的には公式のGithub用のアクションを使いますが、基本動作の確認に焦点を当てるため、CLI経由で挙動を確認していきます。リポジトリはJavascriptのモジュールを想定しています。他にサポートされているプログラミング言語はこちらから確認いただけます。

# GitHubでリポジトリを作成します。 <$owner>/release-please-poc

$ mkdir release-please-poc && cd release-please-poc
$ git init
$ npm init -y
$ npm i -D release-please
$ echo "node_modules" > .gitignore
$ git remote add origin <$owner>/release-please-poc

release-pleaseはGitHubのAPIにリクエストするため、ここではPAT(Personal access token)を準備します。

  1. https://github.com/settings/tokens?type=beta にアクセス
  2. PermissionsのContentsPull requestsにRead and writeのチェックを入れてGenerate tokenをクリック
  3. access tokenの文字列をコピーしておく(以降<$pat>と表記します)

release-pleaseには2つのconfigファイルが必要です(厳密にはなくても可能)。bootstrapサブコマンドが用意されているので、実行するとConfigファイルを作成するPRが生成されます。

$ npx release-please bootstrap \
  --token=<$pat> \
  --repo-url=<$owner>/<$repo> \
  --release-type=node
  --initial-version $(jq -r .version < package.json)
$ gh pr view 1
chore: bootstrap releases for path: . kuritify/release-please-poc#1
Open • kuritify wants to merge 1 commit into main from release-please/bootstrap/default • about 1 minute ago
+16 -0 • No checks


  Configuring release-please for path:

$ gh pr diff 1
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..fea3454
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+  ".": "1.0.0"
+}
\ No newline at end of file
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..161d1ad
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,13 @@
+{
+  "packages": {
+    ".": {
+      "changelog-path": "CHANGELOG.md",
+      "release-type": "node",
+      "bump-minor-pre-major": false,
+      "bump-patch-for-minor-pre-major": false,
+      "draft": false,
+      "prerelease": false
+    }
+  },
+  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
+}
\ No newline at end of file

内容を確認してPRをマージします。これでbootstrap(必要なconfigファイルのセットアップ)が完了します。これ以降は次のフローで適宜リリースを作成できます。

  1. Release branchに対し、Conventional Commitsに従ったコミットメッセージを付与した変更を加えていく
  2. Release PRを作成する。CLI経由で実施
  3. PRの内容を最終確認し、PRをマージ。GitHubのWebコンソールなどから実施する
  4. リリースを作成する。CLI経由で実施
$ echo "console.log('hello world');" > index.js
$ git add index.js
$ git commit -m 'feat: add index.js'

# 説明の簡素化のため、直接mainブランチ(Release branch)にpushしています。
$ git push origin main

release-prサブコマンドでRelease PRを作成します。

$ npx release-please release-pr \
  --token=${PAT} \
  --repo-url=<$owner>/<$repo>
$ gh pr view 2
chore(main): release release-please-poc 1.1.0 kuritify/release-please-poc#2
Open • kuritify wants to merge 1 commit into main from release-please--branches--main--components--release-please-poc
  less than a minute ago
+12 -4 • No checks
Labels: autorelease: pending


  ## 🤖 I have created a release beep boop

  ## 1.1.0 https://github.com/kuritify/release-please-poc/compare/release-please-poc-v1.0.0...release-please-poc-v1.1.0
  (2024-06-04)

  ### Featuresadd index.js (af93cb9 https://github.com/kuritify/release-please-
  poc/commit/af93cb97efc950e6112991a9d2874e6bfb0e068a)

  --------

  This PR was generated with Release Please https://github.com/googleapis/release-please. See documentation
  https://github.com/googleapis/release-please#release-please.

PRの変更内容を確認すると、CHANGELOG.mdの生成、Version Bumpがコミット履歴に従って行われていることがわかります。

$ gh pr diff 2
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index fea3454..2601677 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "1.0.0"
+  ".": "1.1.0"
 }
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..90413ba
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+## [1.1.0](https://github.com/kuritify/release-please-poc/compare/release-please-poc-v1.0.0...release-please-poc-v1.1.0) (2024-06-04)
+
+
+### Features
+
+* add index.js ([af93cb9](https://github.com/kuritify/release-please-poc/commit/af93cb97efc950e6112991a9d2874e6bfb0e068a))
diff --git a/package-lock.json b/package-lock.json
index c13aab7..9cf4736 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "release-please-poc",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "release-please-poc",
-      "version": "1.0.0",
+      "version": "1.1.0",
       "license": "ISC",
       "devDependencies": {
         "release-please": "^16.10.2"
diff --git a/package.json b/package.json
index d070e61..295b877 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "release-please-poc",
-  "version": "1.0.0",
+  "version": "1.1.0",
   "description": "release-please-poc",
   "main": "index.js",
   "scripts": {

上記Release PRをマージします。

github-releaseサブコマンドでリリースを作成します。

$ npx release-please github-release \
  --token=<$pat> \
  --repo-url=<$owner>/<$repo>

BumpされたバージョンのGitのタグが振られ、GitHubリリースが生成されます。

$ git fetch
...

$ git tag
release-please-poc-v1.1.0

$ gh release view release-please-poc-v1.1.0
release-please-poc-v1.1.0
kuritify released this less than a minute ago

  ## 1.1.0 https://github.com/kuritify/release-please-poc/compare/release-please-poc-v1.0.0...release-please-poc-v1.1.0 (2024-
  06-04)

  ### Featuresadd index.js (af93cb9 https://github.com/kuritify/release-please-
  poc/commit/af93cb97efc950e6112991a9d2874e6bfb0e068a)

デフォルトだと、Gitタグの命名規則が<$repo>-v<$semVer>なので、Configファイルを以下のように変更するとv<$semVer>に変更できます。

https://github.com/googleapis/release-please/issues/612#issuecomment-983309002

$ git diff release-please-config.json
diff --git a/release-please-config.json b/release-please-config.json
index 161d1ad..79e25a4 100644
--- a/release-please-config.json
+++ b/release-please-config.json
@@ -6,8 +6,9 @@
       "bump-minor-pre-major": false,
       "bump-patch-for-minor-pre-major": false,
       "draft": false,
-      "prerelease": false
+      "prerelease": false,
+      "include-component-in-tag": false
     }
   },
   "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
}

以上がrelease-pleaseの挙動、基本リリースのフローになります。

Mergeコミット vs Squash and Merge

基本要素は理解できたので、ここからは実際の開発フローに組み組むにあたって検討が必要な事項について考えていきます。まずはマージ戦略について検討します。

プルリクエストのマージについてで説明がありますが、Mergeパターンの王道は以下2パターンがあります。

Merge commit(図左)でいうと、featureブランチのコミットがベースブランチに追記され、Squash and merge(図右)でいうと、featureブランチでのコミットは1つのコミットにまとめてベースブランチに追加されます。

Conventional Commits、release-pleaseとも両方のマージ戦略をサポートしていますが、推奨のマージ戦略についてそれぞれ以下のように言及されています。

We highly recommend that you use squash-merges when merging pull requests. A linear git history makes it much easier to:

-- release-please - Linear git commit history (use squash-merge)

いいえ! Git で squash ベースのワークフローを使用する場合は、主要メンテナがマージ時にコミットメッセージをクリーンアップすることができるため、臨時のコミッタには作業負荷がかかりません。 また、これをするための一般的な方法としては、プルリクエストからのコミットを git システムが自動的に squash し、主要メンテナによるマージ時に適切な git コミットメッセージを入力するためのフォームを表示するというものです。

-- Conventional Commits - 貢献者全員が Conventional Commits の仕様を使用する必要がありますか?

release-pleaseでは明確にSquash and mergeを推奨していますし、私個人も同意見です。Merge commitを採用すると、全てのコミットをConventional Commitsに従うことが前提になります。つまり全てのコントリビューターにConventional Commitsを理解してもらう必要があることになります。これまで複数のパートナー企業と開発をしてきましたが、残念ながらGitリテラシーが低いエンジニアの方もいらっしゃいますし、リテラシーがあったとしてもいちいちコミットメッセージを適切に保つのが鬱陶しいこともあります(featureブランチには雑にコミットしたい)。

つまり、『Relase Branchにコミットを追加(マージ)する時は、必ずメインのコントリビューターがSquash and mergeで、Conventional Commitsに従ったコミットメッセージを追記する』 というルールを設けます。
このルールを厳守するため、以下2つのリポジトリ設定を行います。

  1. ブランチ保護ルールRelease branchにマージできる人間を適切なコントリビューターに制限する
  2. Pull RequestのマージをSquash and mergeに限定する

Squashする際に、複数行の変更を記録したい場合、What if my PR contains multiple fixes or features?で説明されている通り、複数のtypeを1つのコミットメッセージに含めても、適切にCHANGELOG、リリースノートが生成されます。

feat: adds v4 UUID to crypto

This adds support for v4 UUIDs to the library.

fix(utils): unicode no longer throws exception

  PiperOrigin-RevId: 345559154
  BREAKING-CHANGE: encode method no longer throws.
  Source-Link: googleapis/googleapis@5e0dcb2

feat(utils): update encode to support unicode
  PiperOrigin-RevId: 345559182
  Source-Link: googleapis/googleapis@e5eef86

上記コミットメッセージを追加した時のrelease-pleaseが作成するRelease PRの例です。

$ gh pr diff 3
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 2601677..65f558e 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "1.1.0"
+  ".": "2.0.0"
 }
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90413ba..e15d4af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
 # Changelog

+## [2.0.0](https://github.com/kuritify/release-please-poc/compare/release-please-poc-v1.1.0...release-please-poc-v2.0.0) (2024-06-04)
+
+
+### ⚠ BREAKING CHANGES
+
+* **utils:** encode method no longer throws.
+
+### Features
+
+* adds v4 UUID to crypto ([fbd3586](https://github.com/kuritify/release-please-poc/commit/fbd35864e2c5ff0f1a496fe52c855f61d9f03cbc))
+* **utils:** update encode to support unicode ([fbd3586](https://github.com/kuritify/release-please-poc/commit/fbd35864e2c5ff0f1a496fe52c855f61d9f03cbc))
+
+
+### Bug Fixes
+
+* **utils:** unicode no longer throws exception ([fbd3586](https://github.com/kuritify/release-please-poc/commit/fbd35864e2c5ff0f1a496fe52c855f61d9f03cbc))
+
 ## [1.1.0](https://github.com/kuritify/release-please-poc/compare/release-please-poc-v1.0.0...release-please-poc-v1.1.0) (2024-06-04)


diff --git a/package-lock.json b/package-lock.json
index 9cf4736..4341250 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "release-please-poc",
-  "version": "1.1.0",
+  "version": "2.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "release-please-poc",
-      "version": "1.1.0",
+      "version": "2.0.0",
       "license": "ISC",
       "devDependencies": {
         "release-please": "^16.10.2"
diff --git a/package.json b/package.json
index 295b877..8fbc870 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "release-please-poc",
-  "version": "1.1.0",
+  "version": "2.0.0",
   "description": "release-please-poc",
   "main": "index.js",
   "scripts": {

fix、feat以外のtypeとscope、descriptionの利用方針

Conventional Commitsのフォーマットを再掲します。

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

基本フォーマットはあるものの、自由度が高い部分について利用方針を勘案していきます。

type

まずは<type>ですが、Conventional Commits - 概要には以下のような説明があります。

fix: や feat: 以外の型も許されています。たとえば @commitlint/config-conventional (これは Angular の規約が基になっています) は build:, chore:, ci:,docs:, style:, refactor:, perf:, test:, などを推奨しています。

Conventional Commitsエコシステム、OSSを眺めてみると、Anguralの規約をベースに独自拡張していることが大半です。(例: Vue.js

そのため、特にこだわりがなければ、Angular規約 + release-pleaseのRelease PRで使われるchore + リバートコミットのためのrevertに限定する。で大半のユースケースはカバーできると考えます。

<type> 参照元 説明
build Angular規約 Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
ci Angular規約 Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
docs Angular規約 Documentation only changes
feat Angular規約 A new feature
fix Angular規約 A bug fix
perf Angular規約 A code change that improves performance
refactor Angular規約 A code change that neither fixes a bug nor adds a feature
style Angular規約 Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
test Angular規約 Adding missing tests or correcting existing tests
chore release-please no production code change
revert Vue.js revert commit

Angular規約ではないchorerevertについて補足します。

choreを辞書で調べると、雑用、面倒な仕事という意味で、Conventional Commitsの文脈で言うと依存関係のアップデートやビルドスクリプトの変更など本質的なコードの変更ではない作業に使われるようです。Vue.jsを例にすると、chore(deps): update dependency execa to v9といった具合に使われています。

choreの出所はわからなかったのですが、stack overflowにQAがありましたのでリンクしておきます。

続いてrevertについてです。こちらはVue.jsのcommit-conention.mdから引っ張ってきています。以下のように打ち消したコミットをBodyに追加することを推奨しています。

revert: feat(compiler): add 'comments' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

scope

完全な主観になりますが、大半のエンタープライズシステムであれば<scope>は使わなくても問題ないと思います。前述のVue.jsのコミット例(chore(deps): update dependency execa to v9)でいえば、depsというscopeがあることで依存関係の更新であることが明確ですが、chore: update dependency, execa to v9でも十分伝わると考えます。<scope>の設計やリスト管理にかかるコストも無視できません。"無理なく"Conventional Commitsを導入するためにも、<scope>は使わない方針とします。

ちなみに、type同様Angularの規約が存在しますので必要に応じてご参照ください。

Scope

The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.

The following is the list of supported scopes:

  • animations
  • common
  • compiler
  • compiler-cli
  • core
  • elements
  • forms
  • http
  • language-service
  • platform-browser
  • platform-browser-dynamic
  • platform-server
  • platform-webworker
  • platform-webworker-dynamic
  • router
  • service-worker
  • upgrade

description

descriptionについてはどうでしょうか。変更内容を簡潔に伝えるのが大前提ですが、最終的にCHANGELOGに転記される。という観点から考えてみます。

axiosのCHANGELOGをみてみると、変更内容に加え、関連するPRへのリンク、コミットIDが含まれています。

実際のGitログでみると、<description> (#<$pull-request-id>)となっています。

commit 4f79aef81b7c4644328365bfc33acf0a9ef595bc
Author: Dmitriy Mozgovoy <**********@gmail.com>
Date:   Tue May 21 17:20:15 2024 +0300

    fix(fetch): enhance fetch API detection; (#6413)

GitHubのWebコンソールでは、#<$pull-request-id>という文字列は自動的にPRへのリンクに変換してくれるため、descriptionの末尾に(#<$pull-request-id>)を追記する。といったルールが良いでしょう。コミットIDについてはrelease-pleaseが末尾に付与してくれます。

wrap up - リポジトリをセットアップして開発フローに組み込む

ここまでの内容をwarp upし、リポジトリのセットアップ方法とセットアップ後の開発フローを説明します。

1. リポジトリの初期セットアップ

GitHubでリポジトリを作成し、ブランチ保護ルールmainブランチに追加します。

ブランチ保護ルールに加え、Settings > Generalに遷移し、Pull RequestsのメニューでPRのマージをSquash and mergeのみ許可します。

2. リポジトリの初期設定

  1. コードベースの初期設定をします。
$ mkdir release-please-poc && cd release-please-poc
$ git init
$ npm init -y
$ echo "node_modules" > .gitignore
$ git remote add origin <$owner>/release-please-poc
  1. semantic versionを開発開始バージョンに設定します。

semantic version公式のFAQに記載がありますが、0.1.0から開発を開始することが慣習です。

0.y.zのような初期の開発フェーズにおけるバージョンの取り扱いはどのようにすべきでしょうか?
 一番簡単な方法は0.1.0からで開発版をリリースし、その後のリリースのたびにマイナーバージョンを上げていけばよいでしょう。

$ sed -i -e 's/"version":.*/"version": "0.1.0",/' package.json
  1. release-pleaseのConfigファイルを作成します。
$ cat <<EOF > release-please-config.json
{
  "packages": {
    ".": {
      "changelog-path": "CHANGELOG.md",
      "release-type": "node",
      "bump-minor-pre-major": false,
      "bump-patch-for-minor-pre-major": false,
      "draft": false,
      "prerelease": false,
      "include-component-in-tag": false
    }
  },
  "\$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
}
EOF

$ cat <<EOF > .release-please-manifest.json
{
  ".": "$(jq -r .version < package.json)"
}
EOF

3. release-please GitHub Actionの設定

  1. 公式のGitHub Actionを組み込みます。.github/workflows/release.ymlを以下の内容で配置します。
on:
  push:
    branches:
      - main

permissions:
  contents: write
  pull-requests: write

name: release-please

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        id: release

release-pleaseでは、GitHub ActionsからPRをAPI経由で生成するため、Yamlファイルのpermissions.pull-requetsの設定に加え、Settings > Actions > Generalに遷移し、Workflow permissionsAllow GitHub Actions to create and approve pull requestsのチェックをONにする必要があります。

ここまでの変更をmainブランチ(Release branch)にpushすることで、Conventional Commitsと統合した開発フローをスタートすることができます。

4. 開発フロー

以降の開発フローを説明します。

  1. feature/*ブランチ(任意の作業ブランチ)に変更を追加し、mainブランチへのPRを作成する
  2. メインのコントリビューターがPRをマージする。(このタイミングでRelease PRが自動で生成される)
  3. 1, 2を繰り返し、リリースタイミングが来たらRelease PRをマージする。(Release PRはmainブランチへの変更を追従します)
  4. GHANGELOG.md、Version Bumpがmainブランチにコミットされる。更にGitのタグが振られ、GitHubリリースが生成される

5. 動作確認

最後に各種設定の動作確認をします。

ブランチ保護の確認

図左がメインのコントリビューター、図右が通常コントリビューターになります。メインコントリビューターのみRelease branchへのマージの権限(Conventional Commitsを追加できる権限)があります。

マージを実行しようとすると、Squash and mergeのみ許可されています。これにより誤ってMerge commitでマージしてしまい、Conventional Commitsに違反する可能性を低減できます。

release-pleaseの確認

前回のリリースから以下のコミットがRelease branchに蓄積されているとします。(複数typeが1つのコミットにまとまっています)

commit 4899348ff565e26baaf0c516abe510dfa2589736 (HEAD -> main, origin/main, origin/HEAD)
Author: kuritify <***************@gmail.com>
Date:   Wed Jun 5 16:09:08 2024 +0900

    feat: add index js (#5)

    fix: typo(#5)

このCommit履歴の状態でのRelease PRです。

CHNAGELOGの自動生成、Version Bumpは以下の様になります。

$ gh pr diff 6
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 466df71..2be9c43 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.1.0"
+  ".": "0.2.0"
 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..6995a29
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Changelog
+
+## [0.2.0](https://github.com/kurihara-test/xx-poc/compare/v0.1.0...v0.2.0) (2024-06-05)
+
+
+### Features
+
+* add index js ([#5](https://github.com/kurihara-test/xx-poc/issues/5)) ([4899348](https://github.com/kurihara-test/xx-poc/commit/4899348ff565e26baaf0c516abe510dfa2589736))
+
+
+### Bug Fixes
+
+* typo([#5](https://github.com/kurihara-test/xx-poc/issues/5)) ([4899348](https://github.com/kurihara-test/xx-poc/commit/4899348ff565e26baaf0c516abe510dfa2589736))
diff --git a/package-lock.json b/package-lock.json
index 1111942..d0ee1d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "release-please-poc",
-  "version": "1.1.0",
+  "version": "0.2.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "release-please-poc",
-      "version": "1.1.0",
+      "version": "0.2.0",
       "license": "ISC",
       "dependencies": {
         "release-please": "^16.10.2"
diff --git a/package.json b/package.json
index dca81ad..f6f009a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "release-please-poc",
-  "version": "0.1.0",
+  "version": "0.2.0",
   "description": "aaa",
   "main": "index.js",
   "scripts": {

このRelease PRをマージすると、以下のGitHubリリースが生成されます。

自動生成されたCHNAGELOGはこちらです。typeの種別毎に変更がまとめられ、各変更のコミットID、PRのIDがパースされリンクになっていることがわかります。

6. 留意事項

release-pleaseのGitHub Actionですが、本記事の例と同じく自動生成されるアクセストークンを使う場合、別のワークフローYAMLでreleasepull_requestpush.tagsイベントをトリガーにしている処理は実行されません。

By default, Release Please uses the built-in GITHUB_TOKEN secret. However, all resources created by release-please (release tag or release pull request) will not trigger future GitHub actions workflows, and workflows normally triggered by release.created events will also not run. From GitHub's docs:

When you use the repository's GITHUB_TOKEN to perform tasks, events triggered by the GITHUB_TOKEN will not create a new workflow run. This prevents you from accidentally creating recursive workflow runs.

-- https://github.com/google-github-actions/release-please-action?tab=readme-ov-file#github-credentials

ワークアラウンドは、自動で生成されるアクセストークンをGitHub Appのインストール アクセス トークンに置き換えることです。

GitHub Appのインストール アクセス トークンの使い方は別の記事で紹介していますのでご参照ください。
https://zenn.dev/kuritify/articles/gitsubmodule-and-action#github-actionsとの統合

別の方法として、release-pleaseでリリースが完了したタイミングで、例えばnpmのpublish処理をしたいとします。
そのケースでは、公式ドキュメントに記載の通りですが、outputs.release_createdを条件に使い、リリースが実行されたタイミング(Release PRがマージされたタイミング)で必要なアクションを実行できます。

on:
  push:
    branches:
      - main
name: release-please
jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/release-please-action@v4
        id: release
        with:
          release-type: node
      # The logic below handles the npm publication:
      - uses: actions/checkout@v4
        # these if statements ensure that a publication only occurs when
        # a new release is created:
        if: ${{ steps.release.outputs.release_created }}
      - uses: actions/setup-node@v4
        with:
          node-version: 12
          registry-url: "https://registry.npmjs.org"
        if: ${{ steps.release.outputs.release_created }}
      - run: npm ci
        if: ${{ steps.release.outputs.release_created }}
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
        if: ${{ steps.release.outputs.release_created }}

まとめ

以上『Conventional Commitsを無理なく開発フローに組み込む』と題して、release-pleaseを中心においた開発フローをご紹介しました。Conventional Commitsやらなきゃなと思いつつ、そっと蓋をしていたアナタの第一歩目になれたら幸いです。

Discussion