Monorepo 发布工具对比调研(2026-08)
调研日期:2026-08-02 | 用途:评估 dependfix(pnpm workspace monorepo)的发布工具选型与 changelog 格式方案
演进注记(2026-08-10):本调研的结论"保持 changesets + 自研 changelog"已进一步演进——changeset 的剩余职责(版本提升执行 + 依赖传导 + 遍历发布)也由自研脚本替代(
release:plan/release:version/release:publish,见发布管线设计与发布指南),changeset 已完全移除。调研中"changesets 的 changelog 自定义能力最弱"与"semantic-release monorepo 硬伤"两个结论仍是演进方向的依据:changelog 走 conventional-changelog-cmyr 生态(同 momei 格式)、版本与发布自研(规避 semantic-release 的多包独立版本问题)。
结论摘要
- changesets 的 changelog 自定义能力最弱:
getReleaseLine/getDependencyReleaseLine只能定制条目,版本标题(## x.y.z)与分组标题(### Major/Minor/Patch Changes)在@changesets/apply-release-plan@7.1.1源码(dist/changesets-apply-release-plan.esm.js:173、:120)中写死。 - semantic-release 的 changelog 与用户习惯格式天然契合:
@semantic-release/release-notes-generator的config选项可直接挂载自定义 conventional-changelog preset(如conventional-changelog-cmyr-config),momei 的 CHANGELOG 格式即由此生态生成;但 semantic-release 原生不支持 monorepo 多包独立版本,是其硬伤。 - Nx Release 自定义能力最强(自定义 renderer + 分组标题自定义),但要求引入整个 Nx 工具链,对仅需发布能力的项目过重。
- 对 dependfix 的建议:保持 changesets(版本管理 + 发布 + OIDC 已落地),changelog 生成采用"禁用 changesets changelog + 用 conventional-changelog-cmyr-config 生成"的混合方案,可获得与 momei 完全一致的日志格式,且不引入新发布工具。
一、工具总览与活跃度(数据日期:2026-08-02,GitHub API)
| 工具 | 定位 | Stars | 最近活跃 | 周下载量* |
|---|---|---|---|---|
| changesets | 文件式版本管理(.changeset/*.md) | 12,210 | 2026-08-02 | ~3M |
| semantic-release | 全自动、commit 驱动 | 23,934 | 2026-08-02 | ~2M |
| release-it | 交互式手动发布 CLI | 9,017 | 2026-07-31 | ~2M |
| Lerna | 经典 monorepo 版本/发布(现由 Nx 驱动) | 36,053 | 2026-07-31 | — |
| Nx Release | Nx 内置发布能力 | 29,170 | 2026-08-01 | — |
| release-please | Google 的 release PR 自动化 | 7,281 | 2026-07-31 | — |
* 周下载量为 pkgpulse 2026-03 对比文章 引用的 npm registry 数据(2026-02 周均),为第三方数据。
全部工具均为 MIT 许可证(release-please 为 Apache-2.0),均在维护中。Lerna 曾长期维护停滞(2022 年 Nx 团队接管),目前 9.x 由 Nx 引擎驱动、9.0.7(2026-03-13) 仍在发布。
二、核心对比矩阵
| 维度 | changesets | semantic-release | release-it | Lerna 8/9 | Nx Release |
|---|---|---|---|---|---|
| Monorepo 多包独立版本 | ✅ 一等公民 | ❌ 原生不支持(issue #1688,需 community 插件或逐包 workflow) | ⚠️ 有限(issue #516 多包发布为痛点) | ✅ fixed/independent 两种模式 | ✅ 一等公民(release groups / 独立版本) |
| Changelog 数据源 | .changeset/*.md 开发者意图 | conventional commit 消息 | conventional commit 消息(插件) | conventional commit(--conventional-commits)或手动 | conventional commit 消息 + version plans(changeset 风格文件) |
| 版本状态存放 | package.json | git tags(不写 package.json,运行期取版本困难) | package.json | lerna.json / package.json | package.json |
| Changelog 条目自定义 | ✅ getReleaseLine/getDependencyReleaseLine 模块 | ✅ writerOpts/parserOpts/transform 扩展 | ✅ writerOpts 模板函数(commitPartial 等) | ⚠️ 依赖 conventional-changelog 配置 | ✅ renderOptions + 自定义 commit types |
| Changelog 版本标题自定义 | ❌ 写死 ## x.y.z | ✅ preset 模板(headerPartial) | ✅ preset 模板 | ⚠️ 同上 | ✅ versionTitleDate 开关 + 自定义 renderer(Nx 22+) |
| Changelog 分组标题自定义 | ❌ 写死 ### Major/Minor/Patch Changes | ✅ preset 的 commitGroups(中文 emoji 分组即此机制) | ✅ preset.types[].section | ⚠️ 同上 | ✅ Customize Conventional Commit Types(section title 可改) |
| 完全自定义 renderer | ❌ | ⚠️ 需自建 preset | ⚠️ 模板函数可覆盖大部分 | ⚠️ | ✅ 自定义 ChangelogRenderer 类(Nx 22+,renderMarkdown 全权控制) |
| pre-release | pre 模式 + --snapshot | 分支驱动(beta 分支) | --preRelease 参数 | 内置 | 支持 |
| 发布底层 | changeset publish(调 npm/pnpm publish) | @semantic-release/npm | npm/yarn/pnpm | lerna publish(npm) | nx release publish |
| OIDC trusted publishing | ✅(底层 npm/pnpm publish 支持) | ✅(npm 插件走 npm CLI) | ✅(走 npm CLI) | ⚠️ 文档称始终用 npm 发布 | ✅ |
三、Changelog 生成与自定义能力详解
3.1 changesets(当前 dependfix 所用)
- 生成机制:
changeset version消费.changeset/*.md,写入每个包目录的CHANGELOG.md(@changesets/apply-release-plan@7.1.1源码path.resolve(dir, "CHANGELOG.md"))。 - 自定义边界(本项目本地
node_modules中@changesets/apply-release-plan@7.1.1源码实证):- 版本标题:
## ${release.newVersion}(apply-release-plan esm.js:173,写死) - 分组标题:
### ${startCase(type)} Changes→### Major Changes/### Minor Changes/### Patch Changes(esm.js:120,写死,无changelogTypes配置) - 条目:由配置的 changelog 模块(
@changesets/changelog-github或自定义模块路径)输出,可完全定制
- 版本标题:
- 格式限制根源:changeset 只有 major/minor/patch 三种类型维度,没有 commit type(feat/fix/refactor)维度,无法还原 conventional-changelog 的 emoji 中文分组。
- 禁用方式:
.changeset/config.json的changelog字段支持false(config schema 确认)。 - 参考实现:react-turnstile(pnpm + changesets 2.31.1 + OIDC 生产案例)。
3.2 semantic-release(用户生态:momei / semantic-release-cmyr-config)
- 生成机制:
@semantic-release/release-notes-generator用 conventional-changelog 从 commit 消息生成 release notes,@semantic-release/changelog写入CHANGELOG.md。 - 自定义能力(官方 README):
config:NPM 包名形式的自定义 conventional-changelog preset——conventional-changelog-cmyr-config直接可用(semantic-release-cmyr-config 即如此配置)parserOpts/writerOpts:在 preset 之上逐项覆盖linkCompare/linkReferences/host/commit/issue:链接生成控制
- 格式:由 preset 模板决定——momei 的
# [1.25.0](compare) (2026-08-01)+### ✨ 新功能+* **scope:** desc ([hash](链接))即 conventional-changelog-cmyr-config@3.0.0 模板(templates/header.hbs、commit.hbs)的输出。 - monorepo 硬伤:官方 issue #1688(NPM 7 workspaces 支持请求)长期 open;多包独立版本需 semantic-release-monorepo 等社区方案,配置复杂,且全自动 bump 在 monorepo 中"共享工具包改动会误 bump 无关包"的风险(pkgpulse 对比)。
- 安全属性:两阶段可分离(analyze → publish),官方推荐短时 GITHUB_TOKEN。
3.3 release-it
- 机制:
@release-it/conventional-changelog插件包装 conventional-changelog(bump 建议 + changelog 写入)。 - 自定义(插件 README):
preset.types[]:type/section/effect(bump/changelog/hidden)三态控制writerOpts:commitPartial/headerPartial等渲染函数直接自定义(Handlebars 模板已废弃)whatBump/ignoreRecommendedBump/strictSemVer:bump 规则细粒度控制
- monorepo:非原生(多包需逐包运行或外部编排),不适合作为 monorepo 主发布工具。
3.4 Nx Release
- 机制:conventional commits 自动版本 + changelog;可选 version plans(文件式,兼容 changeset 风格)。
- 自定义(Configure Changelog Format):
renderOptions:authors/applyUsernameToAuthors/commitReferences/versionTitleDate- Customize Conventional Commit Types:可改分组 section 标题
- Nx 22+ 自定义
ChangelogRenderer:继承基类实现renderMarkdown,完全掌控输出格式
- 代价:依赖整个 Nx 工具链(nx.json 生态),纯发布场景引入成本高。
四、安全审计(供应链事件)
发布工具是供应链攻击的高价值目标,2025-2026 已发生多起直接攻击事件:
| 事件 | 时间 | 与发布工具关系 |
|---|---|---|
| TanStack 事件 | 2025-05 | changesets + pnpm + OIDC 发布管线被 GitHub Actions 缓存投毒利用,84 个恶意版本带合法 SLSA provenance 发布 |
| S1ngularity 事件 | 2025-08-26 | Nx 仓库的 GitHub Actions workflow 漏洞被利用,盗取 npm 发布 token,Nx 多个包被推送恶意版本(sonatype-2025-003584) |
| Shai-Hulud 蠕虫 | 2025-09 | 180+ 包被投毒,窃取凭据并自我复制(含 GitHub Actions workflow 注入) |
| chalk/debug 事件 | 2025-09 | 维护者凭据钓鱼,主流包更新被注入恶意代码 |
| Axios 事件 | 2026-03 | axios 1.14.1 / 0.30.4 恶意版本 |
| AsyncAPI / miasma | 2026-07 | 发布管线被攻破,5 个官方包被推送恶意版本 |
对发布工具选型的启示(多源确认,Sonatype、TanStack postmortem、e18e 建议):
- OIDC trusted publishing + 最小权限是当前发布管线的标准安全基线(长期 token 是主要被攻击面);
- **changesets 两阶段流程(Version PR 分离发布)**在安全性上有结构性优势:publish 必须经过人工合并 Version PR(pkgpulse 对比);
- semantic-release 的"全自动即发布"模式在 CI 环境被攻破时没有人工闸门,风险面更大;
- 发布 job 不应与构建/依赖安装共享缓存与凭据(TanStack 教训)。
五、对 dependfix 的选型建议
背景
- 现状:pnpm workspace + changesets 2.31.1,OIDC 发布已落地(发布指南),版本 0.1.0 未发布;
- 诉求:CHANGELOG.md 采用用户习惯的 conventional-changelog-cmyr 格式(momei 同款);
- 约束:两包 monorepo(dependfix / @dependfix/core)、Conventional Commits 提交纪律已建立(commitlint + cz-cmyr)。
方案对比
| 方案 | 说明 | 格式达成度 | 工程成本 |
|---|---|---|---|
| A. changesets + 自定义 changelog 模块 | changelog 指向自定义模块,条目输出 cmyr 风格 | ⚠️ 条目像,标题/分组仍为 changesets 默认 | 低 |
B. changesets(changelog: false)+ conventional-changelog-cmyr-config 生成 | 版本管理仍用 changesets,changelog 用同一 preset 生成(与 momei 完全同构) | ✅ 100% | 中(新增 2 个 devDeps + 生成脚本 + tag 策略配合) |
| C. 迁移 semantic-release + semantic-release-monorepo | 全自动 commit 驱动 | ✅ 100%(同生态) | 高(monorepo 插件配置复杂、误 bump 风险、OIDC 流程重做) |
| D. 迁移 Nx Release | 自定义 renderer 完全控制 | ✅ 100% | 高(引入整个 Nx 工具链) |
推荐:方案 B
理由:
- 格式 100% 达成:
conventional-changelog-cmyr-config正是 momei 格式的生成器,数据源(conventional commit)与项目提交纪律(cz-cmyr)完全匹配; - 保持已落地的发布链路:OIDC 认证、
changeset publish自动 tag、Push release tags等均不受影响(changelog: false只影响changeset version的 changelog 写入); - 版本管理保留 changesets 的意图驱动优势(人工选择 bump 类型),规避 semantic-release 在 monorepo 的误 bump 风险;
- 安全基线不变:两阶段发布(Version PR 人工合并)继续保留。
方案 B 落地要点(待用户确认后实施):
.changeset/config.json→"changelog": false- devDependencies 新增:
conventional-changelog、conventional-changelog-cmyr-config@^3 - 新增生成脚本(如
scripts/changelog.mjs):根级 CHANGELOG.md 全仓库生成;包级 CHANGELOG.md 需gitRawCommitsOpts.path过滤 + 包级 tag 序列(tagPrefix) - tag 策略:compare 链接需要
v*或pkg@versiontag 序列(changeset publish 已打pkg@versiontag;根级可用v*补打) - 0.1.0 首版:从现有 git 历史真实生成(非手写)
六、来源清单
- pkgpulse:semantic-release vs changesets vs release-it 2026(2026-03,含周下载量与功能对比)
- Brian Schiller:Changesets vs Semantic Release(2023-09,状态存放/发布流程对比)
- xNok:Why I Chose Changesets over Semantic-Release(2026-06,意图驱动 vs commit 驱动哲学)
- semantic-release/release-notes-generator(官方)
- semantic-release issue #1688(monorepo 支持请求)
- release-it/conventional-changelog 插件(官方)
- release-it issue #516(多包发布)
- Lerna Version and Publish(官方文档)
- Nx Release Configure Changelog Format(官方文档)
- Nx Release File Based Versioning
- Nx Release 主文档
- conventional-changelog-cmyr-config(npm) / GitHub(3.0.0,模板源码已核查)
- semantic-release-cmyr-config(GitHub)
- TanStack 供应链事件 postmortem
- Sonatype:npm 供应链攻击(S1ngularity / Shai-Hulud)
- CISA:npm 生态大规模供应链事件通告
- eSentire:Axios npm 包被攻破
- Unit 42:AsyncAPI / miasma 事件
- momei CHANGELOG.md(格式参照)
数据时效说明:GitHub stars/活跃度为 2026-08-02 实时抓取;周下载量为 2026-02 第三方数据;安全事件均附官方/安全厂商一手来源。