Automating Versioning and Publishing Workflows with the Changesets GitHub Action

The management of versioning in modern software development, particularly within multi-package repositories or monorepos, presents a significant operational challenge. Coordinating semantic versioning (semver) bumps across interdependent libraries while maintaining accurate changelogs often leads to human error or "versioning fatigue." The Changesets ecosystem, and specifically the Changesets GitHub Action, provides a programmatic solution to this problem. By shifting the responsibility of declaring a release from the release coordinator to the individual contributor, the system ensures that the intent to release is captured at the moment the code is written. This architectural shift transforms versioning from a centralized, high-risk event into a distributed, low-risk process integrated directly into the continuous integration and continuous deployment (CI/CD) pipeline.

At its core, a changeset is a formalized declaration of intent. It specifies which packages should be bumped, the type of semver increment required (patch, minor, or major), and a summary of the changes. The Changesets GitHub Action automates the lifecycle of these declarations, transforming them into actual version bumps in the source code and subsequent publishes to registries like npm. By utilizing this action, teams can eliminate the "manual dance" of pulling the base branch, running versioning commands, and pushing tags, replacing it with a streamlined, bot-driven workflow.

The Architectural Philosophy of Changesets

The fundamental goal of Changesets is to solve the complexities associated with multi-package repositories. In a monorepo, a change in one package often necessitates a version bump in another package that depends on it. Manually tracking these dependencies is prone to failure. The @changesets/cli package automates this by flattening bump types into a single release per package and handling internal dependency updates automatically.

The workflow operates on a "declare-first" basis. Contributors create a small Markdown file (the changeset) that describes the change. This file lives in the repository until the release process is triggered. The GitHub Action then monitors these files. When new changesets are detected on the configured base branch, the action creates a Version Pull Request. This PR contains the updated package.json versions and the generated changelogs. Once this PR is merged, the action can be configured to automatically publish the packages to the npm registry.

Implementation and Initial Configuration

Setting up the Changesets GitHub Action requires a combination of local dependency installation, GitHub repository permissions, and secret management.

Local Environment Setup

The first requirement is the installation of the CLI tool that manages the changesets locally.

  • Install the CLI: The tool is provided via the @changesets/cli package. Use npm install @changesets/cli to add it to the project.
  • Package.json Configuration: The project's package.json must be configured to handle the publishing process. A specific script, often named release, must be defined. For example, in the scripts section of package.json, a command like npm run release (or yarn release) is used. This script typically handles the build process for the packages before calling the changeset publish command.
  • Public Access: To ensure packages are discoverable, the publishConfig section in package.json should be set to {"access": "public"}.

GitHub Repository Permissions

The Changesets bot requires specific permissions to operate, as it must create and modify pull requests.

  • Workflow Permissions: Navigate to Settings -> Actions -> General. Under the "Workflow permissions" heading, the option "Allow GitHub Actions to create and approve pull requests" must be checked. Without this, the action will fail when attempting to open the versioning PR.
  • Bot Authorization: The changeset-bot must be authorized for the repository or organization. This is done via the GitHub App marketplace at https://github.com/apps/changeset-bot.

NPM Token Management and Security

Securely publishing to the npm registry requires an authentication token. The modern standard is to use "granular access tokens" rather than legacy global tokens to adhere to the principle of least privilege.

Generating the Granular Access Token

  1. Navigate to the npmjs profile image and select Access Tokens.
  2. Select "Generate New Token" and specifically choose the "Granular Access Token" option.
  3. Assign a descriptive name, such as $LIBRARY-changeset.
  4. Set the expiration period (up to 365 days).
  5. Configure "Packages and scopes" to "Read and write" and explicitly select the libraries that the token is permitted to update.

Integrating the Token with GitHub

Once the token is generated, it must be stored as a GitHub Secret to prevent it from being exposed in the source code or logs.

  • Navigate to Settings -> Secrets and Variables -> Actions.
  • Create a new secret named NPM_TOKEN and paste the granular token value.

Technical Analysis of the Release Workflow

The core of the automation is the release.yml workflow file. This file defines when the action runs and how it interacts with the repository.

Workflow Trigger and Concurrency

The workflow is typically triggered on a push to the main branch. To prevent race conditions where multiple releases overlap, a concurrency group is implemented:

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

This ensures that only one release job runs at a time for a specific branch, preventing duplicate version bumps or conflicting publishes.

The Release Job Configuration

The release job typically runs on ubuntu-latest and involves several critical steps:

  • Checkout: Uses actions/checkout@v3 to pull the source code.
  • Node Setup: Uses actions/setup-node@v3 with node-version: 20.x.
  • Dependency Installation: A yarn or npm install command is executed to ensure the @changesets/cli is available.
  • The Changesets Action: This is the primary step using changesets/action@v1.

Detailed Action Input Parameters

The changesets/action provides several configuration options to customize the behavior of the versioning and publishing process:

Parameter Description Default Value
publish The command used to build and publish packages. Not provided (required)
version The command to update versions, edit CHANGELOG, and delete changesets. changeset version
commit The commit message used for version changes. Version Packages
title The title of the generated Pull Request. Version Packages
setupGitUser Boolean to set the git user as github-actions[bot]. true
createGithubReleases Boolean to create GitHub releases after publishing. true
commitMode Specifies the push method: git-cli or github-api. git-cli
cwd Changes the working directory of the node process. process.cwd()
prDraft Controls draft behavior: create for new PRs or always for existing. N/A

Commit Mode Nuances

The commitMode parameter significantly affects how changes are attributed. When using git-cli, the action uses standard git commands. When using github-api, the action pushes changes via the GitHub API, resulting in GPG-signed commits attributed to the user or app owning the GITHUB_TOKEN.

Advanced Configuration and Customization

While the default behavior of the action is sufficient for most, certain scenarios require advanced overrides.

Manual .npmrc Configuration

By default, the action creates a .npmrc file with the content //registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}. However, if a .npmrc file already exists in the repository, the action will not overwrite it. If a custom configuration is needed, a step can be added before the action:

yaml - name: Creating .npmrc run: | cat << EOF > "$HOME/.npmrc" //registry.npmjs.org/:_authToken=$NPM_TOKEN EOF env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Using the hasChangesets Output

The action provides an output called hasChangesets. This allows developers to hook into the publishing process using their own custom scripts. For instance, a Slack notification can be triggered only if a publish actually occurred:

yaml - name: Send a Slack notification if a publish happens if: steps.changesets.outputs.published == 'true' run: my-slack-bot send-notification --message "A new version of ${GITHUB_REPOSITORY} was published!"

Automating Simple Pull Requests

Beyond the main release workflow, there is a secondary use case: automating the creation of changesets for automated tools like Renovate or Dependabot. The generates/[email protected] can be used to automatically add a changeset when a pull request is labeled.

Example Workflow for Automated Changesets

```yaml
name: Changeset
on:
pull_request:
types: [labeled]

jobs:
changeset:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Changeset
uses: generates/[email protected]
- name: Commit Changes
uses: generates/[email protected]
with:
token: ${{ secrets.GH_PAT }}
```

This workflow ensures that dependency updates (which are often trivial) do not stall the release pipeline due to a missing changeset file.

Manual Release Coordination as an Alternative

For teams that prefer not to use the GitHub Action, the Changesets team recommends a specific manual workflow to avoid the "finicky" nature of manual versioning.

  1. Release Coordinator (RC) stops all merging to the base branch.
  2. RC pulls the base branch.
  3. RC runs changeset version.
  4. RC creates a new PR with these versioning changes.
  5. Versioning changes are merged into the base branch.
  6. RC pulls the base branch again.
  7. RC runs changeset publish.
  8. RC runs git push --follow-tags to push release tags.
  9. RC unblocks merging.

The GitHub Action effectively replaces all these steps, transforming a multi-stage manual process into a single, automated trigger.

Handling Empty Changesets

In certain scenarios, a release might be desired even if no actual version bump is required (e.g., documenting a release that doesn't change code). In these cases, the command changeset --empty can be used. This generates a special changeset that allows the release process to proceed without triggering a semver increment.

Conclusion

The Changesets GitHub Action represents a shift toward "Intent-Based Versioning." By decoupling the declaration of a version bump from the execution of the release, it removes the bottleneck of a single release manager and distributes the responsibility across the entire contributing team. The integration of granular npm tokens and GitHub App permissions ensures that this automation remains secure. Whether used for managing complex monorepos via the changesets/action or automating dependency updates via the generates/changeset-action, the system provides a robust framework for maintaining software lifecycles at scale. The ability to automate the creation of version PRs, manage changelogs, and handle the publishing of multiple interdependent packages makes it an essential component of any professional JavaScript/TypeScript release pipeline.

Sources

  1. Changeset Action Marketplace
  2. Changesets Action Repository
  3. Automating Changesets Documentation
  4. Adding Changeset Guide
  5. Changesets Main Repository

Related Posts