semantic-releaseを使ってReact Nativeアプリケーションのバージョニングとリリースノート作成を行う
By kkoudev
iOS/Androidアプリケーションのバージョニングはセマンティックバージョニングを採用しています。
しかし、バージョニングを行う際に、どのような修正でメジャーバージョンを上げるのか、
どのような修正でマイナーバージョンを上げるかなどの判断がしづらいケースがあります。
また、リリースする際に意図しない修正がリリースに入ることを防ぐために どこまでの修正がリリースに入っているかをわざわざコミットログを確認しながらチェックするといった作業を行うケースもありますが、 毎回それを人的チェックで行うのも面倒です。
そこで、semantic-releaseを使ってリリースする修正内容から自動的にバージョニングを行い、 更にはリリースノートを作成することでどのコミット内容がリリースされるのかをリリースノートへログとして残すことを期待して semantic-releaseを導入してみましたので、そのときの導入手順を紹介させて頂きます。
semantic-releaseについて
semantic-releaseは主にnpmへ公開するライブラリのバージョンをAngularで採用しているコミットメッセージ形式から自動算出し、リリースノートまで作成してくれるライブラリです。
Angularで採用しているコミットメッセージ形式とは以下のようなコミットメッセージになります。(公式から抜粋)
<type>(<scope>): <subject>
typeはコミットの種別を表し、scopeは変更箇所や種類を表す単語、subjectは修正内容を表します。
(scopeは省略可能です)
typeについては以下の種類があります。
feat: 機能追加時
fix: バグ修正時
docs: ドキュメント変更時
style: フォーマッターやインデント、スペースの修正など
refactor: コードのリファクタリング時
perf: パフォーマンスに関する修正時
test: テスト修正時
chore: エコシステムに関する修正など、アプリケーションの修正に直接関わらない細かい修正時
revert: 修正のリバート時
そして、semantic-releaseを使う上では
このコミットメッセージの形式をプロジェクトメンバー全員に利用して貰うことが必須となります。
ルール自体はそこまで難しくないので、すぐに慣れるかと思います。
commitlintというライブラリを使うと、このAngular形式のコミットメッセージのルールに則っているかをチェックすることが出来ますので
huskyと合わせて導入してコミット時にチェックするといったことも可能です。
リリースフローについて
では実際にsemantic-releaseを使うに当たってどのようなリリースフローを取る必要があるかを説明します。
私が作っているアプリケーションではhotfixを行うケースを考慮してgit-flowを採用していますので、
git-flowを前提として説明します。
簡単にまとめると以下の流れになります。
- developブランチからreleaseブランチを作成する
- PRを作成し、1で作成したreleaseブランチをmasterブランチへマージする
- GitHub Actionsを使ってmasterブランチへマージされたらsemantic-releaseが動作するようにする
- semantic-releaseでタグ付けが実行されたら、そのタグ付けをトリガーに本番ビルドが実行されるようにしておく
1. developブランチからreleaseブランチを作成する
developブランチの内容をリリースできる段階になったら、そのときの修正内容をreleaseブランチとして作成します。
ここで作成するreleaseブランチはすぐにマージをするため名前は何でも良いのですが、私のプロジェクトでは迷わないように release/YYYYMMDDhhmm
を命名するようにしています。
すぐにマージするならローカルで手動マージするのと同じなのではと思う方もいるかと思いますが、
リリース時のトリガーをPRとしてログを残すこと、及びその時点で本番にリリースしたい変更をリリースブランチとして一旦切り出すことで、意図しない変更が途中でマージされるということを防ぐ目的や、メジャーバージョンアップする際のコミットメッセージをここで入れる目的もあり、意図的にブランチを作成することとしています。
2. PRを作成し、1で作成したreleaseブランチをmasterブランチへマージする
作成したreleaseブランチをmasterへマージするようにしてPRを作成します。
すぐにリリースする場合はこのままGitHub上からマージします。
3. GitHub Actionsを使ってmasterブランチへマージされたらsemantic-releaseが動作するようにする
ここでsemantic-releaseが動作し、コミットメッセージからバージョンの算出とリリースノートの作成が行われます。 (具体的な設定は後ほど紹介します)
4. semantic-releaseでタグ付けが実行されたら、そのタグ付けをトリガーに本番ビルドが実行されるようにしておく
semantic-releaseでタグ付がされたら、本番ビルドが実行されるようにしておきます。
こうしておくことで、リリースノートの内容と本番ビルド結果が同期されるようになります。
semantic-releaseの各プラグインの説明
では実際にsemantic-releaseを設定する上でのポイントを紹介します。
semantic-releaseにはいくつかプラグインがあり、React Nativeアプリケーションをリリースするに当たっては以下のプラグインを利用します。
@semantic-release/commit-analyzer
@semantic-release/release-notes-generator
@semantic-release/npm
@semantic-release/exec
@semantic-release/github
@semantic-release/git
@saithodev/semantic-release-backmerge
semantic-release-slack-bot
@semantic-release/commit-analyzer
コミットからバージョンの算出やリリースノートの作成を行う上では必須となります。
@semantic-release/release-notes-generator
その名の通り、リリースノートを作成します。
@semantic-release/npm
バージョニングを行います。
このプラグインはバージョニング以外にもnpmへpublishするためのものなのですが、
package.jsonに private: true
が設定されていればpublishはされませんのでそれを利用します。
@semantic-release/exec
各実行フェーズに任意のコマンドを実行するために利用します。
このプラグインを使うことで、iOSアプリケーションのInfo.plistのバージョンを更新します。
ちなみにAndroidの方は package.json の version をGradleから直接読み込み、そのバージョン番号を元にappVersionCodeを算出するようにしておきます。
@semantic-release/github
GitHubの対象リポジトリへリリースノートを作成します。
@semantic-release/git
自動バージョニングされたファイル(package.jsonとInfo.plist)をコミット&プッシュするために利用します。
@saithodev/semantic-release-backmerge
自動バージョニングされた package.json と Info.plist の変更を develop ブランチへ自動的にマージ&プッシュするために利用します。
@semantic-release/git
で自動バージョニングされたファイルは master ブランチへしか反映されないため、このままだと develop ブランチへ変更が反映されません。
develop ブランチへも変更を反映するためにはこのプラグインを利用します。
semantic-release-slack-bot
semantic-releaseの実行結果をSlackへ通知します。
semantic-releaseの設定方法
各ファイルに以下のように設定します。
.github/release.yaml
name: Release
on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- master
jobs:
semantic_release:
runs-on: macos-10.15
steps:
- name: Checkout
if: contains(github.event.head_commit.message, 'chore(release):') == false
uses: actions/checkout@v2
with:
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Semantic Release
if: contains(github.event.head_commit.message, 'chore(release):') == false
uses: cycjimmy/semantic-release-action@v2
with:
# You can specify specifying version range for the extra plugins if you prefer.
extra_plugins: |
@semantic-release/commit-analyzer
@semantic-release/release-notes-generator
@semantic-release/npm
@semantic-release/exec
@semantic-release/github
@semantic-release/git
@saithodev/semantic-release-backmerge
semantic-release-slack-bot
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_SEMANTIC_RELEASE }}
actions/checkout 実行時の tokenに Personal Access Token (PAT) を設定しているのがポイントです。(これは自前で環境変数として設定しています)
GITHUB_TOKEN を設定すると、CIでPushされた変更を元に更にアクションを実行するといったことが出来ません。
ただ、PAT を利用すればそれが可能になります。
今回のリリースフローでは自動的にバージョニングされたバージョン番号をタグとしてPushすることと、
タグPushで本番ビルドを実行するようにしているため、アクション内で別アクションを実行可能にする必要があります。
stepsにある if はバージョニング後のコミット内容がPushされたことで、再度semantic-releaseが起動しないようにするために行っています。
GitHub Actionsでは skip ci というテキストをコミットメッセージに記述するとアクションの起動をしないようにすることが可能ですが、
これをしてしまうと前述のタグPush時の本番ビルドアクションも実行されなくなってしまいます。
そのため、少々トリッキーですが if で多重にsemantic-releaseが実行されないように回避しています。
package.json
"release": {
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
[
"@semantic-release/exec",
{
"prepareCmd": "./scripts/ci_app_version_modify_plist.sh"
}
],
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"ios/semantic-release-example/Info.plist"
],
"message": "chore(release): ${nextRelease.version} \n\n${nextRelease.notes}"
}
],
[
"@saithodev/semantic-release-backmerge",
{
"branchName": "develop",
"plugins": [
[
"@semantic-release/exec",
{
"successCmd": "echo 'Latest release version is ${nextRelease.version}' > release-version.txt && git add release-version.txt"
}
]
]
}
],
[
"semantic-release-slack-bot",
{
"notifyOnSuccess": true,
"notifyOnFail": true,
"markdownReleaseNotes": true,
"packageName": "semantic-release-example"
}
]
]
}
ポイントとしては以下の通りです。
- @semantic-release/exec でInfo.plistのバージョンを更新している
- @saithodev/semantic-release-backmerge で develop へマージする際のファイル変更を git add している
Info.plistの更新のために実行しているスクリプトは以下のようになっています。
ci_app_version_modify_plist.sh
#!/bin/bash
# No exits plutil?
if ! type plutil > /dev/null 2>&1; then
echo "Please install xcode command line tools."
exit 1
fi
# No exits jq?
if ! type jq > /dev/null 2>&1; then
echo "Please install jq command."
exit 1
fi
# ------------------------
# Defines variables
# ------------------------
readonly PROJECT_DIR="$(cd ${0%/*}/.. && pwd)"
readonly INFO_PLIST_PATH="${PROJECT_DIR}/ios/semantic-release-example/Info.plist"
readonly PACKAGE_JSON_PATH="${PROJECT_DIR}/package.json"
readonly APP_VERSION="$(cat ${PACKAGE_JSON_PATH} | jq -r '.version' | tr -d '\n')"
# ------------------------
# Modify Info.plist
# ------------------------
plutil -replace "CFBundleShortVersionString" -string "${APP_VERSION}" "${INFO_PLIST_PATH}"
メジャーバージョンを上げる場合
メジャーバージョンを上げる場合は、semantic-releaseの仕様上、コミットメッセージの description に BREAKING CHANGE: というメッセージを含める必要があります。
ただ、それを各修正でいちいち考慮するのは大変なので、リリースブランチを作成した際に、以下のような空コミットを作成することで行うようにしています。
git commit --allow-empty -m "chore(release): YYYYMMddHHmmリリース" -m "BREAKING CHANGE: メジャーバージョンアップ"
ビルド番号について
以上の方法でsemantic-releaseを使ったバージョニングとリリースノート作成が可能となります。
ただ、この方法は1つ欠点があり、ビルド番号の変更を考慮できていません。
iOSでは特に審査NGになった際にはバージョンはそのままでビルド番号を上げて再申請することもあるかと思います。
このリリース方法では、現状それが出来ないことになります。
これについては私の利用ケースでは、審査NGになった場合は思い切ってパッチバージョンを1つ上げることで対応しています。
semantic-releaseがあくまでセマンティックバージョニングにしか対応していない以上、
このようなケースにはどうしても自動で対応し辛いのが現状です。
自動でバージョニング出来る利点とのトレードオフと考えればそこまで苦ではないので現状そのように対処してしまっているのですが、 良い方法が思いついたらいずれ改修してみようかと思います。
まとめ
以上で、React Nativeアプリケーションのバージョニングとリリースノート作成について紹介しました。
この方法はReact Nativeアプリケーション以外にも応用可能ですので、自動でバージョニングとリリースノート作成を行いたいという方の参考になれば幸いです。