🍊

changesetsを使ってWebサイトのバージョン管理を自動化する

2024/10/06に公開

はじめに

Web サイトのバージョン管理にchangesetsを導入してみたところ、めちゃめちゃ便利でした。ただ、ドキュメントがわかりにくかったので、使い方やつまった点など書いていこうと思います。
changesetsにはnpmへの公開機能などもあり、Chakra UIAstroEmotionなど名だたるパッケージのバージョン管理に使用されています。ちなみにYamada UIでも使っています。今回はnpmへの公開機能ではなく、Webサイトなどのバージョン管理をすることに焦点を当てたいと思います。

よく比較されるツールでsemantic-releaseというものもあるみたいです。こちらはコミットごとにバージョンが上がっていくようで、自分は適当にコミットすることも多いため、より柔軟にバージョンの操作ができるchangesetsのほうを使っています。

changesetsとは

changesetsセマンティックバージョニングに基づいたバージョン管理を効率化できるツールです。好きなタイミングで変更のログを書き込むことができ、リリースノートの作成なども GitHub Action 設定すれば自動でやってくれるのでめっちゃ便利です。

ざっくり、以下のような機能があります。

  • 変更ログの管理

    • どのレベル(Major, Minor, Patch)の変更にするかを都度選択可能
    • コミットや PR との紐づけもしてくれる
  • リリースノートの自動作成

    • コマンドでの作成も可能
    • GitHub Action もあり、リリースの PR を自動でつくってくれる
    • PR、コミット、ユーザーの紐づけもやってくれる

リリースノートはこんな感じになります。

https://github.com/108yen/changeset-zenn/blob/main/CHANGELOG.md

PRはこんな感じで作ってくれます。

https://github.com/108yen/changeset-zenn/pull/2
pr sample

準備

公式のドキュメントはこちらになります。

intro-to-using-changesets.md

インストールと初期化

cli ツールは@changesets/cliに入っているので、これをインストールします。
公式ドキュメントだとそのままインストールしていますが、devDependenciesの方に入れればいいと思います。

npm install --save-dev @changesets/cli

cli ツールを入れたら、まず初期化します。
これにより、ルートフォルダに.changesetフォルダが作成されます。

npx changeset init

package.jsonの設定

versioning-apps.md

changesetsでバージョン管理するためにpackage.json内に最低限、以下の3項目が含まれる必要があります。逆に、この3項目が含まれるpackage.jsonさえ作成すれば、どんなプロジェクトでもバージョン管理可能なようです。

{
  "name": "package",
  "version": "1.0.0",
  "private": true
}
  • name: パッケージ名を指定します。
  • version: changesetsversionの値を元にバージョンをあげていきます。o.o.oから始めたい場合は、ここを書き換えるとそこからバージョンを上げれます。
  • private: privatefalseの場合、npmなどに公開するためのスクリプトが走ります。今回は公開しないので、trueにします。

config.jsonの設定

npx changeset initを実行すると、.changeset/config.jsonが作成されます。
プログラムをnpmなどに公開しない場合は、以下の設定を追加する必要があります。

.changeset/config.json
{
  ...
  "privatePackages": {
    "version": true,
    "tag": true
  }
}

使い方

changesetsのコマンドで、変更の記録が記載されたファイルを生成することができ、そのファイルを含めてコミットすることで、コミットおよびそれが含まれるPRと合わせて管理できます。
記録された変更を元に、CHANGELOG.mdファイルにリリースノートを吐き出すことが可能です。

変更を記録する

変更を書き込む場合は、以下のコマンドを実行します。

npx changeset

実行すると、以下のようにどのレベルのバージョン変更をしたのか聞かれます。

🦋  What kind of change is this for changeset-zenn? (current version is 0.1.0) …
❯ patch
  minor
  major

選択すると、変更内容を聞かれるので、入力します。

🦋  What kind of change is this for changeset-zenn? (current version is 0.1.0) · patch
🦋  Please enter a summary for this change (this will be in the changelogs).
🦋    (submit empty line to open external editor)
🦋  Summary ›

入力してEnterを押下すると、これでいいですか?と聞かれるので再度Enterを押下して、変更の記録が記載されたファイルの生成は完了になります。

🦋  === Summary of changesets ===
🦋  patch:  changeset-zenn
🦋
🦋  Is this your desired changeset? (Y/n)true

完了すると、.changesetフォルダ配下に先ほどの変更が記録されたmdファイルが作成されます。変更の記録を追加するたびに、このファイルは増えていきます。
mdファイルなので、バージョン変更のレベルの変更や、変更内容の文章を更新したい場合は直接変更可能です。
このファイルをコミットに含め、変更の記録は完了になります。

changeset folder

CHANGELOG.mdの作成

リリースのタイミングで、溜まった変更の記録を元にCHANGELOG.mdファイルを生成します。
以下のコマンドを実行します。

npx changeset version

