🤖

monorepoのリポジトリにRelease Drafterを導入して複数のタグを管理する[NestJSプロジェクトを例に]

2023/12/07に公開

はじめに

リリースノートの管理、手動でやると大変ですよね。

  • 1日に複数回のリリースを行っている
  • 複数のチームで同じリポジトリを触っている

といった状況だと、毎回のリリースの変更管理を手でやるのはなかなか骨が折れます。

こういった場合、Release DrafterというGitHub Actionを導入することで、リリース間にマージされたPR一覧をもとにリリースノートを生成できます。

https://github.com/release-drafter/release-drafter

このRelease Drafterはmonorepoのプロジェクトでも活用できます。

この記事ではNestJSプロジェクトを例に、monorepoのリポジトリにRelease Drafterを導入して、複数のタグを管理する様子を紹介してみます。

Release Drafterの流れ

Release Drafterに関しては以下の記事がとても参考になるので、合わせて読んでいただけると理解が深まるかと思います。

https://zenn.dev/kounoike/articles/20220211-easy-generate-release-notes

基本的なワークフローは次の通りです。

  • Pull Requestを作成する
  • PRのtitleやブランチ名に基づいてlabelを自動で付与する(autolabeler)
    • 手動でラベルを設定してもよい
  • ラベルが設定されたPRをマージする
  • 次期リリースのリリースノートが下書き状態(draft)で生成される
    • 次期バージョン番号は既存のタグを参照してあらかじめ決めたルールで解決される
    • あるいはワークフロー上で自力で次期リリースのタグ番号を決めることも可能(CalVerの場合など)
    • 次リリースに含まれるPR一覧がラベルによりカテゴライズされてリリースノートに載る
  • リリースする段階になったら、draftをpublishしてリリース完了

よって、導入する際はまず次の内容を決定して導入を進めると良いでしょう。

  • どのようなルールでリリースタグを作成するか、タグ名のルール
    • Semantic Versioning, Calendar Versioning
    • Semantic Versioningの場合はmajor, minor, patchの基準
  • どのようなラベルをリポジトリ内で利用するかと、その設定ルール
    • feature, bug fix, dependencies, ... etc.
    • Release Drafterの組み込みのautolabelerでは以下の正規表現によるマッチに対応している
      • 変更したファイルパス (files)
      • ブランチ名 (branch)
      • PRタイトル (title)
      • PR本文 (body)

ちなみに最近自社のプロダクトにいくつか追加していったのですが、CalVerで整備していきました。CalVerで利用するにはworkflowにて自前でバージョン番号を作る必要があります。CalVerの設定については次の記事が参考になります。

https://qiita.com/madogiwa/items/87469239d1bee515a514

monorepoに導入する

monorepoの場合は少し工夫が必要です。monorepoに導入する場合は、tag-prefixinclude-paths の項目を利用します。

tag-prefix を指定した場合、過去のリリースタグをフィルタして次期バージョンを決定する際に、同じprefixを持ったタグのみにフィルタして次期リリース内容を決定してくれます。これを使うことで、ひとつのリポジトリに複数の命名ルールのタグを作成することができます。

- common-package@0.0.4 # draft。common-package@0.0.3をもとに決定される
- common-package@0.0.3 # リリース済み
- common-package@0.0.2
- common-package@0.0.1

- admin-package@0.0.4 # draft。admin-package@0.0.3をもとに決定される
- admin-package@0.0.3 # リリース済み
- admin-package@0.0.2
- admin-package@0.0.1

include-paths は指定したパスに含まれるファイルが変更されたPRのみリリースノートに含まれるようになります。

例として、

  • common-packageadmin-package というパッケージを同一のリポジトリで開発している
  • packages/common-packagepackages/admin-package というディレクトリで管理している
  • タグを common-package@<major>.<minor>.<path>, admin-package@<major>.<minor>.<path> という風に分けて管理する

とした場合、次のように設定することで、そのパッケージ内のファイルを変更したPRのみを含んだリリースノートの生成が可能です。

package tag-prefix include-paths
common-package common-package@ packages/common-package/
admin-package admin-package@ packages/admin-package/

NestJSのmonorepoで試してみる

monorepoで利用する場合どんなイメージになるか、サンプルを作ってみます。NestJSのmonorepo modeでサクッとプロジェクトの構造を作って解説します。

この記事に書くにあたって作成したプロジェクトを以下のrepositoryにpushしています。よければ参考にしてみてください。

https://github.com/koga1020/nestjs-monorepo-release-drafter-sample

NestJSに対する解説はこの記事の本筋ではないので割愛します。以下のバージョンでジェネレーターを動かしています。

$ nest new --version
10.2.1

サンプルプロジェクトの作成

例として、次のような構成を作ってみます。

  • 2つのアプリケーション
    • products-api
    • users-api
  • 1つの共有ライブラリ
    • shared

ジェネレーターを利用してプロジェクトの枠組みを生成します。

