iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🍊

Automating Website Versioning with changesets

に公開
2024/10/07

Introduction

I recently introduced changesets for website version management, and it was incredibly convenient. However, the documentation was a bit hard to follow, so I'd like to write about how to use it and some points where I got stuck.
changesets also includes features for publishing to npm and is used for version control by prominent packages like Chakra UI, Astro, and Emotion. By the way, we also use it in Yamada UI. This time, I want to focus on managing versions for things like websites rather than focusing on the npm publishing feature.

There is also a tool called semantic-release that is often compared. That one seems to bump the version with every commit. Since I often commit somewhat casually, I prefer using changesets, which allows for more flexible version management.

What is changesets?

changesets is a tool that can streamline version control based on semantic versioning. It's very convenient because you can record change logs at your preferred timing, and if you set up GitHub Actions, it will automatically handle things like creating release notes.

Broadly, it has the following features:

  • Change log management

    • You can choose the level of change (Major, Minor, Patch) each time.
    • It also handles linking with commits and PRs.
  • Automatic creation of release notes

    • Creation via command is also possible.
    • There is a GitHub Action that automatically creates release PRs.
    • It also handles linking PRs, commits, and users.

The release notes look something like this.

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

It creates PRs like this.

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

Preparation

The official documentation can be found here.

intro-to-using-changesets.md

Installation and Initialization

The CLI tool is in @changesets/cli, so we'll install that.
While the official documentation suggests installing it directly, I think it's better to add it to devDependencies.

npm install --save-dev @changesets/cli

After installing the CLI tool, first initialize it.
This will create a .changeset folder in the root directory.

npx changeset init

package.json Setup

versioning-apps.md

To manage versions with changesets, at minimum, the following three items must be included in package.json. Conversely, as long as you create a package.json that includes these three items, it seems any project can be version-managed.

{
  "name": "package",
  "version": "1.0.0",
  "private": true
}
  • name: Specifies the package name.
  • version: changesets bumps the version based on the version value. If you want to start from 0.0.0, changing this will let you bump the version from there.
  • private: If private is false, scripts for publishing to npm, etc., will run. This time we won't be publishing, so set it to true.

config.json Setup

Running npx changeset init creates .changeset/config.json.
If you are not publishing your program to npm, etc., you need to add the following settings.

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

Usage

You can use changesets commands to generate files that record changes. By including these files in your commits, you can manage your versioning along with the commits and their associated PRs.
Based on the recorded changes, you can output release notes to a CHANGELOG.md file.

Recording Changes

To record changes, run the following command:

npx changeset

When executed, you will be asked what level of version change you have made, as follows:

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

Once selected, you will be asked for a summary of the change, so enter it.

🦋  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 ›

After entering the summary and pressing Enter, you'll be asked if this is correct. Press Enter again, and the generation of the file recording the changes will be complete.

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

Upon completion, a markdown file containing the change record will be created under the .changeset folder. A new file is added each time you record a change.
Since it is a markdown file, you can directly modify the version level or update the content if you wish to change the summary.
Include this file in your commit to complete the recording of the changes.

changeset folder

Creating CHANGELOG.md

At the timing of a release, a CHANGELOG.md file is generated based on the accumulated change records.
Run the following command:

npx changeset version

When executed, the markdown files previously stored under .changeset are deleted, and the CHANGELOG.md file is updated. If you have made one minor change and two patch changes, it will look like the following. The version has changed from 0.1.0 to 0.2.0. The version field in package.json is also updated at the same time.

CHANGELOG.md
# changeset-zenn

## 0.2.0

### Minor Changes

- 8ccbd1d: Test changelog3.

### Patch Changes

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

GitHub Action

changesets also provides a GitHub Action, which is incredibly useful; if you use changesets, you should definitely use this as well.
It handles everything from running npx changeset version and committing those changes to automatically creating a PR. If additional changes are pushed, it incorporates them and updates the existing PR.

Setup Method

With the following workflow, every time a push is made to the main branch, it checks for markdown files recording changes and, if found, reflects them in a 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 }}

It will create a PR like this.
When releasing, you only need to merge this PR, eliminating the need to run npx changeset version manually.

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

Linking PRs

While the PR created earlier shows which commit the changes are linked to, it does not include information about which PR they originated from or who made the changes.

no pr contain

