GitHub Actions + github-pr-release でリリースノート生成を自動化する

[github-pr-release](https://github.com/uiur/github-pr-release) は、あるリリースに含まれるPull Requestをいい感じにリストアップしたリリース用のPRを作ってくれる便利ツールです。
自分が開発に携わっているチームではリリースノートをGitHubのリリース機能で残しているのですが、その内容は`github-pr-release`がまとめてくれたPRリストから作られており、人力コピペ作業が発生していました。
今回は GitHub Actions を使って、リリース用PRがマージされたらその内容をコピーしたGitHubリリースを生成するようにしてみました。

## 実現したこと
まず`github-pr-release` で開発用ブランチ → 本番環境用ブランチへのリリースPRを作ります。
そのリリースに含まれる修正内容がPRの概要にリストアップされていますね。

![releasePR](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19366/5e13b9ec-a967-c42d-b54e-df8d5d6561b4.png)

このリリースPRをマージすると、自動でPRの概要をコピーしたGitHubのリリースが作られます。

![release](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19366/94c66649-9d1a-ed4d-4c40-b8cb46850f5f.png)


## GitHub Actions の定義

これを実現する GitHub Actions の定義例はこちら。

```yml:.github/workflows/create_release.yml
on: 
  pull_request:
    types: [closed]

jobs:
  create-release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@0.3.0
        env:
          PRODUCTION_BRANCH: release
          TZ: Asia/Tokyo
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const pr = context.payload.pull_request
            if (!(pr.merged && pr.base.ref === process.env.PRODUCTION_BRANCH)) return;
            
            const merged_at = new Date(pr.merged_at);
            const tag_name = String(merged_at.getFullYear()).padStart(4, '0')
              + String(merged_at.getMonth() + 1).padStart(2, '0')
              + String(merged_at.getDate()).padStart(2, '0')
              + String(merged_at.getHours()).padStart(2, '0')
              + String(merged_at.getMinutes()).padStart(2, '0')
              + String(merged_at.getSeconds()).padStart(2, '0');

            github.repos.createRelease({
              ...context.repo,
              draft: true,
              tag_name: tag_name,
              name: tag_name,
              target_commitish: process.env.GITHUB_SHA,
              body: pr.body
            });
```

部分ごとに解説します。

## ワークフローをトリガーするイベント

GitHub Actions では、PRのマージを `pull_request`イベントの `closed`アクティビティで検知できます。[^1]


```yml
on: 
  pull_request:
    types: [closed]
```

PRがマージされずにクローズされた場合も起動するため、マージされたPRのみに対象を絞りたい場合は、ワークフローが起動したあとに別途チェックする必要があります。

## ステップで実行するアクション

アクション [actions/github-script](https://github.com/actions/github-script) を利用することで、 ワークフロー内で GitHub API を利用した処理が簡単に記述できるようになります。

```yml
jobs:
  create-release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@0.3.0
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            // code
```

_※ `actions/github-script`は実験段階のリポジトリのため、今後のバージョンアップで破壊的な仕様変更が起きる可能性があります。_

## マージされたリリースPRの取得

`actions/github-script`のスクリプト内では、`github`と`context`の2つの引数が提供されます。`github`は GitHub APIのクライアント、`context`は実行中のワークフローのコンテキスト情報が含まれるオブジェクトです。`pull_request`イベントの場合、マージされたPRの情報も`context`オブジェクトから参照できます。

```js
const pr = context.payload.pull_request
if (!(pr.merged && pr.base.ref === process.env.PRODUCTION_BRANCH)) return;
```

対象でない `pull_request` イベントでリリース作成処理を走らせないために、
ここではcloseされたPRがマージされているか、本番環境用ブランチに向けられているかを確認しています。

## リリースの作成

`github`引数で渡されるGitHub APIクライアントを使ってリリースを作成します。`context`から取得したリリースPRの概要をリリースの`body`として登録しています。

```js
github.repos.createRelease({
  ...context.repo,
  draft: true,
  tag_name: tag_name,
  name: tag_name,
  target_commitish: process.env.GITHUB_SHA,
  body: pr.body
});
```
タグを打つ対象のコミットにはリリースPRのマージコミットを指定しており、 `pull_request`イベントでは`GITHUB_SHA`環境変数からコミットハッシュ値が取得できます。
リリースに紐付けるタグやタイトルの形式は現場によって変えてください。ここではリリースPRのマージ日時をタグとして利用しています。

## おわりに
GitHub Actions を使うことで、GitHub上の運用効率化が簡単にできるようになりました。
Actionsは日々進化しているので、活用して効率化を進めていきましょう。


[^1]: https://help.github.com/ja/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows