モノレポで複数パッケージを管理していると、依存関係のあるパッケージを正しい順序で公開するのが面倒になります。Changesets を使えば、この問題を解決できます。
例えば、以下のような依存関係があるとします。
@example/cli → @example/core
@example/react-components → @example/core@example/core に破壊的変更を加えた場合、
core のバージョンを上げるcli と react-components の依存バージョンを更新するcli と react-components のバージョンも上げるcore → cli → react-components)で公開する手動でやると、どこかで必ずミスします。
Changesets は、モノレポのバージョン管理とリリースに特化したツールです。
主な機能:
pnpm add -DEw @changesets/clipnpm changeset init.changeset/config.json が作成されます。
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}アルファ版開発向けの推奨設定は以下のようにします。
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@example/sandbox-*"],
"privatePackages": {
"version": false,
"tag": false
}
}| 設定 | 説明 |
|---|---|
access: "public" | scoped パッケージを public で公開 |
updateInternalDependencies | 依存パッケージの更新方法(patch / minor / major) |
ignore | バージョン管理対象外のパッケージ |
privatePackages | private パッケージの扱い |
この記事の GitHub Actions の設定の場合に行う日常のワークフローです。
機能追加やバグ修正をしたら、コミット前に changeset を作成します。
pnpm changeset対話形式で以下を入力します。
.changeset/ 以下に markdown ファイルが生成されます。
---
'@example/core': minor
'@example/cli': patch
---
Added new validation API to core packageTip
changeset ファイルはコードと一緒にコミットします。
changeset ファイルと変更したコードを一緒にコミットします。
git add .
git commit -m "feat: add new validation API"
git pushリリース準備ができたら、次のコマンドを実行します。
pnpm changeset versionこのコマンドは
package.json のバージョンを更新CHANGELOG.md を生成・更新git add .
git commit -m "chore: release"
git pushコミット & プッシュ で自動的に npm に公開されます。
# 1. コードを変更
# 2. 変更を記録
pnpm changeset
# 3. 変更をコミット & プッシュ
git add .
git commit -m "chore: release"
git push
# 4. バージョン更新(CHANGELOG も生成される)
pnpm changeset version
# 5. リリースをコミット & プッシュ (
git add .
git commit -m "chore: release"
git pushChangesets の真価は CI/CD 連携で発揮されます。
main ブランチで直接作業し、push したら自動で公開されるシンプルなフローです。
ワークフローを2つのジョブに分けて、未公開パッケージがあるときだけビルド・公開を実行します。
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
check:
name: Check for unpublished packages
runs-on: ubuntu-latest
outputs:
has_unpublished: ${{ steps.check.outputs.has_unpublished }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'
- name: Check unpublished packages
id: check
run: |
HAS_UNPUBLISHED=false
for dir in packages/*/; do
if [ -f "$dir/package.json" ]; then
PKG_NAME=$(jq -r .name "$dir/package.json")
PKG_VERSION=$(jq -r .version "$dir/package.json")
# Skip sandbox packages
if [[ "$PKG_NAME" == *"sandbox"* ]]; then
continue
fi
if ! npm view "$PKG_NAME@$PKG_VERSION" version 2>/dev/null; then
echo "Unpublished: $PKG_NAME@$PKG_VERSION"
HAS_UNPUBLISHED=true
fi
fi
done
echo "has_unpublished=$HAS_UNPUBLISHED" >> $GITHUB_OUTPUT
release:
name: Release
needs: check
if: needs.check.outputs.has_unpublished == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build packages
run: pnpm build:packages
- name: Publish to npm
id: changesets
uses: changesets/action@v1
with:
publish: pnpm run release
createGithubReleases: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Summary
if: steps.changesets.outputs.published == 'true'
run: |
echo "## Published successfully! :rocket:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Packages" >> $GITHUB_STEP_SUMMARY
echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r '.[] | "- **\(.name)@\(.version)** - [npm](https://www.npmjs.com/package/\(.name))"' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Tag:** alpha" >> $GITHUB_STEP_SUMMARY
echo "- **Auth:** Trusted Publishing (OIDC)" >> $GITHUB_STEP_SUMMARYTip
check ジョブで未公開パッケージの有無を先にチェックすることで、通常のコミットでは重いビルド処理をスキップできます。
package.json にリリーススクリプトを追加します。
{
"scripts": {
"release": "./scripts/release.sh"
}
}スクリプトファイルを作成します。
#!/bin/bash
set -euo pipefail
# Publish packages that have not been published yet
# Used by changesets/action in GitHub Actions
ROOT_DIR=$(pwd)
PACKAGES=$(pnpm -r --filter '@example/*' --filter '!@example/sandbox-*' exec pwd)
PUBLISHED=""
for dir in $PACKAGES; do
cd "$dir"
PKG_NAME=$(jq -r .name package.json)
PKG_VERSION=$(jq -r .version package.json)
if npm view "$PKG_NAME@$PKG_VERSION" version 2>/dev/null; then
echo "Skip: $PKG_NAME@$PKG_VERSION (already published)"
else
echo "Publish: $PKG_NAME@$PKG_VERSION"
pnpm pack
npm publish *.tgz --access=public --tag alpha --provenance
rm -f *.tgz
if [ -n "$PUBLISHED" ]; then
PUBLISHED="$PUBLISHED,$PKG_NAME@$PKG_VERSION"
else
PUBLISHED="$PKG_NAME@$PKG_VERSION"
fi
fi
done
cd "$ROOT_DIR"
echo ""
echo "Published packages: ${PUBLISHED:-none}"
# Create git tags for changesets/action to detect and create GitHub releases
if [ -n "$PUBLISHED" ]; then
pnpm changeset tag
fiNote
npm view で公開済みかどうかをチェックし、未公開のパッケージだけを publish しますpnpm changeset tag を実行することで、changesets/action が git タグと GitHub リリースを自動作成しますchangesets/action の publishedPackages 出力を使ってサマリー表示に利用できます正式リリース前のアルファ版は、別のフローで管理するのが一般的です。
pnpm changeset pre enter alphaこのモードでは、pnpm changeset version を実行すると:
1.0.0 → 1.0.1-alpha.01.0.1-alpha.0 → 1.0.1-alpha.1のようにアルファ版としてバージョンが上がります。
正式リリースの準備ができたら:
pnpm changeset pre exit次の version コマンドで正式バージョン(1.0.1)になります。
Changesets を導入することで:
最初の設定は少し手間ですが、パッケージが増えるほど効果を発揮します。