To change the text in the release notes, you can customize it using the changelog setting in .changeset/config.json. (For details, see Customizing Release Notes.)
Since a tool for linking PRs is already provided, we will use that.

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

Install @changesets/changelog-github.

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

Modify .changeset/config.json to configure the use of @changesets/changelog-github for generating release notes. Change the changelog entry as shown below. In the repo field of the second item, enter your project's remote repository.

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

With this setup complete, using it in the same way will now result in automatically created PRs that include links to the PRs containing the changes and the individuals who made them.

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

pr with links

Executing other processes (deployment, etc.) during release

You might want to run a deployment process when merging the PR created by changesets.
This can be done by setting up a separate workflow as follows.

This workflow is triggered when a PR targeting main is closed. Additionally, a conditional statement is set to execute the deployment job only if the PR has been merged and it comes from a branch starting with changeset-release.
changesets creates a branch named changeset-release/[target-branch] and creates a release PR from there, so this allows you to execute the process only when the release PR is merged.

.github/workflows/deploy.yml
name: Deploy

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

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

jobs:
  deploy:
    name: Deploy

    # Run if a PR from changeset-release/** is merged
    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 # Deployment process, etc.

There is also a way to use the functionality of changesets/action@v1 to handle both creating the release PR and deployment in a single workflow. However, please note that this assumes that "the change log is always recorded when pushing to the default branch."

Pattern using the release workflow

If you pass a script to the publish property of changesets/action@v1, it will be executed "when there are no change logs." Therefore, if "no change logs remaining" is equivalent to "the need to run the deployment process," this method works well. You must have an established operation where change logs are always left when pushing to the default branch.

Conversely, if that's not the case, you should separate the workflows. For example, after outputting change logs to CHANGELOG.md, the state becomes "no change logs remaining." If you push to the default branch in this state without creating a new change log, the script passed to publish will be executed.

.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 # Deployment process, etc.
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

When there is a separate branch for release

In cases like Vercel, deployment might occur when pushing to a specific branch. It is possible to change the target branch for PRs issued by changesets. It is recommended to create a release branch and use it as the branch for deployment.

Set the target branch in the branch parameter of changesets/action@v1.
With this, a PR targeting the release branch will be created from a branch checked out from the default branch (main).

.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 # Add this
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

It will look something like this.

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

Supplement

Customizing the Release Notes

modifying-changelog-format.md

As mentioned in Linking PRs, customization is possible via the changelog setting in .changeset/config.json. Create a changelog-function.js file under the .changeset folder and load it in the changelog setting.

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

The output of getReleaseLine becomes a single line in the release notes. getDependencyReleaseLine is used to describe dependencies in cases like monorepo configurations. (You don't need to worry about this much when using it for web.)

Visuals

The string returned as the value of getReleaseLine corresponds to the single line surrounded by the red box below.

release line image

The return value of getDependencyReleaseLine corresponds to the part surrounded by the red box below.

dependency release line image

Arguments for getReleaseLine

The arguments are as follows:

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 uses the same structure.

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

Passing Values

To set parameters to be received in options, do the following. This allows 108yen/changeset-zenn to be retrieved from options.repo.

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

Creating a GitHub release

It's the one displayed on the right side of GitHub.

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

It's not strictly necessary since I'm not publishing it as a package, but I set it up because I wanted to create them for myself. In changesets, the GitHub release creation feature is tied to the npm publishing feature and couldn't be run on its own, so I created a dedicated GitHub Action for it. Please feel free to use it if you'd like.

.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 # Set the target branch if you are creating a release for something other than the default branch.
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Deploy
        run: [Deployment process, etc.]

If you want to manage it yourself, feel free to copy and paste the code below.
What it does isn't that complicated: it reads the version from package.json and the content of the corresponding version from CHANGELOG.md, then creates a GitHub release based on that info.

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

Displaying the version on a web page

changesets also updates the version in package.json, so you can simply extract this value. Using find-packages is convenient for this.

npm install find-packages

In the case of Next.js App Router, you can display it as follows:

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

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

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

Issuing a tag

Executing the following command creates a git tag. It does not perform a push, so if you want to issue a tag on GitHub Actions, please push it using git push --follow-tags.

npx changeset tag

Conclusion

It has been a long post, but once you get the hang of it, you can automate most of your version management, so please give it a try. There are other features I couldn't introduce, such as monorepo management and GitHub Action functionalities, so please look into them if you're interested!

GitHubで編集を提案

Discussion