毎月や半年に一回といったように、リリースする時期(間隔)を決めて更新するタイプのパッケージがあります。 具体的には、次のtextlintのプリセットルールは1月と7月という形で半年に一回リリースしています。

なぜ、このようにリリースする時期を決めているかというと、これらのパッケージは他のパッケージに依存していて、他のパッケージの更新がそのままメジャーアップデートになりやすい性質があるためです。 そのため、依存を更新してリリースすると、頻繁にメジャーアップデートしないといけなくなります。

具体的には、Semantic Versioningに則っているので次のルールでバージョンを更新しています。

  • Patch リリース
    • 各ルールのバグ修正 (警告を減らす方向への修正)
    • ドキュメントの改善
    • 内部的な変更 (リファクタリングやテストの改善など)
    • リリース失敗時の再リリース
  • Minor リリース
    • 各ルールのバグ修正 (警告を増やす方向への修正)
    • 新オプションの追加
    • 既存ルールの非推奨化
  • Major リリース
    • プリセットへのルールの追加
    • プリセットからルールの削除
    • 既存のオプション値の変更

ルールの追加やルールのメジャーアップデートがあると、プリセットもメジャーアップデートが必要になります。 頻繁にメジャーアップデートすると、ユーザーがメジャーアップデートをするのが面倒になるので、ある程度の間隔にまとめてリリースするようにしています。

まとめてリリースすると、それまでの間に最新のルールを使えない問題があります。 その対応として@nextのdist-tag付きでインストールすると最新のルールも含んだパッケージをインストールできるようにしています。

npm install textlint-rule-preset-ja-technical-writing@next

このような、定期的にリリース + 最新を常に@nextのdist-tagでリリースといったパッケージを管理するために、Changesetsを利用しています。

Changesetsの特徴

Changesetsはmonorepoのパッケージのリリースツールとして知られていますが、個別のパッケージでも利用できます。 特徴として、PRごとの変更内容とその変更内容のsemverを記録して、それをまとまったものとしてリリースできるというものです。

マージのたびにリリースする場合は、変更内容は覚えているのでその場で書けます。 しかし、時間が空くと何を変更したのか忘れてしまうことがあります。 そのため、変更(PR)ごとの変更内容を記録しておくことで、リリース時はただリリースするだけで良くなります。

Changesetsでは、変更内容のリリースノートだけではなく、その変更がpatch/minor/majorのどれになるかを記録しています。 これによって、リリースするときは記録から自動的に次のバージョンを決めてくれます。

Changesetsの導入

ドキュメントや次の記事と基本的に同じになるので、そちらを参照してください。

基本的な導入の流れを書くと次のようになります。

  1. changesetsをインストールする
  2. changeset-botをリポジトリにインストールする
  3. Changesets Release Actionを設定する

changesetsは最初に設定だけして、その後はあまり意識しないです。

changeset-botをインストール後に、PRを出すとBotがChangesets(変更内容)を記録するかを尋ねてきます。 バージョンに影響があるものなら、Botの指示に従ってChangesetsを記録していくだけです。

このChangesetsは次のようなMarkdownになっていて、Frontmatterにsemverを記録して、あとは変更内容を書くだけです。 Botの指示に従ってやれば、ブラウザ上で編集できるのでわざわざエディタを開く必要もありません。

---
"textlint-rule-preset-ja-technical-writing": major
---

:sparkles: [ルール名](https://example.com)をアップデート

変更点の解説

パッケージのリリースは、Changesets Release Actionを使ってCIから行うように設定できます。 CIからnpmにリリースするにはNPMのtokenが必要ですが、次のような感じで設定ファイルを一つ書くだけです。


name: Release

on:
  push:
    branches:
      - master

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v3
        with:
          # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Install Dependencies
        run: yarn install
      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          publish: yarn run release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.SHARED_BOT_NPM_TOKEN }}
{}

ここまで設定できると、PRをマージするたびにそこまでの変更履歴をまとめたVersion PackagesというPRを作ってくれます。 このPRには、次のバージョンに入る変更がまとまっているので、そのままマージするとリリースされます。

リリースもブラウザ上でできるので、PRを出した時にちゃんと変更内容を書いていれば、リリースは簡単です。

Canary Release

先ほどまでの設定で、まとめてリリースはできるようになります。 半年に一度のリリースなので、リリースするまで最新のバージョンが利用できないのは不便です。

changesetsにはSnapshot Releasesという仕組みがあり、いわゆるCanary Releaseが可能です。

この設定も簡単で、次のような感じで設定ファイルを一つ書くだけです。

{% raw %}
name: "Snapshot Release@next"

on:
  push:
    branches:
      - master

permissions:
  contents: read

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          registry-url: 'https://registry.npmjs.org' # to create .npmrc file with NODE_AUTH_TOKEN
          node-version: 18
      - name: Install Dependencies
        run: yarn install
      - name: "Release @next"
        run: |
          yarn changeset version --snapshot next
          yarn changeset publish --no-git-tag --snapshot --tag next
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.SHARED_BOT_NPM_TOKEN }}

これで、PRをマージするたびに0.0.0-next-20230716010722のようなバージョンでnpmへリリースできます。 npmのdist-tagという仕組みを使い、通常のnpm installした時に参照される@latestではなく、適当な@nextのようなタグをつけてパッケージを公開できます。

# @latestのdist-tagがインストールされる
npm install textlint-rule-preset-ja-technical-writing
# 次と意味は同じ
npm install textlint-rule-preset-ja-technical-writing@latest
# @nextのdist-tagがインストールされる
npm install textlint-rule-preset-ja-technical-writing@next

これによって、通常のユーザーは@latestをインストールしますが、試したい人だけ@nextで最新のバージョンをインストールできます。

定期的なリリースのタイミングをGitHub Actionsで通知する

半年に一回リリースしますが、リリース自体は手動でVersion PackagesのPRをマージする必要があります。 このタイミングを忘れてしまう問題もあるので、次のようなGitHub Actionsを半年に一回リリース用のIssueを作成して通知しています。


name: Create ReleaseIssue
on:
  schedule:
    # 1月/7月にリリース
    - cron:  "0 0 1 */6 *"

jobs:
  create_issue:
    name: Create ReleaseIssue
    runs-on: ubuntu-latest
    permissions:
      issues: write
    steps:
      - name: Create team sync issue
        uses: imjohnbo/[email protected]
        with:
          assignees: "azu"
          labels: "Type: Release"
          title: "Next Release"
          body: |
            ### 次のリリースの準備ができました

            - [ ] マージ忘れのPRがないかを確認
            - [ ] Version PackagesのPRをマージ
            - [ ] 新しいバージョンがリリースされたことを確認

          pinned: false
          close-previous: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

おわりに

Changesetsはリリースまでの間隔をあけるようなプロジェクトにはとても便利です。

これらの仕組みを使って、それぞれのパッケージのメジャーアップデートをリリースしています。

自分が管理する大部分のパッケージは、マージのたびにリリースしています。 この場合のパッケージのリリースには、GitHub Releasesを使った方法を利用しています。 詳細は次の記事を参照してください。