【Github Actions】リリースノートとタグを自動生成する 【2024】
実現したいこと
- 😙 mainブランチにマージ後に自動でタグとリリースノートが生成されること
- 😙 リリースに含まれるPRの一覧が表示されること
- 😙 ラベルに応じてカテゴライズ出来ること
- 😙 リリースノートにSummary的な内容が追加出来ること
- 🫨 release-drafterなどでリリースノートのDraftを生成する
- 基本的に人の手を使わない方針
- 🫨 手動でタグを切る
成果物
生成サンプル
1.タグとリリースノートの自動生成
リリースノートとタグを自動生成する(workflow/release.yml
)
- mainへPRがマージされる
- マージされずにcloseされた時はスキップ
- 前回のリリースタグを取得する
- タグを生成する「{YYYY.MM.DD}-{count}」
- 同日の場合 → count + 1
- mainへのPRのbodyを取得する
- bodyがない場合は空文字を返す
- リリースノートの本文を生成する
- 1でラベルがつけられたPRをカテゴライズする(
release.yml
)
- 1でラベルがつけられたPRをカテゴライズする(
- リリースノートを作成する
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 | sed 's/"//g' | awk '{if ($0 == "null") print ""; else print}')" >> $GITHUB_OUTPUT
# 前回リリースからの差分をもとに、リリースノートの本文を生成する
- name: Generate release note changes
id: release_note
run: |
echo "release_note=$(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 | sed 's/"//g')" >> $GITHUB_OUTPUT
# タグを切り、リリースノートを作成する
- name: Create Release
run: |
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 }}\"}" \
https://api.github.com/repos/${{ github.repository }}/releases
2.PRのカテゴライズ
リリースノートを生成するときに下記が自動的に割り当てられる
# 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 | sed 's/"//g')" >> $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 | sed 's/"//g' | awk '{if ($0 == "null") print ""; else print}')" >> $GITHUB_OUTPUT
| awk '{if ($0 == "null") print ""; else print}'
他にも、jq .body // empty
を試しましたが、マークダウン(複数行の文字列)に対応できず(?)正常に動作しませんでした。
リリースノートを作成する
流れとしては次のようになります。
- リリースノートの本文を生成
- リリースノートを作成する
- PRで記載した内容をのせる
本文を生成する
こちらで生成が可能です
POST /repos/{owner}/{repo}/releases/generate-notes
# 前回リリースからの差分をもとに、リリースノートの本文を生成する
- name: Generate release note changes
id: release_note
run: |
echo "release_note=$(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 | sed 's/"//g')" >> $GITHUB_OUTPUT
リリースノートを作成する
生成した本文を使用しリクエストを行います。
またpr_description
でoutputしたもの(${{steps.pr_description.outputs.pr_description}}
)は生成した本文のbodyを合わせて、リクエストボディに含めます。
POST /repos/{owner}/{repo}/releases
# タグを切り、リリースノートを作成する
- name: Create Release
run: |
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 }}\"}" \
https://api.github.com/repos/${{ github.repository }}/releases
リリースノートをカテゴライズ
# 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 }}
set-outputではなくGITHUB_OUTPUT
将来的にset-outputは非推奨になりworkflowでエラーとなるためGITHUB_OUTPUTを使用する
- name: Set output
run: echo "::set-output name={name}::{value}"
↓
- name: Set output
run: echo "{name}={value}" >> $GITHUB_OUTPUT
# 前回のリリースタグを取得する
- name: Get previous tag
id: pre_tag
run: |
- echo "::set-output name=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)"
+ 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
# タグを生成する 「v{バージョン番号}」
- name: Generate tag
id: release_tag
run: |
- echo "::set-output name=release_tag::$today-$(($pre_release_count + 1))"
+ echo "release_tag=$today-$(($pre_release_count + 1))" >> $GITHUB_OUTPUT
# 前回リリースからの差分をもとに、リリースノートの本文を生成する
- name: Generate release note
id: release_note
run: |
- echo "::set-output name=release_note::$(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 }}"}' | jq .body | sed 's/"//g')"
+ echo "release_note=$(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 }}"}' | jq .body | sed 's/"//g')" >> $GITHUB_OUTPUT
セマンティックバージョン
イメージ
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などでコミットメッセージを整備できていなかったので手間が増える
参考
こちらの記事がなければ実装できていなかったです。ありがとうございます!
スクラップ
Discussion