結果から
前提
- GitHubのAutomatically generated release notesという仕組みを使います
- 今回はライブラリのリリースノート自動生成を行います。セマンティックバージョニングに対応して、リリースノートの内容が変わるようにします。(この辺の独自要件をどうするかは後半に纏めてます)
Automatically generated release notes とは
要約すると、.github/release.yml
ファイルをリポジトリに置いて、プルリクエストにラベルを貼ればいい感じのリリースノートが作れるぞってことです!
release.yml
でリリース内容をカテゴリ分けして、対応するラベルを定義します。
実際見た方がわかりやすいので、今回作ったyamlファイルを以下に載せます。
changelog:
categories:
- title: Breaking Changes 🛠
labels:
- Semver-Major
- title: Exciting New Features 🎉
labels:
- Semver-Minor
- title: Fixes and improvements 🐛
labels:
- Semver-Patch
- title: Dependencies
labels:
- dependencies
- title: Other Changes
labels:
- "*"
説明すると、一番上の設定はプルリクエストにSemver-Major
ラベルが貼ってあったら、リリースノートにBreaking Changes 🛠
セクションを設けて、そのセクション内に対象のプルリクエストを記載するという意味です。
他の設定も同様で、もちろんtitleやlabelsは好きに変更できます。
今回はセマンティックバージョニングに対応させる為にそれっぽい名前にしてます。
ちなみにdependenciesはdependabotが生成するプルリクエストに自動で付与されるラベルです。
プルリクエストにラベルを貼る
release.yml
が用意できたら、次はラベル貼りです。
手動で貼っても良いのですが、どうせなら自動化したいですよね。
GitHub Actionsからgithub-scriptを使うことで実現可能です。
github-scriptは、JavaScriptでGitHub APIを叩けるやつです。
実際作ったyamlファイルはこんな感じ。
name: Set Label to Pull Request
inputs:
semver:
required: true
runs:
using: "composite"
steps:
- uses: actions/github-script@v6
with:
script: |
const { SEMVER } = process.env
github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [SEMVER]
});
env:
SEMVER: ${{ inputs.semver }}
いくつか今回の要件に合わせた記法が含まれてますが(※後ほど解説します)
ラベルの付与はgithub.rest.issues.setLabels
APIを呼び出せばOK。
APIの呼び出しに使っているgithub
は、github-scriptが提供している引数で、デフォルトで使えます。
setLabels
APIのリファレンスはこちら
なお、addLabels
という似たAPIがありますが、こちらはその名の通りラベルを追加します。setLabels
は今付与されているラベルがあったら削除してから貼る挙動。
リリースノートの生成
今回導入したリポジトリではリリースの作成にaction-gh-releaseというライブラリを使っていたので、これのgenerate_release_notes
という設定をtrue
にするだけでした。
他の手段としては、GUI上でリリースを作成する際にGenerate release notes
ボタンを押下するか、gh release create --generate-notes
コマンドを実行する方法もあるようです。
package.jsonのバージョンに連動してプルリクエストにラベルを付与したい
さて、ここからが独自要件です。まずは前提から。
- 対象とするリポジトリは、社内で使っている共通ライブラリ
- 一つのリポジトリ内で複数のパッケージを管理している
- セマンティックバージョニングを採用しており、パッケージに何らかの変更を加えたら、package.jsonのversionを手動で変更してプルリクエストに含める運用をしている
ここで改めてsetLabels
を実行するyamlファイルを見てみましょう。
name: Set Label to Pull Request
inputs:
semver:
required: true
runs:
using: "composite"
steps:
- uses: actions/github-script@v6
with:
script: |
const { SEMVER } = process.env
github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [SEMVER]
});
env:
SEMVER: ${{ inputs.semver }}
using: "composite"
という記述がありますね。
これは複数のワークフローで同じアクションを実行したい場合に一連のステップを切り出しておいて、ワークフローから利用することで処理を共通化する仕組みです。
複数のパッケージで使いたかったので、採用しています。
compositeの詳細はこちら。構文の詳細はこちらも参照。
inputs
構文は、他のstep(利用する側のワークフロー)から値を受け取る為に記載しています。
ここでは、ラベルに記載する文字列を受け取っていますね。
この文字列が「Semver-Major」とか「Semver-Minor」になります。
package.jsonのversionが1.0.0
だったとして、これを1.0.1
に変えたら、「Semver-Patch」が渡ってくるようにしたいわけです。
では、どうするか。
やるべきことは、以下のフローになります。
- package.jsonから現在のversionと変更後のversionを取得する
- それらを比較し、メジャー・マイナー・パッチのどのバージョンが上がっているかを検出する
- 検出結果から、付与するラベルを決定する
この一連の流れをアクションyamlにすると、以下のようになります。
name: utility/version-check
on:
pull_request:
branches:
- main
paths:
- "utility/**"
jobs:
version-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get new version
id: new-package-version
uses: martinbeentjes/npm-get-version-action@v1.3.1
with:
path: utility
- uses: actions/checkout@v3
with:
ref: "main"
- name: Get current version
id: current-package-version
uses: martinbeentjes/npm-get-version-action@v1.3.1
with:
path: utility
- uses: actions/checkout@v3
- name: Check version update
id: check-version-update
uses: ./.github/workflows/composite/check-version-update
with:
new-version: ${{ steps.new-package-version.outputs.current-version }}
current-version: ${{ steps.current-package-version.outputs.current-version }}
- name: Set Label to Pull Request
uses: ./.github/workflows/composite/set-label
with:
semver: ${{ steps.check-version-update.outputs.semver }}
実は元々version-check
という「package.jsonのversionをちゃんとインクリメントしているか」チェックするワークフローが作られており、ラベル付与においてもこの仕組みを利用しました。
npm-get-version-action
というライブラリで、現在のブランチのpackage.jsonからversionを取得します。
最初は作業ブランチ側のversionを取得して、その後actions/checkout@v3
の対象をmain
にすることでマージ対象のブランチからversionを取得しているわけですね。
そして、それらをcheck-version-update
に渡す。ここでもcompositeを使っています。
その内容がこちら
name: check-version-update
inputs:
new-version:
required: true
current-version:
required: true
outputs:
semver:
description: "Semantic Versioning"
value: ${{ steps.check-version-update.outputs.semver }}
runs:
using: "composite"
steps:
- name: Check version update
id: check-version-update
shell: bash
run: |
new_version=${{ inputs.new-version }}
new_ver_array=(${new_version//./ })
current_version=${{ inputs.current-version }}
current_ver_array=(${current_version//./ })
if [ ${new_ver_array[0]} -gt ${current_ver_array[0]} ]; then echo "semver=Semver-Major" >> "$GITHUB_OUTPUT"; exit 0; fi
if [ ${new_ver_array[0]} -eq ${current_ver_array[0]} ] && [ ${new_ver_array[1]} -gt ${current_ver_array[1]} ]; then echo "semver=Semver-Minor" >> "$GITHUB_OUTPUT"; exit 0; fi
if [ ${new_ver_array[0]} -eq ${current_ver_array[0]} ] && [ ${new_ver_array[1]} -eq ${current_ver_array[1]} ] && [ ${new_ver_array[2]} -gt ${current_ver_array[2]} ]; then echo "semver=Semver-Patch" >> "$GITHUB_OUTPUT"; exit 0; fi
echo "Please update version in package.json"
exit 1
bashで1.0.0
などの文字列をドット区切りで配列にする書き方が珍しいですね。
あとは条件分岐でラベルに使いたい文字列を出力してます。echo "semver=Semver-Major" >> "$GITHUB_OUTPUT";
とかがそれです。
stepの出力仕様についてはこちら
ここまで出来たら、あとは度々掲示しているsetLabels
を実行しているアクションと繋いで完了です!
感想
リリースノートの自動生成を行う技術記事は他にも色々あったものの、GitHub Actionsの更新が多いからか記述方法が古かったり、そもそもやり方が違うものも多い状況でした。
ということで本記事を書いてみましたが、これもすぐに古くなるんだろうなあ。。
参考
actions/github-script と composite アクションでカスタムアクションをかんたんに作れる
GitHubのリリースノートを自動化する仕組み