上一篇 发布流水线 讲清了"两阶段发布"的全景;本篇聚焦开发者每天都会碰到的具体问题:什么改动需要 changeset?bump level 怎么选?多个包一起改怎么写?写错了会发生什么?以及 lhx-kit 在 2026 年 5 月把
fixed切换成linked之后语义有什么变化。
改动了
packages/*/src/**下的任何一个文件,提交前必须pnpm changeset add。否则 release workflow 不会触发发布——你的改动会进 git,但永远不会上 npm。
下面这张决策树是日常的速查参考:
Changesets 用的是标准 SemVer:major.minor.patch。每写一份 changeset 都要选择 bump level,规则不复杂但很多人选错:
| 场景 | bump | 例子 |
|---|---|---|
| 修 bug、性能优化、内部重构、类型收紧 | patch | 修一个 race condition;把 any 换成精确类型 |
| 新增功能、新增 API、新增 export、新增配置项(向后兼容) | minor | createRequest 新增 dedupe 选项;新增 @lhx-kit/runtime/cdn-loader 子路径 |
| 删除/重命名 export、改变默认行为、改变现有 API 签名 | major | 把 setupMobile() 改名为 installMobile();默认 cdn.fallback 从 local 改为 none |
几个容易判错的边界情况:
peerDependency → 用户必须装新包 = majordependency 的 minor → 自己内部的事 = patchdependency 的 major(且影响外部行为) → minor 或 major,看具体行为是否变化internal 标记的 API(即使外部能 import) → 严格说也是 major(用户能 import 到的就是公共面)交互式选择哪些包、什么 level、写一段描述。但 lhx-kit 实战里更常见的是手写——文件就放在 .changeset/<slug>.md,格式很简单:
好的描述回答三件事:
糟糕的描述——下面这些都属于:
为什么重要:CHANGELOG 直接由 changeset 描述拼接而成。用户在 npm 页面看的就是这段话。半年后回头看 git log 找问题的也是你自己。
changesets/cli 默认生成像 unique-cats-jump.md 这样的随机两词文件名,看起来 cute 但不利于检索。lhx-kit 的实战做法是手写一个有语义的 slug:
格式约定:<scope>-<what>-<optional-yyyy-mm>.md。日期后缀只在"批量改" / "硬化" / "扫除" 这种集中维护批次里加。
这是 2026 年 5 月才切换的语义,前后差别很大,单独讲清楚。
| 维度 | fixed |
linked |
独立 |
|---|---|---|---|
| 改一个包,发版会发哪些? | 全部 8 个(即使没改) | 只发被改的 | 只发被改的 |
| 没改的包版本号会变吗? | 会跟着升 | 不变 | 不变 |
| 用户视角 | 全套同 1.4.0,最简单 | 改的升到统一新版本,没改的停留 | 各包独立,需要看 peer 兼容性 |
| npm 上的"空 release"噪声 | 严重 | 无 | 无 |
| 适合 | 紧耦合套件(pre-3 Vue 全家桶) | 协同发布的工具集 | 完全独立的库 |
切换发生在 2026-05,commit 2747a93。原因:
@lhx-kit/tsconfig 这种纯配置包从来不变,但每次 release 都被强制 bump 到新版本号 → npm 上长出一堆"代码完全相同、只有版本号涨"的包,用户 diff 也对不上❌ 错误示例 1:图省事把所有包都列上
这等价于回到 fixed 模式,用 linked 就没意义了。只列实际改了源码的包。
❌ 错误示例 2:跨包改动只列了"主"包
但实际上 CLI 的实现调用了 @lhx-kit/offline 的新 export。只列 CLI 会让 offline 包的版本号停留,下游用户单独装 @lhx-kit/offline 会找不到那个新 export。两个包都要列。
写一份"空"的 changeset 不是优雅的做法——它会让 CHANGELOG 出现"无意义条目"。不需要的场景:
| 改动类型 | 例子 | 为什么不需要 |
|---|---|---|
| 文档站修改 | apps/docs/** |
@lhx-kit/docs 在 changeset ignore 列表里 |
| Examples | examples/** |
examples 不发包 |
| 仓库工具 | scripts/**、.github/**、Makefile |
不在 packages 里 |
| 配置改动 | 根 tsconfig.json、biome.json、commitlint.config.cjs |
不发布 |
| Test only | packages/*/tests/**(如果有) |
tests 不进 dist |
| README 同步 | 由 pnpm sync:readmes 脚本生成 |
不影响产物行为 |
唯一例外:如果你真的想做"无代码 release"(比如纯升 README 当作版本里程碑),手写一份描述说明意图——但 99% 场景没必要。
最常见。比如本批次的 lint + any 收敛——动了 6 个包:
例:同一个 PR 里既加了新功能又修了 bug,两件事不相关——写两份:
好处:CHANGELOG 上是两条独立条目,用户能分别看到。
@lhx-kit/cli depends on @lhx-kit/runtime。如果你改了 runtime 的内部行为但没改 API,cli 不需要写 changeset——但 changesets 会自动给 cli 加一个 patch bump(因为 updateInternalDependencies: "patch" 配置),保证 cli 的 dep range 拉到新 runtime。这一步不需要你手动写。
正常 release 走 master → Version PR → publish。如果你想在 PR 上预发版让别人测试,用 snapshot:
效果:
0.0.0-canary-20260504123456 这种格式canary tag(不是 latest)pnpm add @lhx-kit/cli@canary 才能装到,不会污染 stable 安装适用场景:跨 PR 的集成测试、给 reviewer 一个真实安装的包试用。
最常见原因:PR 里没有 .changeset/*.md 文件。release workflow 检测到没有 pending changeset → 直接退出,既不开 Version PR 也不发布。
自检:
原因:你的 changeset frontmatter 没列那个包。即使你改了它的源码,changesets 不会"自动检测"——它只看你声明的那行 yaml。
修复:编辑现有的 .changeset/<slug>.md,把缺的包加进 frontmatter,重新 push。
原因 1:linked 组里另一个包要 bump,所以整组对齐到统一新版本号——这是 linked 的预期行为,不是 bug。
原因 2:内部依赖联动(updateInternalDependencies: "patch")。如果 runtime 升了 patch,cli 因为 depend on runtime 也会被自动加一个 patch bump。
最常见三种:
access 字段不是 public——scoped 包必须显式声明@lhx-kit/<name> 在 npm 已被别人占用,scope 没有所有权详细日志在 GitHub Actions 的 release job 输出里翻 npm error 关键字。
总结一条最重要的纪律:
每个改动
packages/*/src/**的 commit/PR,都必须带一份 changeset。把"忘写 changeset"当成和"忘写测试"同等量级的事故。
具体执行靠两道防线:
changesets/changelog-github + pnpm changeset:status 在 PR 上加一个 status check,没 changeset 就红脸。lhx-kit 当前还没启用这条强制检测,靠 review 把关——这也是一个值得做的优化项。如果你正在改 packages 但忘了写 changeset,任何时候补一份都不晚——只要在 merge 之前。merge 之后想补就只能新发一个 commit + 新 PR。