技术解读
把发版从“玄学”变成流水线:Algolia shipjs 深度解析
项目背景与痛点
JavaScript 生态里的发版历来是个“半自动”的苦差事:改 package.json 版本、手写 CHANGELOG、git tag、npm publish、建 GitHub Release、发 Slack 通知。单包项目尚可忍受,一旦上了 monorepo,十几个子包版本交错,人工维护版本号与依赖引用几乎必然出错。
于是出现两条路线:以 semantic-release 为代表的“全自动黑盒”,合并即发布;以 release-it 为代表的“本地手动发布”。前者省事,代价是版本号和 CHANGELOG 完全交给 commit 解析,出错只能靠 revert;后者可控,但流程不可审计、无法团队协作。
shipjs 的定位恰好卡在中间:它把发版拆成两个阶段——本地“准备”和 CI“执行”,中间插入一次 Pull Request 评审。 这就是项目描述的“Take control of what is going to be your next release”的真实含义:发布这件事,你依然拥有最终否决权。
核心技术揭秘
从架构上看,shipjs 不是一个“框架”,而是一个任务编排器 + 可插拔钩子的组合。它没有引入复杂的插件系统,而是用最朴素的方式——把每个动作写成独立的异步步骤函数,串成顺序执行的任务链。
其核心设计有四层:
- CLI 层:
commander定义setup / prepare / trigger三个命令,inquirer承担交互式提问(选版本级别、确认 CHANGELOG 分组、是否预发布)。 - 任务层:Git 操作通过
child_process直接调用系统git,而非嵌入nodegit之类的库——这是刻意的取舍,保证用户本地 Git 配置(签名、hook、SSH agent)原封不动生效。 - 远端层:通过
@octokit/rest操作 GitHub API,创建/更新 PR、打 Release、提交评论。 - 配置层:
.shipjs/config.js与prepare.js/trigger.js两个钩子脚本,允许团队把自己的 build、测试、上传 CDN 等私货塞进流程。
难点不在单个步骤,而在状态机的边界。prepare 阶段的所有产物(版本号、CHANGELOG、分支)都体现在一个 PR 的 diff 里,评审看到的不是日志而是可修改的文本;trigger 阶段则必须假设“本地的一切已经就绪”,只做不可逆的推送与发布。因此代码里大量使用 --dry-run 贯穿始终,让整条链路在真正落盘前先跑一遍,这也是它能被信任的前提。
monorepo 的支持是另一个技术重心:它需要判断哪些包真正发生了变更(基于目录 diff 而非 commit 信息),再按依赖拓扑决定发布顺序,并同步改写 package.json 中对内部包的版本引用。这部分逻辑与 lerna 的思路同源,但更轻量。
功能亮点与差异
可评审的 CHANGELOG 是它的杀手锏。 相比 semantic-release 生成后即发布,shipjs 把 CHANGELOG 变成 PR 里的一份草稿,人可以删掉无意义的条目、改写措辞、合并分类。对于面向开发者的开源库,CHANGELOG 质量直接影响口碑,这一点不可替代。
两阶段模型天然适配权限模型。 本地 prepare 只需要普通写权限;trigger 在 CI 上运行,用受限的 bot token 完成 push 与 publish,避免了“每个开发者本地都揣着 npm 发布密钥”的地狱场景。
钩子设计极其务实。 prepare.js 在 PR 创建前执行,适合跑全量测试与构建;trigger.js 在发布时执行,适合产物上传。它没有发明插件协议,就是导出函数,学习成本近乎为零。
对比 changesets(更偏 monorepo 的 changeset 文件管理),shipjs 更强调“一次完整的发布编排”,而非“变更意图的记录”,两者定位并不重叠。
应用场景与落地建议
它最适合中大型 monorepo 开源库或 SDK 团队:发布频率中等(周/双周),需要对外维护高质量 CHANGELOG,并且已有 PR 评审文化。
落地时有几个坑必须提前踩掉:
- 分支保护:
trigger需要向主分支推送 tag 和 merge commit,务必为 bot 账号配置例外规则,否则 CI 会卡在保护策略上。 - CI 的 Git 身份:容器里通常没有
user.name/email,需在钩子或流水线中显式配置,否则 commit 会失败。 - npm 凭据:使用 Automation Token 绕过 2FA,不要指望交互式登录。
- 务必先
--dry-run:整套流程建议先在内部仓库跑通两轮,尤其是 monorepo 的内部依赖改写,一旦出错修复成本很高。 - 预发布版本用
--preid走1.2.0-beta.0这类路径,避免污染正式版本线。
综合评价
优点:发布流程透明可审计;CHANGELOG 人工可控;monorepo 支持到位;钩子简单直接;本地与 CI 职责划分清晰,安全性优于“本地发包”方案。
缺点:强依赖 GitHub,GitLab/Bitbucket 用户直接出局;交互式 CLI 在纯自动化场景下略显笨重;@octokit/rest 与 Git 版本演进的兼容性需要持续跟进;社区体量与 semantic-release 相比仍有差距,遇到边缘问题查资料不易。
一句话总结:如果你需要“自动化的效率”但又不想放弃“人对发布的最终控制权”,shipjs 是当前 JavaScript 生态中最接近理想解的那一个。



评论