実行すると、今まで溜まっていた.changeset配下のmdファイルが削除され、CHANGELOG.mdファイルが更新されます。Minorの変更とPatchの変更二つ実施した場合は以下のようになります。バージョンは0.1.0 → 0.2.0に変更になりました。package.json内のversionの項目も一緒に変更されます。

CHANGELOG.md
# changeset-zenn

## 0.2.0

### Minor Changes

- 8ccbd1d: Test changelog3.

### Patch Changes

- 0903189: Test changelog2.
- 52036f3: Test changelog.

GitHub Action

changesetsが、Github Actionも用意してくれており、これがすごく便利なのでchangesetsを使うのであれば、絶対使うべきです。
npx changeset versionを実行し、その変更を含めコミットした上で、PRの作成まで自動でしてくれます。追加の変更がpushされると、それも取り込んで既存のPRを上書きしてくれます。

設定方法

以下のようなワークフローでmainブランチにプッシュされるごとに、変更が記録されたmdファイルがあるかチェックし、あればそれをPRに反映してくれます。

.github/workflows/release.yml
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release

    permissions:
      pull-requests: write
      contents: write

    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Dependencies
        run: npm install

      - name: Create Release Pull Request
        uses: changesets/action@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

こういう感じのPRを作成してくれます。
リリースの時はこのPRをマージするだけで良くなり、手動でnpx changeset versionを実行する必要はなくなります。

https://github.com/108yen/changeset-zenn/pull/1

PRを紐づける

先ほどのPRで作成されたものは、どのコミットに紐づいているかは記載されていますが、どのPRか、誰がやったかは含まれていません。

no pr contain

リリースノートの文章を変更するには、.changeset/config.jsonchangelogの設定でカスタマイズできます。(詳細はリリースノートをカスタマイズする
PRを紐づけれるものはすでに提供されているのでそれを利用します。

https://github.com/changesets/changesets/blob/main/docs/config-file-options.md#changelog-false-or-a-path

@changesets/changelog-githubをインストールします。

npm install --save-dev @changesets/changelog-github

.changeset/config.jsonを変更して、リリースノートの生成に@changesets/changelog-githubを使用するように設定します。changelogの項目を以下のように変更してください。2つ目の項目のrepoには、自身のプロジェクトのリモートリポジトリを記載してください。

.changeset/config.json
{
  ...
-  "changelog": "@changesets/cli/changelog",
+  "changelog": [
+    "@changesets/changelog-github",
+    { "repo": "108yen/changeset-zenn" }
+  ],
  ...
}

これで設定は完了であとは同じように利用すれば、自動作成してくれるPRの内容に、変更が含まれるPRや変更を実施した人がリンクされます。

https://github.com/108yen/changeset-zenn/pull/2

pr with links

リリース時に他の処理(デブロイなど)を実行する

changesetsが作成したPRをマージする際に、デプロイ処理を走らせたい場合があると思います。
以下のように、別のワークフローを設定すれば可能です。

このワークフローは、mainをターゲットとしたPRがクローズされた時にトリガーされます。加えて、そのPRがマージされており、changeset-releaseで始まる名前のブランチからのPRであればデプロイジョブを実行するように条件文を設定しています。
changesetschangeset-release/[ターゲットブランチ]という名称でブランチを作成し、そこからリリースのPRを作成するので、これでリリースのPRがマージされた時のみに処理を実行することができます。

.github/workflows/deploy.yml
name: Deploy

on:
  pull_request_target:
    types:
      - closed
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  deploy:
    name: Deploy

    # changeset-release/**からのPRがマージされていれば実行する
    if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'changeset-release')

    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Deploy
        run: npm run deploy # デプロイ処理とか

changesets/action@v1の機能を使って、リリース用のPRの作成もデプロイも一つのワークフローでやる方法もあります。ただ、「デフォルトのブランチにプッシュされるときは必ず変更ログが記録されている」という前提があるので注意が必要です。

releaseワークフローでやるパターン

changesets/action@v1publishにスクリプトを渡すと、「変更ログが一つもない場合」、実行してくれます。そのため、「変更ログが一つもない状態」==デプロイ処理を走らせる必要があるのであればこの方法でよいと思います。デフォルトのブランチにプッシュするときは必ず変更ログを残すという運用が立てついている必要があります。

逆にそうでないのであれば、ワークフローを分けましょう。例えば、変更ログをCHANGELOG.mdに吐き出したあとは「変更ログが一つもない状態」になります。このまま変更ログを作成せずデフォルトのブランチにプッシュするとpublishに渡したスクリプトは実行されてしまいます。

.github/workflows/release.yml
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release

    permissions:
      pull-requests: write
      contents: write

    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Dependencies
        run: npm install

      - name: Create Release Pull Request
        uses: changesets/action@v1
        with:
          publish: npm run deploy # デプロイ処理とか
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

リリース用のブランチが別にある場合

Vercelのように設定したブランチにプッシュするとデプロイされる場合があります。changesetsが発行するPRのターゲットブランチは変更することが可能です。releaseブランチを作成して、それをデプロイ用のブランチにするのがおすすめです。