$ nest new products-api
$ cd products-api
$ nest generate app users-api
$ nest generate library shared

コマンドの結果、このようなファイル構成となりました。

./README.md
./apps/
./apps/products-api/
./apps/products-api/src/
./apps/products-api/src/app.controller.spec.ts
./apps/products-api/src/app.controller.ts
./apps/products-api/src/app.module.ts
./apps/products-api/src/app.service.ts
./apps/products-api/src/main.ts
./apps/products-api/test/
./apps/products-api/test/app.e2e-spec.ts
./apps/products-api/test/jest-e2e.json
./apps/products-api/tsconfig.app.json
./apps/users-api/
./apps/users-api/src/
./apps/users-api/src/main.ts
./apps/users-api/src/users-api.controller.spec.ts
./apps/users-api/src/users-api.controller.ts
./apps/users-api/src/users-api.module.ts
./apps/users-api/src/users-api.service.ts
./apps/users-api/test/
./apps/users-api/test/app.e2e-spec.ts
./apps/users-api/test/jest-e2e.json
./apps/users-api/tsconfig.app.json
./libs/
./libs/shared/
./libs/shared/src/
./libs/shared/src/index.ts
./libs/shared/src/shared.module.ts
./libs/shared/src/shared.service.spec.ts
./libs/shared/src/shared.service.ts
./libs/shared/tsconfig.lib.json
./nest-cli.json
./package-lock.json
./package.json
./tsconfig.build.json
./tsconfig.json

workflowの方針決め

次に、どのようなリリース管理を回していくかを設計します。今回のサンプルプロジェクトでは次のような方針とします。

  • mainブランチ一本での運用とし、mainブランチに対してPRを作成、マージする
  • Semantic Versioningとする
  • 2つのアプリケーション、1つの共有ライブラリでそれぞれリリースタグを作成する
    • products-api@<major>.<minor>.<patch>
    • users-api@<major>.<minor>.<patch>
    • shared@<major>.<minor>.<patch>
  • major, minor, patchの判定は次のようにする
    • major: major のラベルを手動でつけた場合にmajorとする
    • minor: feature のラベルがついていたPRが含まれる場合、minorとする
    • patch: 上記以外。デフォルトでpatchとする
  • auto labelerの設定
    • ブランチ名の正規表現によるマッチで行う。<category>/ のprefixをつけたブランチ名で運用する
    • 以下のカテゴリーを用意する
      • feature
      • bug fix
      • docs

初期リリースを作っておく

まずはアプリケーション、ライブラリの最初のリリースを 0.0.1 として手動で作っておきます。

GitHubのリリース作成画面キャプチャ

07d09bdd-618c-48f8-9322-2fa90178db5f.png

ここからの差分をRelease Drafterで管理します。

Release Drafterの導入

Release Drafterの導入には、大きく分けて2種類のファイル追加を行います。

  • Release Drafterの設定ファイル
  • GitHub Actionsのworkflow

設定ファイルの追加

.github/ 配下に設定ファイルを追加します。autolabelerの設定はアプリケーション、ライブラリですべて共通なので、_extends を使って重複分は1つのファイルにまとめることが可能です。

ここでは _extends 元のファイルを release-drafter-base-config.yml としましょう。

.github/release-drafter-base-config.yml
categories:
  - title: "🚀 Feature"
    labels:
      - feature
  - title: "🐛 Bug Fixes"
    labels:
      - bug
  - title: "📖 Docs"
    labels:
      - documentation
autolabeler:
  - label: "feature"
    branch:
      - "/^feat/i"
      - "/^feature/i"
  - label: "bug"
    branch:
      - "/^fix/"
  - label: "documentation"
    branch:
      - "/^docs/"
version-resolver:
  major:
    labels:
      - "major"
  minor:
    labels:
      - "feature"
  default: "patch"

template: |
  ## Changes

  $CHANGES

前述の通り、ブランチ名に対する正規表現のマッチをもとに自動でラベルが設定されるようにしています。feat/feature/ の両方を許容するなど、柔軟に設定可能です。categories の設定により、ラベルに応じてリリースノート内でPRがカテゴライズされて表示されます。

続いて、このファイルを拡張してアプリケーション、ライブラリの設定ファイルを追加します。それぞれ release-drafter-products-api.yml, release-drafter-users-api.yml, release-drafter-shared.yml とします。ファイルの命名は特に制約はないのでプロジェクトごとに自由に決めましょう。

.github/release-drafter-products-api.yml
_extends: nestjs-monorepo-release-drafter-sample:.github/release-drafter-base-config.yml # extend元の設定ファイルをリポジトリ名:ファイルパスで指定
name-template: products-api@$RESOLVED_VERSION # リリースノートのタイトルはタグ名と同じにする
tag-template: products-api@$RESOLVED_VERSION # タイトルとタグ名を同じにする
tag-prefix: products-api@ # tagのprefixを指定
include-paths: # products-apiのリリースノートに含めるファイルパスを指定
  - "apps/products-api/"
