GitHub Actionsとsemantic-releaseで、自動で高品質npmパッケージを一般公開する

この記事は、[弁護士ドットコム Advent Calendar](https://qiita.com/advent-calendar/2021/bengo4com)の17日目の記事です。

# この記事で伝えたいこと

- GitHub Actions とsemantic-releaseで、自動で高品質npmパッケージをリリースできます
- 使う人も作る人も簡単にメンテナンスできるようことが大事
- textlint ルールを npm パッケージとして OSS 公開

# 誰でも使いやすいパッケージとは何か

使いやすいパッケージとは、大きく下記の4つがあげられます。

- **セマンティックバージョン**で変更内容とバージョンが紐づいてわかりやすい
- **リリース頻度が多く**機能追加、バグ修正に素早く対応している
- **リリースノート**が正しく書かれて、変更内容が瞬時に把握できる
- **テスト**があり、動作が担保されている。

パッケージの中身も当然大事です。ですがいくら中身が良くても一般公開時にこれらが欠けると、使いづらくなってしまいます。

# 変更内容を見てリリースノート作って、バージョンを決めるのは大変

セマンティックバージョンで毎回整合性が担保できていますか？何をリリースしたか確認して、必要な変更だけリリースノート書けていますか？

これらを愚直に手動で実現するのは大変です。:cry:

作業が増えるほど、リリースのハードルがあがます。デザイナーさんも簡単にリリースできるようにするなど、夢のまた夢です。

# パッケージ公開までのフローを設計する

可能な限り作業を自動化する前提で考えます。手動での介入を最小限にすることで、ミスを減らし、運用コストを削減できます。

## npmパッケージリリース支援ライブラリ

よく使われるふたつのライブラリがあります。今回は自動化前提なので`semantic-release`を採用しました。

### semantic-release

https://semantic-release.gitbook.io/semantic-release/

CI で実行&完全に自動化することが前提のライブラリです。

新しいリリースをメンテナやユーザに通知したり、コミットメッセージを使用してコードベースの変更を文書化できます。

マージに基づいて、 Git タグうち&npm などで公開できます。

### standard-version

https://github.com/conventional-changelog/standard-version

バージョン管理、変更ログの生成、および Git タグ付けを処理してくれます。コミットメッセージの分解は同じです。

大きく違うのはローカルで動かす前提で、リモート反映はしないこと。またnpm のパッケージ公開までは対応してません。

## コミットメッセージとバージョン

### commit メッセージ

コミットメッセージは[Angular のコミットメッセージフォーマット](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-format)に従います。

これによりコミットメッセージを解析して、自動でバージョンが決まりCHANGELOG.mdが更新されます。

```
<種別>: <具体的な変更内容>

例
fix: 日本語の副詞「と」のルールを修正
```

### バージョン

デフォルトのcommit時の種別とバージョンの対応は下記です。設定を変更すれば、オリジナルの種別を追加できます。

| 種別            | 内容                       | マージリリース | バージョン    |
| --------------- | -------------------------- | -------------- | ------------- |
| feat            | 新しい機能                 | 〇             | Miner Release |
| fix             | バグ修正&軽微な修正        | 〇             | Patch Release |
| perf            | コードのパフォーマンス修正 | 〇             | Patch Release |
| BREAKING CHANGE | 互換性のない破壊的変更     | 〇             | Major Release |
| etc..           |                            |                |               |

### コミッター全員がコミットメッセージ綺麗するのが必須？

GitHubにはマージ方法が 3 種類あります。

- Create a merge commit
  - フィーチャブランチからのすべてのコミットがマージコミット内でベースブランチに追加されます。
- Rebase and merge
  - トピックブランチ（あるいはheadブランチ）のすべてのコミットが、マージコミットなしに個別にベースブランチに追加されます。
- Squash and merge
  - そのプルリクエストのコミットは１つのコミットにsquashされます。

`Squash and merge`を使い、マージする際にメンテナーが Squash して commit メッセージを加工するのが良いでしょう。そうすれば、全てのコミッターがコミットメッセージを完璧にする必要はないです。

万が一ルールに沿っていなくても、無視されるので影響はありません。


## semantic-releaseの設定

`package.json`に使うプラグインとブランチを指定します。

今回は、commit解析によるCHAGELOG.md&リリースノートの作成、GitHubを使ったGitでのタグうちと差分のcommitまで行います。


```package.json
  "release": {
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      "@semantic-release/changelog",
      "@semantic-release/npm",
      "@semantic-release/github",
      "@semantic-release/git"
    ],
    "branches": [
      "main"
    ]
  }
```

処理のイメージは下記のようになります。

|ステップ |説明 |
| -- | -- |
|条件の確認 |すべての条件を確認して、条件があっているか確認します。|
|最後のリリースを取得 |分析して、最後のリリースに対応するコミットを取得します。|
|コミットの分析 | 前回のリリース以降に追加されたコミットに基づいて、リリースのタイプを決定します。|
|リリースを確認する | リリースの適合性を確認します。|
|メモを生成する | 前回のリリース以降に追加されたコミットのリリースノートを生成します。|
|Gitタグを作成する | 新しいリリースバージョンに対応するGitタグを作成します。|
|準備 |リリースを準備します。|
|公開|リリースを公開します。|
|通知する |新しいリリースまたはエラーを通知します。|

## GitHub Action Workflowで自動化

npmのリリースのために`NPM_TOKEN`にnpmのアクセストークンをGitHubのレポジトリの`SECRETS`に設定しておきます。GitHubの操作も行いますが、自動で`GITHUB_TOKEN`という名前のGitHubアクセストークンが設定されているので対応不要です。

https://docs.github.com/ja/actions/security-guides/automatic-token-authentication#about-the-github_token-secret

![スクリーンショット 2021-12-16 19.29.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525723/3aa82c9a-c819-3aa6-cd81-0a747f698881.png)

GitHub Actions のワークフローは、Test と Release のふたつを想定します。

こちらは、Release用のワークフローです。起動条件にTestの成功を指定し、テストが通った時だけ起動します。


```yml
name: Release
on:
  workflow_run:
    workflows:
      - Test
    branches:
      - main
    types:
      - completed

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}

    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Setup NodeJS 14
        uses: actions/setup-node@v2
        with:
          node-version: 14

      - name: Install dependencies
        run: npm ci

      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release
```

## リリースの流れ

プルリクエストのマージからまとめるとこうなります。

1. プルリクエストをマージ
1. Test ワークフロー
   1. テスト実行
1. Release ワークフロー
   1. Test が無事に終わっていたらトリガー
   1. Git タグを解析して、最後のリリースに該当するコミットを取得
   1. 前回のリリース以降に追加されたコミットをもとに、リリースの種類を決定
   1. リリースの適合性を確認
   1. 前回のリリース以降に追加されたコミットのリリースノートを作成
   1. 新しいリリースバージョンに対応する Git タグを作成
   1. npm package リリース :tada:
   1. 新しいリリースやエラーを通知

# このフローで公開しているtextlintルールパッケージ

実際に上記のワークフローを使いtextlintのルールをnpmで公開しました。

https://github.com/bengo4/textlint-rule-preset-bengo4

弁コムサービス内の文字校正のレビューコストが高くなってました。ルールの数が増えると、レビューに時間がかかります。レビュアーによっても指摘内容が異なり、レビューの難易度もあがります。

そのためMarkdown などのテキストファイルを特定のルールにしたがってチェックする[textlint](https://github.com/textlint/textlint)を導入しました。Slackをインターフェイスにして、裏側でtextlintの上記のルールを使ったAPIが動いてます。

一緒に作ったデザイナーさんの記事もあるので、ぜひ見てください。:clap:

https://note.com/shellme/n/nbfef26660270

# まとめ

GitHub Actions & semantic-version での自動リリースは非常に便利なので、楽に高品質なパッケージを公開しましょう。

使う人も作る人にも優しいOSSの提供を目指します。