Polaris 多包仓库的 Changesets 变更日志与版本发布指南
Polaris 多包仓库的 Changesets 变更日志与版本发布指南
导读
本文以 Polaris 设计系统仓库根目录下 .changeset/README.md 为骨架,完整讲解这个由 pnpm workspace + Turborepo 组织、横跨 @shopify/polaris-react、@shopify/polaris-tokens、@shopify/polaris-icons、@shopify/polaris-migrator、@shopify/stylelint-polaris 等多个 npm 包的多包仓库,如何使用 Changesets 统一管理 CHANGELOG 与版本发布。读者学完后,将能在自己的 feature 分支上熟练运行 pnpm changeset,正确回答 CLI 的三个版本决策问题(包含哪些包、哪些 major、哪些 minor),并理解 .changeset/config.json 的配置语义、changeset 文件的 front matter 结构,以及它们如何与 changeset version、changeset publish 和 Turbo 构建流水线协同工作,最终在 CI 中自动生成 "Version Packages" PR 并发布到 npm。
一、为什么用 Changesets:多包仓库的统一版本控制
1.1 仓库背景:一个仓库、多个 npm 包
当前仓库以 pnpm workspace 管理多个独立发布的包。根据 pnpm-workspace.yaml 的 packages 列表,共包含以下子包:
| 子包目录 | npm 包名 | 用途 |
|---|---|---|
| polaris-react | @shopify/polaris |
Shopify 后台核心 React 组件库(当前版本 13.10.1) |
| polaris-tokens | @shopify/polaris-tokens |
设计令牌(9.4.2) |
| polaris-icons | @shopify/polaris-icons |
图标库(9.3.1) |
| polaris-migrator | @shopify/polaris-migrator |
升级 codemod 工具(1.0.7) |
| stylelint-polaris | @shopify/stylelint-polaris |
Stylelint 校验工具(16.0.7) |
| polaris-for-vscode | VSCode 扩展 | 编辑器中提供 Polaris 辅助能力 |
| polaris.shopify.com | 站点 | 设计系统文档站 |
这些包之间的依赖关系也很清晰:@shopify/polaris 依赖 @shopify/polaris-icons 与 @shopify/polaris-tokens;@shopify/polaris-migrator 又依赖 @shopify/polaris-tokens 和 @shopify/stylelint-polaris。当一次 PR 同时改动多个包时,如果靠人工维护各包的 CHANGELOG 与版本号,很容易漏改、错改,或出现内部依赖版本不匹配。Changesets 正是为这种"monorepo 多包发布"场景设计的方案:开发者只需在 PR 里描述这次改动影响哪些包、属于什么语义化版本级别,版本号与 CHANGELOG 的落地交给工具自动完成。
1.2 Changesets 在本仓库中的落地位置
本仓库的 Changesets 配置集中在 .changeset 目录,当前包含三个文件:
- .changeset/README.md:给开发者看的操作说明(本文主体);
- .changeset/config.json:Changesets 的全局配置;
- .changeset/loud-rivers-wear.md:一个真实存在的待处理 changeset 文件(内容是
polaris.shopify.com的 patch 级改动)。
根目录 package.json 中还暴露了与版本发布相关的脚本(均通过根目录的 pnpm 调用):
"changeset": "changeset":新建 changeset 的入口;"version-packages": "changeset version && pnpm install --lockfile-only":落地版本号与 CHANGELOG,并同步锁文件;"release-packages": "pnpm build:release && changeset publish":构建全部非站点包后发布到 npm;"build:release": "turbo run build --filter='!polaris.shopify.com'":用 Turborepo 构建除站点外的所有包。
也就是说,Changesets 并不是孤立的一个目录,而是与仓库的构建、发布流水线深度绑定。
二、核心操作:运行 pnpm changeset 添加变更条目
2.1 前置约定
根据 .changeset/README.md 的说明,为你的 PR 添加 CHANGELOG 条目的标准姿势是:在你的 feature 分支上运行:
pnpm changeset
然后使用方向键(arrow)、空格键(spacebar) 和 回车键(return) 回答 Changesets CLI 提出的三个问题。整个交互过程是 TUI 式的多选界面,不需要手写任何 YAML。
2.2 问题一:🦋 Which packages would you like to include?
- 按
Space键勾选本次改动涉及的包(可多选); - 按
Enter键进入下一个问题。
这一步是"改动范围声明":本次 PR 改了哪些包,就把哪些包勾进来。勾选本身不决定版本级别,只决定"哪些包的 CHANGELOG 会获得条目"。
2.3 问题二:🦋 Which packages should have a major bump?
- 直接按
Enter表示本轮没有破坏性变更,进入下一问; - 若本次改动包含破坏性变更(如移除组件 API、修改公共接口),则用方向键定位到对应包,按
Space勾选,使其在发布时获得 major 级别升级(对应 SemVer 的 X.0.0)。
2.4 问题三:🦋 Which packages should have a minor bump?
- 直接按
Enter表示不选择任何 minor 包,此时你的改动会自动被归为 patch 级别; - 若本次改动包含向后兼容的新功能(新增组件、新增 props、新增 API),用上下方向键定位并配合空格键勾选,使其获得 minor 升级(对应 SemVer 的 X.Y.0)。
注意这里的关键设计:"默认 patch"。如果不主动声明 major 或 minor,Changesets 会保守地按 patch 处理,避免开发者遗漏标记导致意外的大版本提升。CLI 完成后,终端会输出类似如下的确认信息:
🦋 The following packages will be patch bumped:
🦋 {PACKAGE NAME}
🦋 {PACKAGE NAME}
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary › {CHANGELOG ENTRY}
接下来输入一段面向读者的摘要(Summary),它会直接进入该包未来的 CHANGELOG。如果直接提交空行,Changesets 会打开外部编辑器($EDITOR)供你撰写更长的说明。
2.5 提交与收尾
- 按 Change Log 内容规范撰写摘要(本仓库 README 指向团队维护的 changelog 编写规范,核心要求是:写清楚"对使用者意味着什么",而不是复述代码 diff);
- 随后
commit并push这个生成的 changeset 文件即可。
2.6 一个真实示例:changeset 文件长什么样
运行 pnpm changeset 后会在 .changeset 目录生成一个随机命名(通常是"形容词-名词-动词"风格)的 Markdown 文件。仓库里现成的 .changeset/loud-rivers-wear.md 就是一个标准示例:
---
'polaris.shopify.com': patch
---
Updating dux package to 5.0.1
其结构非常清晰:
- 开头的
---包裹部分是 front matter,每行形如'包名': 版本级别,声明"哪个包要升到什么级别"; ---之后是 CHANGELOG 正文,即开发者输入的 summary;- 该文件还有未使用的元数据部分(如
'@shopify/polaris': minor这种行会被同时保留用于说明内部依赖是否需要跟随升级)。
正因如此,changeset 文件本身就是一个可评审、可回溯的"发布意图声明",PR reviewer 可以在合并前就确认版本级别是否合理。
三、配置解读:.changeset/config.json 的每一项语义
仓库的 .changeset/config.json 是 Changesets 的全局配置,内容如下:
{
"$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
"changelog": ["@changesets/changelog-github", {"repo": "Shopify/polaris"}],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"updateInternalDependents": "always"
}
}
各字段含义如下:
| 配置项 | 值 | 含义与影响 |
|---|---|---|
$schema |
@changesets/config@2.0.0/schema.json |
编辑器校验与智能提示用,声明配置 schema 版本 |
changelog |
["@changesets/changelog-github", {"repo": "Shopify/polaris"}] |
指定 CHANGELOG 生成器为 GitHub 风格(会生成"感谢贡献者 + 关联 PR 链接"的条目),并指向目标仓库 Shopify/polaris,对应根目录 devDependencies 中的 @changesets/changelog-github@^0.5.1 |
commit |
false |
执行 changeset version 时不自动创建 git commit,将版本落地与提交动作解耦,便于人工审查 diff 后再提交 |
fixed |
[] |
不启用"固定版本组";若启用,组内包必须一起发布到同一版本 |
linked |
[] |
不启用"联动版本";若启用,组内包版本号保持同步递增 |
access |
public |
发布到 npm 时使用 public 访问级别,适用于公开发布的开源包 |
baseBranch |
main |
以 main 作为版本计算的基准分支 |
updateInternalDependencies |
patch |
当仓库内包 A 依赖包 B,且 B 本次有 minor/major 升级时,A 对 B 的版本依赖至少以 patch 级别跟随更新,保证安装后内部依赖版本一致 |
ignore |
[] |
不忽略任何包 |
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH.updateInternalDependents |
always |
实验性选项:内部依赖方(如 @shopify/polaris 之于 @shopify/polaris-tokens)只要依赖被升级,就无条件更新其依赖声明 |
这些配置与子包 package.json 中的 publishConfig 是配套的。例如 polaris-react/package.json、polaris-tokens/package.json、polaris-icons/package.json、polaris-migrator/package.json 都声明了 "publishConfig": {"access": "public"},与 access: "public" 一致,确保发布时无需交互确认即可公开发布。
四、从 changeset 到发布:完整的发布工作流
4.1 消费 changeset 的两条命令
changeset 文件并不会自己生效,版本落地与发布由以下命令完成(对应根目录 package.json 的脚本):
pnpm version-packages # 等价于: changeset version && pnpm install --lockfile-only
pnpm release-packages # 等价于: pnpm build:release && changeset publish
changeset version会读取 .changeset 下所有待处理 changeset,按 front matter 的声明更新各包 package.json 的 version 字段、追加/合并 CHANGELOG.md,并消费掉这些 changeset 文件(通常移动到.changeset的历史归档中);- 之后用
pnpm install --lockfile-only同步 pnpm-lock.yaml,保证锁文件与更新后的版本一致; changeset publish则根据新版本号逐个npm publish各包。
4.2 版本计算与内部依赖联动
从配置可以看出本仓库的发布策略:
- 每个 PR 独立产生 changeset,互不阻塞,版本号在发布前统一计算;
fixed与linked均为空,说明各包版本独立演进,互不强制同版本;- 但
updateInternalDependencies: "patch"与updateInternalDependents: "always"保证内部依赖链(@shopify/polaris→@shopify/polaris-tokens、@shopify/polaris-icons;@shopify/polaris-migrator→@shopify/polaris-tokens、@shopify/stylelint-polaris)的版本声明始终跟随上游升级,避免出现"发布后内部依赖还指向旧版本"的经典 monorepo 问题。
4.3 CI 中的自动发布:Version Packages PR
仓库的发布文档 documentation/Releasing.md 描述了 Changesets 官方 GitHub Action 在 CI 中的行为:
- 每当 PR 合入
main,CI 自动创建/更新changeset-release/main分支,并打开标题为 "[Version Packages]" 的 PR,该 PR 始终包含一次最新执行的changeset version结果; - 合入
changeset-release/main到main后触发release.yml工作流执行发布; - 发布完成后重建
changeset-release/main分支,准备下一轮版本。
此外,documentation/Releasing.md 还介绍了两种补充发布手段:
- Snapshot releases(快照发布):在 CI 通过的 feature 分支 PR 上评论
/snapit,即可把当前改动打成带前缀的预发布版本供消费项目临时测试,无需真正发布正式版本; - Prerelease(beta):在
next分支上执行pnpm changeset pre enter beta进入预发布模式,所有 changeset 会被汇总到 "[Version Packages (beta)]" PR,合并后以betadist-tag 发布,供大版本(含破坏性变更)的候选验证。
五、写给团队成员的实操清单
把上面的内容收敛成一份可执行的清单(适用于任何在本仓库提交 PR 的开发者):
- 确保当前在 feature 分支,且改动已完成;
- 运行
pnpm changeset; - 问题一:
Space勾选本次改动的包,Enter进入下一问; - 问题二:若无破坏性变更直接
Enter;若有,用方向键 +Space勾选对应包; - 问题三:若无新功能直接
Enter(自动归为 patch);若有,勾选对应包; - 在
Summary ›处按 CHANGELOG 规范撰写面向使用者的摘要(空行可唤起外部编辑器); - 检查生成的 changeset 文件(front matter 中的包名与级别、正文摘要),
commit并push; - 后续由 CI 自动生成 Version Packages PR,合并即发布;若想提前验证,可用
/snapit触发 snapshot release。
六、常见疑问与注意事项
- 为什么默认是 patch? 因为 Changesets 的哲学是"保守升级":只有显式声明 major/minor 才会提升版本级别,避免无意的破坏性版本。
- 可以直接手写 changeset 文件吗? 可以。参考 .changeset/loud-rivers-wear.md 的格式即可,但 CLI 能保证包名拼写正确、级别选择正确,仍建议用
pnpm changeset生成。 - 版本落地与提交是分离的。
commit: false意味着changeset version只改文件不自动 commit,团队可以在提交前人工审查版本 diff。 - Node 版本要求。 根据根目录 package.json 的
engines,本仓库要求 Node>=20.10.0,且使用pnpm@8.15.5作为包管理器,运行 changeset 相关命令前请先满足环境要求。 - 发布前必须构建。 仓库的
release-packages先执行pnpm build:release(Turborepo 构建除polaris.shopify.com外的所有包)再changeset publish,因为发布的是构建产物(如@shopify/polaris的build/目录,见 polaris-react/package.json 的files字段)。
结语
Changesets 为 Polaris 这样的多包 monorepo 提供了一套"声明式 + 自动化"的版本治理方案:开发者只需在 PR 中回答三个问题、写一段面向使用者的摘要,剩下的版本号计算、CHANGELOG 生成、内部依赖联动、npm 发布与 CI 自动化,都由 .changeset/config.json 与根目录 package.json 中的脚本协同完成。理解这一套机制,不仅能让你在 Polaris 仓库中顺畅提交变更,也能迁移到任何使用 Changesets 的 pnpm monorepo 项目中。