.github/release-drafter-users-api.yml
_extends: nestjs-monorepo-release-drafter-sample:.github/release-drafter-base-config.yml # extend元の設定ファイルをリポジトリ名:ファイルパスで指定
name-template: users-api@$RESOLVED_VERSION # リリースノートのタイトルはタグ名と同じにする
tag-template: users-api@$RESOLVED_VERSION # タイトルとタグ名を同じにする
tag-prefix: users-api@ # tagのprefixを指定
include-paths: # users-apiのリリースノートに含めるファイルパスを指定
  - "apps/users-api/"
.github/release-drafter-shared.yml
_extends: nestjs-monorepo-release-drafter-sample:.github/release-drafter-base-config.yml # 継承元の設定ファイルをリポジトリ名:ファイルパスで指定
name-template: shared@$RESOLVED_VERSION # リリースノートのタイトルはタグ名と同じにする
tag-template: shared@$RESOLVED_VERSION # タイトルとタグ名を同じにする
tag-prefix: shared@ # tagのprefixを指定
include-paths: # sharedのリリースノートに含めるファイルパスを指定
  - "libs/shared/"

_extends を使うことで共通部分は省略して設定ファイルを書くことができています。include-paths の設定により、各アプリケーション、パッケージごとに指定したファイルパスの変更が含まれるPRのみをリリースノートに含めるようにしています。

ワークフローファイルの追加

設定ファイルが用意できたので、この設定ファイルに基づいて動作するワークフローを追加しましょう。

.github/workflows/release-drafter.yml
name: Release Drafter

on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, reopened, synchronize]
jobs:
  update_release_draft:
    permissions:
      contents: write
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - name: Draft products-api release
        uses: release-drafter/release-drafter@v5
        with:
          config-name: release-drafter-products-api.yml
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Draft users-api release
        uses: release-drafter/release-drafter@v5
        with:
          config-name: release-drafter-users-api.yml
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Draft shared library release
        uses: release-drafter/release-drafter@v5
        with:
          config-name: release-drafter-shared.yml
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

PRへのラベル付与と、リリースノートの作成ができるようにpermissionsの設定が必要です。autolabelerを利用しない場合はpull_requestのタイミングでのワークフロー実行は不要です。

これで設定自体は終わりです。あとはPRを作ってマージしていくのみです。

ファイルを変更する

feat/ のprefixをつけたブランチを作成して、apps/products-api 配下のファイルに変更を加えてcommit, PRを作成してみます。このとき、PRに対して自動でラベルが設定されます。

GitHub PR画面キャプチャ。Release Drafterによってラベルが自動で設定されている

PRをマージすると再度release-drafterのワークフローが動作し、アプリケーション、ライブラリの次期バージョンのリリースのドラフトが作成されます。products-apiの分には今回の変更が一覧に出てきており、minorが上がっています。今回の修正に関係のないproducts-api以外のリリースノートには含まれていないのがポイントです。

GitHub リリース画面キャプチャ。Release Drafterによってproducts-apiの次期リリースのリリースノートのdraftが作成されている

今度はshared配下のdocumentの更新をしてみます。docs/ のprefixをつけたブランチとして、PRを作成します。この場合、documentation というラベルがPRに対して自動で設定されます。

PRをマージしてリリースを確認すると、shared のリリースのdraftに今回のPRが載り、minorではなくデフォルトのpatchとして次期タグが決定されています。リリース内の見出しもラベルに応じて変わっていることが分かります。

GitHub リリース画面キャプチャ。Release Drafterによってsharedの次期リリースのリリースノートのdraftが作成されている

あとはこの繰り返しです。プロジェクトのルールに沿ってdraft状態のリリースを公開し、次にCIが回るとまたdraftのリリースが作成されるというサイクルで開発を進めることができます。

余談:公式にもリリースノートの生成機能あるよね?

公式にもリリースノートの生成機能があり、リリースの画面やAPIから利用することができます。

https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes

一方で、この公式の機能だけだと次期バージョン番号の決定までは行えず、リリースノートの生成までに留まります。「リリースの時に何らかまとまったドキュメントが得られれば良い」といった温度感であれば、こちらの公式の機能で十分です。プロジェクトごとに何をどこまで自動化するのかに応じて使い分けると良いと思います。

まとめ

Release Drafterを導入してmonorepoで管理しているアプリケーション・ライブラリのタグを複数作成する流れを解説しました。この記事では触れていませんが、リリースノート内の特定の文字列を置換する機能(Replacers)や、ラベルによるPRの除外機能などもあり、機能も豊富です。

ドキュメンテーションは大事である一方、手動で運用するとコストが見合わずに放置されがちです。コスパよく快適に運用したいものです。ぜひ参考にしてみてください。

スタフェステックブログ

Discussion