【Github Actions】リリースノートとタグを自動生成する
はじめに
Rehab for JAPAN フロントエンドエンジニアの @tara_is_ok です!🧢
私が所属しているチームではリリースを行う際、リリースノートを毎回作成しています。
以前はリポジトリのメインページから Releases をクリック → タグを生成 → リリースノートを記入・・・など手動で行っている、かつリポジトリが複数あるためかなり手間がかかっていました。(作成フロー)
今回はそれを解消するため、リリースノートとタグを自動生成する方法を紹介していきます 🫶
実現したいこと
- 😙 main ブランチにマージ後に自動でタグとリリースノートが生成されること
- 😙 リリースに含まれる PR の一覧が表示されること
- 😙 ラベルに応じてカテゴライズ出来ること
- 😙 リリースノートに Summary 的な内容が追加出来ること
- 🥶 release-drafter などでリリースノートの Draft を生成する
- 基本的に人の手を使わない方針
- 🥶 手動でリリースノートを作成、タグを切る
成果物
生成サンプル
1.タグとリリースノートの自動生成
リリースノートとタグを自動生成する(.github/workflows/release.yml
)
処理の流れは次の通りです
- main へ PR がマージされる
- マージされずに close された時はスキップ
- 前回のリリースタグを取得する
- タグを生成する「{YYYY.MM.DD}-{count}」
- 同日の場合 → count + 1
- main への PR の body を取得する
- body がない場合はスキップ
- 前回リリースからの差分をもとに、変更点を取得する
- 1 でラベルがつけられた PR をカテゴライズする(
.github/release.yml
)
- 1 でラベルがつけられた PR をカテゴライズする(
- リリースノートの本文を生成する
- 4, 5 で取得したテキストをマージする
- リリースノートを作成する
name: Create release tag and release note.
on:
pull_request:
types: [closed]
branches:
- main
jobs:
create-release-tag:
# PRがマージされたときのみ実行
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TZ: "Asia/Tokyo"
steps:
- uses: actions/checkout@v3
# 前回のリリースタグを取得する
- name: Get previous tag
id: pre_tag
run: |
echo "pre_tag=$(curl -H 'Accept: application/vnd.github.v3+json' -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r .tag_name)" >> $GITHUB_OUTPUT
# タグを生成する 「{YYYY.MM.DD}-{当日リリース回数}」
- name: Generate tag
id: release_tag
run: |
today=$(date +'%Y.%m.%d')
pre_release_date=$(echo ${{ steps.pre_tag.outputs.pre_tag }} | awk -F'-' '{print $1}')
pre_release_count=$(echo ${{ steps.pre_tag.outputs.pre_tag }} | awk -F'-' '{print $2}')
if [[ ! $pre_release_date = $today ]]; then
pre_release_count=0
fi
echo "release_tag=$today-$(($pre_release_count + 1))" >> $GITHUB_OUTPUT
# PRのDescriptionを取得しマークダウン形式に変換する
- name: Get pr description
id: pr_description
run: |
echo "pr_description=$(curl -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number}}' \
| jq .body | awk '{if ($0 == "null") print ""; else print}')" >> $GITHUB_OUTPUT
# 前回リリースからの差分をもとに、変更点を取得する
- name: Generate release note changes
id: changes
run: |
echo "changes=$(
curl -X POST \
-H 'Accept: application/vnd.github.v3+json' \
-H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' \
https://api.github.com/repos/${{ github.repository }}/releases/generate-notes \
-d '{
"tag_name": "${{ steps.release_tag.outputs.release_tag }}",
"previous_tag_name": "${{ steps.pre_tag.outputs.pre_tag }}",
"target_commitish": "main"
}' | jq .body
)" >> $GITHUB_OUTPUT
# リリースノートの本文を作成する
- name: Create release note body
id: release_note_body
run: |
echo "release_note_body=$(echo \
${{ steps.pr_description.outputs.pr_description }} \
${{ steps.changes.outputs.changes }} \
| sed 's/\\"//g' | sed 's/["“]//g')" >> $GITHUB_OUTPUT
# タグを切り、リリースノートを作成する
# PRのラベルに応じてカテゴライズする場合は、使用先のリポジトリで下記を定義する
# https://docs.github.com/ja/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
- name: Create Release
run: |
response=$(curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-d "{ \
\"tag_name\": \"${{ steps.release_tag.outputs.release_tag }}\", \
\"target_commitish\": \"main\", \
\"name\": \"${{ steps.release_tag.outputs.release_tag }}\", \
\"body\": \"${{ steps.release_note_body.outputs.release_note_body }}\" \
}" \
-w "%{http_code}" \
-o response_body.txt \
https://api.github.com/repos/${{ github.repository }}/releases)
status_code=$(tail -n1 <<< "$response")
echo "Status Code: $status_code"
body=$(cat response_body.txt)
echo "Response Body: $body"
if [ $status_code -ne 201 ]; then
echo "Failed to create release"
exit 1
fi
2.PR のカテゴライズ
「1.タグとリリースノートの自動生成 5」に該当するコードです
リリースノートを生成するときに下記の Title が自動的に割り当てる
# PRのラベルに応じてリリースノートのカテゴリを分ける
changelog:
exclude:
authors:
- github-actions
categories:
- title: New Features
labels:
- "feature"
- title: Bug Fixes
labels:
- "bug"
- title: Hot Fixes
labels:
- "hotfix"
- title: Other Changes
labels:
- "*"
3.PR のラベル自動付与
ブランチ名に基づいて PR にラベルを付与する
-
feature/~
→ feature ラベル -
fix/~
→ bug ラベル -
hotfix/~
→ hotfix ラベル -
*
→ ラベルなし
name: Automatically labeling pull request.
on:
pull_request:
types: [opened]
jobs:
auto-labeling-pr:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@v3
# ラベル名を取得する
- name: Get label name
id: label_name
run: |
branch_type=$(echo ${{ github.head_ref }} | cut -d "/" -f1)
if [ $branch_type == 'feature' ]; then
label_name="feature"
elif [ $branch_type == 'fix' ]; then
label_name="bug"
elif [ $branch_type == 'hotfix' ]; then
label_name="hotfix"
else
label_name=""
fi
echo "label_name=$label_name" >> $GITHUB_OUTPUT
# PRにラベルを付与する
- name: Auto labeling
if: ${{ steps.label_name.outputs.label_name != '' }}
run: |
number=$(echo ${{ github.event.pull_request.number }})
gh pr edit $number --add-label ${{ steps.label_name.outputs.label_name }}
以上が成果物です 👩💻
ここからは要点を絞って実装を見ていきます!
前回のリリースタグを取得する
こちらで取得可能です。
GET /repos/{owner}/{repo}/releases/latest
# 前回のリリースタグを取得する
- name: Get previous tag
id: pre_tag
run: |
echo "pre_tag=$(curl -H 'Accept: application/vnd.github.v3+json' -H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r .tag_name)" >> $GITHUB_OUTPUT
リクエストやレスポンスは下記に書いてあります
PR の Summary を任意でつける
PR の一覧を全てカテゴライズして表示する場合、リリースの規模が大きかったりするとノイズとなる PR が出てきます。
そこで、main への PR の body にリリース内容のまとめを書き、それをリリースノートに含める運用とすることでリリースノートで強調したい機能が分かりやすく表示できるようにしました。
流れは次のようにします。
- PR の番号を取得する
- get-a-pull-requestをリクエストし、description を取得
- 2 で取得した description を整形する(null 判定)
- release note の body に Summary を入れる
既に完成で見せていますが、次のイメージです
main への PR
PR から body を取得して表示
対象の PR 番号を取得する(準備)
${{github.event.pull_request.number}}
で取得が可能です。
作成当初は、main への push をトリガーに workflow を走らせていました。
しかし、push をトリガーでは pr 番号が取得できなかったため、PR の close をトリガーに変更します。
name: Create release tag and release note.
on:
- push:
- branches: [main]
+ pull_request:
+ types: [closed]
+ branches:
+ - main
jobs:
create-release-tag:
# PRがマージされたときのみ実行
+ if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
PR の情報を取得し、body が空の場合に表示をスキップする
GET /repos/{owner}/{repo}/pulls/{pull_number}
を使うことで情報を取得することができます。
リクエスト方法やレスポンスのサンプルはこちらで確認できます。
# PRのDescriptionを取得しマークダウン形式に変換する
- name: Get pr description
id: pr_description
run: |
echo "pr_description=$(curl -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' 'https://api.github.com/repos/${{ github.repository }}/pulls/${{github.event.pull_request.number}}' | jq .body)" >> $GITHUB_OUTPUT
しかし PR の body が null の場合に、リリースノートに文字列の nullが表示されてしまいます。
そこで今回は、文字列の null が返ってきた時に、空文字を返すようにしました。
# PRのDescriptionを取得しマークダウン形式に変換する
- name: Get pr description
id: pr_description
run: |
echo "pr_description=$(curl -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number}}' \
| jq .body | awk '{if ($0 == "null") print ""; else print}')" >> $GITHUB_OUTPUT
| awk '{if ($0 == "null") print ""; else print}'
リリースノートを作成する
流れとしては次のようになります。
- リリースノートの本文を生成
- リリースノートを作成する
- PR で記載した内容をのせる
前回リリースからの差分をもとに、変更点を取得する
こちらで生成が可能です
POST /repos/{owner}/{repo}/releases/generate-notes
# 前回リリースからの差分をもとに、変更点を取得する
- name: Generate release note changes
id: changes
run: |
echo "changes=$(
curl -X POST \
-H 'Accept: application/vnd.github.v3+json' \
-H 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' \
https://api.github.com/repos/${{ github.repository }}/releases/generate-notes \
-d '{
"tag_name": "${{ steps.release_tag.outputs.release_tag }}",
"previous_tag_name": "${{ steps.pre_tag.outputs.pre_tag }}",
"target_commitish": "main"
}' | jq .body
)" >> $GITHUB_OUTPUT
本文をを作成する
取得したリリース PR の description と前回からの差分をマージしていきます。
また、"
や“
は特殊文字として扱われてしまいエラーになるので省きました。
# リリースノートの本文を作成する
- name: Create release note body
id: release_note_body
run: |
echo "release_note_body=$(echo \
${{ steps.pr_description.outputs.pr_description }} \
${{ steps.changes.outputs.changes }} \
| sed 's/\\"//g' | sed 's/["“]//g')" >> $GITHUB_OUTPUT
リリースノートを作成する
生成した本文を使用しリクエストを行います。
release_note_body
で output したもの(${{steps.release_note_body.outputs.release_note_body}}
)をリクエストボディに含めます。
POST /repos/{owner}/{repo}/releases
また、status codeが成功(201)以外の場合はエラーを出力し処理を終了させるようにします。
# タグを切り、リリースノートを作成する
- name: Create Release
run: |
response=$(curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-d "{ \"tag_name\": \"${{ steps.release_tag.outputs.release_tag }}\", \"target_commitish\": \"main\", \"name\": \"${{ steps.release_tag.outputs.release_tag }}\", \"body\": \"${{steps.pr_description.outputs.pr_description}} ${{ steps.release_note.outputs.release_note }}\"}" \
-w "%{http_code}" \
-o response_body.txt \
https://api.github.com/repos/${{ github.repository }}/releases)
status_code=$(tail -n1 <<< "$response")
echo "Status Code: $status_code"
body=$(cat response_body.txt)
echo "Response Body: $body"
if [ $status_code -ne 201 ]; then
echo "Failed to create release"
exit 1
fi
リリースノートをカテゴライズ
リリースノートを自動生成する際に、Pull Request をラベルに応じてカテゴライズすることが可能です。
# PRのラベルに応じてリリースノートのカテゴリを分ける
changelog:
exclude:
authors:
- github-actions
categories:
- title: New Features
labels:
- "feature"
- title: Bug Fixes
labels:
- "bug"
- title: Hot Fixes
labels:
- "hotfix"
- title: Other Changes
labels:
- "*"
また、ここで定義するラベルは事前に定義する必要があります。
PR にラベルを自動付与
テンプレートは用意できたが、前セクションの段階では手動で PR にラベルを付与しなければなりません。
なので、ブランチに応じて PR にラベルを付与できるよう自動化を行います
name: Automatically labeling pull request.
on:
pull_request:
types: [opened]
jobs:
auto-labeling-pr:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout code
uses: actions/checkout@v3
# ラベル名を取得する
- name: Get label name
id: label_name
run: |
branch_type=$(echo ${{ github.head_ref }} | cut -d "/" -f1)
if [ $branch_type == 'feature' ]; then
label_name="feature"
elif [ $branch_type == 'fix' ]; then
label_name="bug"
elif [ $branch_type == 'hotfix' ]; then
label_name="hotfix"
else
label_name=""
fi
echo "label_name=$label_name" >> $GITHUB_OUTPUT
# PRにラベルを付与する
- name: Auto labeling
if: ${{ steps.label_name.outputs.label_name != '' }}
run: |
number=$(echo ${{ github.event.pull_request.number }})
gh pr edit $number --add-label ${{ steps.label_name.outputs.label_name }}
セマンティックバージョン
今回はカレンダーバージョンを採用したのですが、その経緯について触れたいと思います。
イメージ
type | increment | reset |
---|---|---|
major | 1.0.0 | minor+patch |
minor | 0.1.0 | patch |
patch | 0.0.1 | - |
その他 | - | - |
当初は上記のようなセマンティックバージョンでのタグ方針で考えていたが、次の懸念点からカレンダーバージョンとしました
- main への PR のタイトルに v.1.0.1 のように書いて github action に伝搬させる必要がある?
- PR に major,minor,patch などのラベルをつける
- github actions 側で major の場合+1 などを定義
- Conventional Commits などでコミットからバージョンを特定する(+1 か+0.1 のような)
- 私が参画したプロジェクトでは commitzen などでコミットメッセージを整備できていなかったので今回は見送りとしました
おわりに
今回は GitHub Actions でリリースノートとタグを自動生成する方法を紹介しました。
この方法により、リリース作業を効率化し、手動でのミスを減らすことができました!🐥
複数のリポジトリ間でアクションを共有することも可能なので、興味のある方は是非こちらもご覧ください!
最後までありがとうございました!💆♂️
参考
こちらの記事がなければ実装できていなかったです。ありがとうございます!
Discussion