Changesets 自动化发布实战指南:从 CI 强制校验到 version/publish 全流程自动化
【免费下载链接】changesets🦋 A tool to manage versioning and changelogs with a focus on monorepos项目地址: https://gitcode.com/gh_mirrors/ch/changesets
Changesets 是一款面向 monorepo 的版本管理与 changelog 生成工具,它允许贡献者以“changeset 文件”的形式声明变更应如何发布,再由工具统一更新包版本、生成 changelog 并完成发布。本文围绕当前仓库中的 docs/automating-changesets.md 指南展开,系统讲解如何将这套原本手动的工作流自动化:包括"如何确保每个 Pull Request 都带有 changeset"(非阻塞提示与阻塞校验两种方案),以及"如何自动运行 version 与 publish 命令"(基于 GitHub Actions 的多种发布模式与纯手动流程兜底)。读完本文,你将掌握changeset status --since、changeset --empty等命令的正确用法,并能够为你的仓库配置出一套完整、安全、可落地的自动化发布流水线。
一、自动化的两个核心决策
虽然 changeset 的设计初衷是配合完全手动的工作流使用,但它也提供了帮助自动化的工具。整个自动化体系可以拆解为两个独立的决策问题:
- 如何确保 Pull Request 带有 changeset?——解决"贡献者忘记写 changeset"这一最常见的人为疏漏;
- 如何运行 version 和 publish 命令?——解决"版本更新与发布"这一重复且容易出错的手动环节。
两个问题彼此独立,可以单独实施:即使不强制校验 changeset,也可以让 GitHub Action 自动创建版本 PR;反之亦然。
二、确保 Pull Request 带有 changeset
Changesets 以文件形式提交到仓库,理论上细心的 reviewer 总能发现 changeset 缺失并要求补上。但作为人类,肉眼检查"某个文件不存在"是很容易遗漏的。因此官方建议引入某种机制来自动检测 PR 上 changeset 的存在与否,把这件事从"人肉检查"变成"机器检查",同时直接向 PR 作者高亮提示。实现方式分两种:非阻塞与阻塞。
2.1 非阻塞:缺失 changeset 不阻止合并
非阻塞方案下,PR 即使没有 changeset 也可以合并,缺失 changeset 不会让 CI 变红。官方推荐使用Changesets GitHub Bot(在仓库中安装该 GitHub App 即可):它会在 PR 上自动评论 changeset 是否存在,但不会阻止合并。一个额外便利是,Bot 还会给维护者提供"直接补充 changeset"的链接,方便维护者在合并 PR 时自行补上,而不必等待贡献者返回修改。
如果你更希望用 GitHub Action 自己写一套自定义的非阻塞检查,可以参照仓库中 site/guide/_snippets/automating-non-blocking.yaml 提供的模板:它使用pull_request_target事件(用于支持 fork 仓库的 PR),第一步仅读取代码并生成 PR 状态文本,第二步由pr-comment动作把评论写到 PR 上。
安全警告:不要运行不可信代码
pull_request_target事件默认开启写权限,如果执行了来自 fork 的不可信代码,且权限未收窄,将带来安全风险。上述模板中的做法是:只checkout 与 readfork 的代码,绝不executefork 中的任何代码。若你更倾向锁死权限、不处理 fork PR,可以改用pull_request事件,并加入如下 if 判断避免 Action 在 fork PR 中失败:jobs: pr-status: if: github.event.pull_request.head.repo.full_name == github.repository && !startsWith(github.head_ref, 'changeset-release/') # ...
2.2 阻塞:让 CI 在缺失 changeset 时失败
如果希望流程绝对一致——每个 PR 都必须带 changeset,否则 CI 失败——就在 CI 中加入一个步骤运行:
# 以 pnpm 为例(npm/yarn 对应下方代码组) changeset status --since=main各包管理器下的等价写法:
pnpm changeset status --since main # pnpm npx @changesets/cli status --since main # npm yarn changeset status --since main # yarn该命令的语义是:如果自main分支以来有包被改动、但没有新增 changeset,则退出码为 1(CI 失败);如果没有包被改动,则不会失败。
这一点可以直接从源码得到印证。在 packages/cli/src/commands/status/index.ts 中,status命令先通过getPackages、readConfig、readPreState、readChangesets收集仓库信息,再用assembleReleasePlan组装发布计划,并用getVersionableChangedPackages计算自since引用以来发生变更的可发布包;当changedPackages.length > 0 && releasePlan.changesets.length === 0时,它会打印错误提示并throw new ExitError(1)——这正是 CI 失败退出码 1 的来源。错误提示中会直接建议开发者运行changeset add或changeset add --empty。
对应的测试用例也验证了这些行为,见 packages/cli/src/commands/status/tests/status.test.ts:
- 有变更包但无 changeset 时抛出错误(第 175-204 行);
- 无变更包时不触发退出(第 206-235 行);
- 有变更包且同时存在 changeset 时不触发退出(第 237-276 行);
- 仅改动被
ignore或privatePackages.version = false排除的包时不触发退出(第 493-586 行); - 支持
changedFilePatterns配置(如["src/**"]),只有改动命中了这些模式且缺 changeset 才失败(第 351-491 行)。
2.3 例外情况:只改测试、构建工具等无需发版的内容
有时你确实想要合并一个不需要发版的变更(例如只改测试或构建工具)。此时可以运行:
changeset --empty这会添加一个特殊的空 changeset——它不产生任何版本更新,却能让阻塞式校验"有 changeset"这一关通过。需要特别指出的是,官方文档明确不推荐普遍采用阻塞方案,因为并非每个变更都需要发版;阻塞只是"偏好绝对一致流程"时的选项。
三、如何运行 version 与 publish 命令
3.1 推荐方案:Changesets GitHub Action
官方提供Changesets GitHub Action来承担版本与发布环节,其能力包括:
- 创建一个
versionPR,并在后续提交时持续更新它;该 PR 始终包含最新一次changeset version的运行结果; - 当变更合并到基础分支后,可选地执行真正的发布动作。
在 site/guide/automating.md 中给出了官方推荐的整体流程判断逻辑,可以用下面的流程图概括:
Has changesets? ──YES──▶ Version packages and create/update PR │ NO ▼ Are there any publishable packages? ──YES──▶ Publish packages │ NO ▼ Do nothing即:推送到基础分支后,先判断是否有 changeset;有则更新版本 PR,没有则继续判断是否存在可发布包,存在才发布。
3.2 前提条件:允许 GitHub Actions 创建并批准 PR
由于 Action 需要为版本更新创建 PR,请务必在仓库设置的Actions > General中开启"Allow GitHub Actions to create and approve pull requests"。若未开启,可能遇到如下报错:
remote: Permission to xxx.git denied to github-actions[bot]GitHub Actions is not permitted to create or approve pull requests
3.3 发布模式一:Trusted Publishing(推荐)
npm 官方推荐使用Trusted Publishing(或 Staged Publishing)从 CI 安全地发布包。当前阶段Staged Publishing 与 Changesets 不兼容,所以应选择 Trusted Publishing。
与 npm 官方工作流建议相反,务必让id-token: write只出现在真正需要发布的那个 job上,因此建议把构建、测试、发布拆分成独立 job。仓库中的 site/guide/_snippets/automating-trusted-publishing.yaml 提供了一个完整示例,其 job 结构为:
select-mode:读取仓库、安装依赖,调用changesets/action/select-mode@v2判断本次推送是进入version模式还是publish模式;version(mode == 'version'时):需要contents: write与pull-requests: write权限,调用changesets/action/version@v2生成并更新版本 PR;pack(mode == 'publish'时):构建并用changesets/action/pack@v2打包产物;publish:仅此 job声明id-token: write(Trusted Publishing 需要),调用changesets/action/publish@v2发布。
此外还可以考虑为publishjob 配置一个带"必需审阅者"的 GitHub environment,让发布 job 在继续前必须经过维护者审批——这是确保只有受信任维护者才能发布的一种方式。
3.4 发布模式二:Token-based Publishing(npm token)
官方已不再推荐token 方式(尤其不推荐 Granular Access Tokens),原因是其限制较多:token 最长 90 天过期、需要周期性人工轮换;2FA-bypass token 也正在被弃用,启用 2FA 后将无法直接用于发布。但如果你的目标是不支持 Trusted Publishing 的其他 npm 兼容 registry,仍可选择 token 方式。
你需要一个勾选了"Bypass two-factor authentication"的 npm token(避免 CI 中被 npm 要求二次验证),并以NPM_TOKEN为名加入仓库 Secrets。之后参照 site/guide/_snippets/automating-token-based-publishing.yaml 配置:在publishjob 中通过actions/setup-node的registry-url: https://registry.npmjs.org/配置认证,并在调用changesets/action/publish@v2时通过env.NODE_AUTH_TOKEN传入${{ secrets.NPM_TOKEN }}。
对于"贡献者可信的私有仓库",仓库还提供了一个简化版工作流 site/guide/_snippets/automating-token-based-publishing-simplified.yaml:单个 job 直接调用changesets/action@v2,通过publish-script: npx @changesets/cli publish指定发布命令,同样以NODE_AUTH_TOKEN传 token。
若要在 GitHub Package Registry 发布而非 npm,把registry-url改为https://npm.pkg.github.com/,并将NODE_AUTH_TOKEN传${{ secrets.GITHUB_TOKEN }}。
3.5 发布模式三:只创建 Git Tags,由其他工作流发布
如果发布动作由独立工作流承担(例如基于 git tag 创建事件触发),可以让 Changesets 只负责在发布时创建 git tag。这要求把包的package.json设为"private": true,并在配置中开启privatePackages(参见 site/guide/config.md 与 site/guide/beyond-npm.md 的相关说明)。工作流模板见 site/guide/_snippets/automating-publish-git-tags-only.yaml,结构与 Token 方式相似,但不设置registry-url与NODE_AUTH_TOKEN。
3.6 发布模式四:只做版本(Version Only)
如果完全不打算发布包,或只用 Changesets 管理 changelog,可以配置只执行版本的流水线。模板见 site/guide/_snippets/automating-version-only.yaml:推送到main后安装依赖、构建,再调用changesets/action@v2的 version 部分(该 job 需要contents: write与pull-requests: write权限)。此模式下同样需要把NODE_AUTH_TOKEN传给 Action(官方模板如此,即使不发布,也保持一致配置以避免失败)。
3.7 兜底方案:手动执行 version 与 publish
如果你不想引入 GitHub Action,官方文档 docs/automating-changesets.md 给出了手动运行version与publish的推荐流程,由一名发布协调人(RC,Release Coordinator)执行:
- RC 通知暂停向基础分支合并;
- RC 拉取基础分支,运行
changeset version,将版本变更提交为一个新 PR; - 将版本 PR 合并回基础分支;
- RC 再次拉取基础分支,运行
changeset publish; - RC 运行
git push --follow-tags推送发布 tag; - RC 解除基础分支的合并限制。
这套流程步骤较多且比较繁琐(需要从基础分支拉取两次),官方也承认这一点,建议你根据自身情况灵活调整。这也正说明:用 GitHub Action 接管这两个环节能显著降低人为出错率。
四、从源码理解 version 与 publish 的底层行为
changeset version命令的核心实现在 packages/cli/src/commands/version/index.ts,阅读它可以更准确地理解自动化工作流中 Action 做了什么:
- 先读取配置与 changeset,通过
assembleReleasePlan组装发布计划(会综合 pre 模式状态、ignore、snapshot等配置); - 若处于 pre(预发布)模式但请求了 snapshot 发布,或没有未发布的 changeset(且不在
pre exit状态),命令会报错退出——这正是 CI 里"无 changeset 就不应触发 version"的底层保证; - 随后
applyReleasePlan会实际改写 package.json 版本号与 CHANGELOG.md; - 若配置了
commit,它会把改动文件逐个git add并提交;否则只改文件、留待人工提交(日志提示 "All files have been updated. Review them and commit at your leisure")。
配置了commit时,工作流中还可以通过 Changesets 的commit配置项(含skipCI选项)自定义提交信息与消息格式。相应地,changeset publish会基于 version 产生的版本信息更新包并推送 git tag——自动化场景下,Action 正是分别调用这两个命令完成"版本 PR"与"实际发布"的。
五、自动化流水线的额外注意事项
5.1 理解 npm 认证原理
当使用actions/setup-node并设置registry-url时,它内部会生成一个类似下面的.npmrc:
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}这种写法只在NODE_AUTH_TOKEN环境变量存在时才向 npm registry 认证,比把 token 直接写死在.npmrc中更安全。进阶场景下也可以手写~/.npmrc(注意此时要移除actions/setup-node的registry-url以避免冲突)。例如需要把不同 scope 发布到不同 registry 时:
- run: | cat << 'EOF' > ~/.npmrc # 无 scope 的包发布到默认 npm registry //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} # @foo/* 发布到自定义 registry @foo:registry=https://my-registry.com/ //my-registry.com/:_authToken=${NODE_FOO_AUTH_TOKEN} # @bar/* 发布到 GitHub Package Registry @bar:registry=https://npm.pkg.github.com/ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} EOF5.2 让工作流在版本 PR 上自动运行
GitHub Actions 创建的 PR 默认不会触发针对 PR 的工作流。要让版本 PR 上的 CI 自动运行,需要把个人 token 或 GitHub App token 传给 Changesets GitHub Action。以 GitHub App 为例(需要配置APP_CLIENT_ID变量与APP_PRIVATE_KEY密钥):
jobs: version: runs-on: ubuntu-latest permissions: contents: read # 用于 actions/checkout steps: # ... - name: Create GitHub App Token uses: actions/create-github-app-token@v3 id: app-token with: client-id: ${{ vars.APP_CLIENT_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} permission-contents: write # 提交版本变更 (changesets/action/version) permission-pull-requests: write # 创建 PR (changesets/action/version) - name: Version packages uses: changesets/action/version@v2 with: github-token: ${{ steps.app-token.outputs.token }}同时注意:版本提交与合并提交中不要包含[skip ci](或其任何变体),否则会跳过工作流运行,导致测试或发布步骤不执行。这在使用commit-message或 Changesetscommit配置的skipCI选项时需要格外小心。
5.3 包管理器各自的认证配置差异
- pnpm:出于安全原因,项目目录下的
.npmrc不支持环境变量,应改在用户主目录配置(其他包管理器同样建议如此,避免与项目内既有配置混杂)。 - yarn:yarn 不支持
.npmrc,需改用~/.yarnrc.yml:
- run: | cat << 'EOF' > ~/.yarnrc.yml npmAuthToken: "${NODE_AUTH_TOKEN}" EOF多 registry 场景可扩展为npmScopes配置,例如将fooscope 指向https://my-registry.com/、barscope 指向 GitHub Package Registry,并分别使用不同的认证 token。
六、自动化方案选型速查
| 需求场景 | 推荐做法 | 关键命令 / 动作 |
|---|---|---|
| 提示 PR 作者补 changeset(不阻塞) | Changesets GitHub Bot,或自定义非阻塞 Action(见 site/guide/_snippets/automating-non-blocking.yaml) | Bot 自动评论 |
| 强制每个 PR 都必须有 changeset | CI 中运行 status 校验 | changeset status --since=main(退出码 1 即失败) |
| 无需发版的变更绕过校验 | 添加空 changeset | changeset --empty |
| 自动版本 + 发布(npm 推荐) | Trusted Publishing 工作流(见 site/guide/_snippets/automating-trusted-publishing.yaml) | changesets/action/version@v2+changesets/action/publish@v2,id-token: write仅限 publish job |
| 自动版本 + 发布(其他 registry) | Token 工作流(见 site/guide/_snippets/automating-token-based-publishing.yaml) | NPM_TOKENSecret +NODE_AUTH_TOKEN |
| 只创建 git tag,发布交给其他工作流 | Tags-only 工作流(见 site/guide/_snippets/automating-publish-git-tags-only.yaml) | 包设为 private 并开启privatePackages |
| 只管理版本/changelog,不发布 | Version-only 工作流(见 site/guide/_snippets/automating-version-only.yaml) | changesets/action@v2的 version 环节 |
| 不使用 Action | 手动 RC 流程(版本 PR → 合并 → publish → push tags) | changeset version/changeset publish/git push --follow-tags |
七、延伸阅读
- changeset 的创建、格式与语义:见 docs/adding-a-changeset.md;
status命令的更多选项(--verbose、--output、--since)与配置项:见 docs/command-line-options.md 与 docs/config-file-options.md;- 发布在 monorepo 中可能遇到的问题与解法:见 docs/problems-publishing-in-monorepos.md;
- 本文引用的工作流模板全部位于 site/guide/_snippets/,可直接复制到仓库
.github/workflows/下按需修改使用。
【免费下载链接】changesets🦋 A tool to manage versioning and changelogs with a focus on monorepos项目地址: https://gitcode.com/gh_mirrors/ch/changesets
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考