changesets/action@v1branchパラメーターにターゲットブランチを設定します。
これでデフォルトブランチ(main)からチェックアウトし、releaseブランチに向けたPRが作成されます。

.github/workflows/release.yml
name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release

    permissions:
      pull-requests: write
      contents: write

    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Dependencies
        run: npm install

      - name: Create Release Pull Request
        uses: changesets/action@v1
        with:
          branch: release # これを追加する
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

こんな感じになります。

https://github.com/108yen/changeset-zenn/pull/5

補足

リリースノートをカスタマイズする

modifying-changelog-format.md

PRを紐づけるでも変更した.changeset/config.jsonchangelogの項目でカスタマイズが可能です。.changesetフォルダ配下にchangelog-function.jsファイルを作成し、それをchangelogで読み込みます。

.changeset/config.json
  ...
-  "changelog": "@changesets/cli/changelog",
+  "changelog": "./changelog-function.js",
  ...

getReleaseLineの出力がリリースノートの1行になります。getDependencyReleaseLineはモノレポ構成になっているような場合に、依存関係が記載されるものです。(Webで使うときにはあまり気にする必要はないと思います。)

イメージ

getReleaseLineの戻り値として返した文字列が、赤枠で囲った1行分になります。

release line image

getDependencyReleaseLineの戻り値は、以下の赤枠で囲った部分になります。

dependency release line image

getReleaseLineの引数

引数は以下脳ようになっています。

type getReleaseLine(
    changeset: {
        // This is the string of the summary from the changeset markdown file
        summary: string
        // This is an array of information about what is going to be released. each is an object with name: the name of the package, and type, which is "major", "minor", or "patch"
        releases
        // the hash for the commit that introduced the changeset
        commit
    },
    // the type of the change this changeset refers to, as "major", "minor", or "patch"
    type
    // This needs to be explained - see @changesets/changelog-github's code for how this works
    changelogOpts
) => string
.changeset/changelog-function.js
async function getReleaseLine(changesets, type, options) {
  return "changelog"
}

async function getDependencyReleaseLine(changesets, dependenciesUpdated,options) {
    return 
}

const changelogFunctions = {
  getReleaseLine,
  getDependencyReleaseLine
}

module.exports = changelogFunctions

@changesets/changelog-githubも同じ構成になっています。

https://github.com/changesets/changesets/blob/main/packages/changelog-github/src/index.ts

値を渡す

optionsで受け取るパラメータを設定するには以下のようにします。これで、options.repoから108yen/changeset-zennが取れます。

.changeset/config.json
  ...
-  "changelog": "@changesets/cli/changelog",
+  "changelog": [
+    "@changesets/changelog-github",
+    { "repo": "108yen/changeset-zenn" }
+  ],
  ...

GitHub の release を作成する

GitHubの右側に表示されるやつです。

github release
https://github.com/108yen/changeset-zenn/releases/tag/v0.2.2

特に公開するわけではないので必須ではないのですが、自分は作成したかったので設定しています。changesetsではGitHubのリリースの作成機能が、npmの公開の機能と紐づいており、それ単体で動かせなかったため、専用のGitHub Actionを作ってみました。よければ使ってください。

.github/workflows/deploy.yml
name: Deploy

on:
  pull_request_target:
    types:
      - closed
    branches:
      - release

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  deploy:
    name: Deploy

    permissions:
      contents: write

    if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'changeset-release')
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Create GitHub Release
        uses: 108yen/changeset-release@v1
        with:
          target: release # デフォルトブランチ以外にリリースを作成する場合は、ターゲットブランチを設定してください。
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Deploy
        run: [デプロイ処理とか]

自分で管理したい場合は、以下のコードコピペして使ってください。
やっていることはそんなにむつかしくなく、package.jsonversionCHANGELOG.md該当のバージョンの内容を読みだして、それらをもとにGitHubのリリースを作成しています。

https://github.com/108yen/changeset-release/tree/main/src

web ページにバージョンを表示する

changesetspackage.jsonversionも変更してくれるので、ここの値を取り出せば良いです。find-packagesというのがあるので、使うと便利です。

npm install find-packages

NextjsのApp Routerであれば以下のようにして表示できます。

page.tsx
import { findPackages } from "find-packages";

export default function Home() {
  const packages = await findPackages("./");
  const {version} = packages[0].manifest;

  return <p>{version}</p>
}

tag を発行する

以下のコマンドを実行すると、git tagを作成してくれます。
pushはしてくれないので、GitHub Action上でタグを発行する場合はgit push --follow-tagsでtagをpushしてください。

npx changeset tag

まとめ

長くなりましたが、使いこなせるとバージョン管理をほとんど自動化できるので、ぜひ使ってみてください。monorepoの管理や、GitHub Actionの機能など紹介しきれなかった機能もあるので、興味があれば調べてみてください!

GitHubで編集を提案

Discussion