MIT License

技术解读

以下内容由 GitRead 中文社区 编辑团队基于项目 README 与官方资料整理, 结合中文开发者的常见使用场景补充技术选型建议。具体以项目官方文档为准。

把发版从“玄学”变成流水线: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 不是一个“框架”,而是一个任务编排器 + 可插拔钩子的组合。它没有引入复杂的插件系统,而是用最朴素的方式——把每个动作写成独立的异步步骤函数,串成顺序执行的任务链。

其核心设计有四层:

  1. CLI 层:commander 定义 setup / prepare / trigger 三个命令,inquirer 承担交互式提问(选版本级别、确认 CHANGELOG 分组、是否预发布)。
  2. 任务层:Git 操作通过 child_process 直接调用系统 git,而非嵌入 nodegit 之类的库——这是刻意的取舍,保证用户本地 Git 配置(签名、hook、SSH agent)原封不动生效。
  3. 远端层:通过 @octokit/rest 操作 GitHub API,创建/更新 PR、打 Release、提交评论。
  4. 配置层:.shipjs/config.js 与 prepare.js / trigger.js 两个钩子脚本,允许团队把自己的 build、测试、上传 CDN 等私货塞进流程。
graph TD A[开发者本地执行 shipjs prepare] --> B{"工作区是否干净"} B -->|"否"| C[中止并提示未提交改动] B -->|"是"| D[解析 commit 生成 CHANGELOG 草稿] D --> E{"版本策略"} E -->|"单包"| F[更新 package.json] E -->|"monorepo"| G[逐包计算版本并修正内部依赖] F --> H[创建 release 分支并提交] G --> H H --> I[推送远端并创建 Pull Request] I --> J{"评审是否通过"} J -->|"否决"| K[关闭 PR 回流开发分支] J -->|"通过"| L[CI 触发 shipjs trigger] L --> M[打 tag 并 npm publish] M --> N[创建 GitHub Release 与通知]

难点不在单个步骤,而在状态机的边界。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 生态中最接近理想解的那一个。

关于

GitHub 原始描述(英文)

Take control of what is going to be your next release.

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
878
价值分
477
活跃状态
活跃

语言占比

JavaScript
Shell

Release

README

🛳 Ship.js

npm CI NPM All Contributors Chat on Slack

Take control of what is going to be your next release.
https://community.algolia.com/shipjs/

Features

  • Automated
    • Minimize your effort for release and make less mistakes.
  • Asynchronous
    • You don’t have to release on your local machine. Do it asynchronously and continue your work.
  • Collaborative
    • Don’t sweat it alone. Review the next release on pull request with your colleagues.

Installation

Running the following command will guide you to set it up interactively.

npx shipjs setup

This interactive CLI will help you install Ship.js into your package and create a tailored config file for your project.

npx shipjs setup

Why 🤷🏻‍

Coding is fun, debugging and testing are okay, but releasing is NOT.

When releasing, you go through something like the following:

  • Update the version in package.json
  • Update the changelog
  • Actually release it (e.g. npm run build && npm publish)
  • Create a git tag
  • Create a release on GitHub

What could go wrong?

  • You might make mistakes during the release.
    • Environments are different across your team members.
    • You’re releasing alone because the whole process happens on your local machine.
    • It’s not your everyday-job. Mistakes can happen.
  • You are blocked and cannot do anything else until it’s done.
    • Even if you have a release script, you need to watch until the script finishes well.
    • You don’t want to switch to another feature branch and work there until the script finishes.

How to solve them❓

In Ship.js, the release process consists of three parts.

Part 1. Preparation (shipjs prepare)

Preview

Run shipjs prepare and it will briefly do the following:

  • Figure out next version.
  • Update the version and changelog.
  • Create a pull request.

It takes less than a couple of minutes.

Part 2. Review

  • Review the PR by yourself, or with your colleagues.
  • Add more commits to the PR if you want.
  • You can hold the release, build from the staging branch and test it manually.
  • If you want to cancel the release, just close the PR and delete the staging branch.

When you think it’s ready to release, merge the PR.

Part 3. Trigger a release (shipjs trigger)

Run shipjs trigger and it will briefly do the following:

  • Run a final test (unit, e2e, etc).
  • Release it to NPM (or elsewhere as you configure it).
  • Create a git tag for the version.
  • Create a release for the tag on GitHub.

You can manually run shipjs trigger on the base branch after the PR is merged.

However you can also configure your CI service(e.g. CircleCI) to do this for you. It means the longest process is on the CI service asynchronously, not occupying your working environment.

Getting Started

Let’s move on to the guide.

Or, you can watch this video if you prefer.

Getting Started with Ship.js

How is it different from semantic-release?

semantic-release is a tool for fully automated version management and package publishing.

Ship.js gives you more control over the release process. Ship.js automatically creates a PR before publishing every release, so that you can:

  • Confirm the next version is correct.
  • Confirm which commits are going to be released and discuss them with colleagues.
  • Edit the automatically generated changelog for clarity & readability.
  • Run any automated tests on the package release candidate.
  • Build a release candidate automatically (with Pika CI or CodeSandbox).

How to Contribute?

Read Contribution Guide →

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Eunjae Lee

💻 📖

Fred K. Schott

📖

Yasuaki Uechi

💻 📖

Dmitry Ivakhnenko

💻 📖

James George

💻 📖

kazuya kawaguchi

💻 📝

tyankatsu

💻 📖

Haroen Viaene

💻

Shinya Ohyanagi

💻

Donghoon Song

💻

Miyata Jumpei

💻

lulzneko

💻 📖

Vinayak Kulkarni

💻

Sarah Dayan

💻 📖 🚧

This project follows the all-contributors specification. Contributions of any kind welcome!

Badge deploy

Show the world you’re using Ship.js

[![deploy](https://img.shields.io/badge/deploy-🛳%20Ship.js-blue?style=flat)](https://github.com/algolia/shipjs)
<a title="deploy" href="https://github.com/algolia/shipjs" rel="nofollow">
  <img src="https://img.shields.io/badge/deploy-🛳%20Ship.js-blue?style=flat" />
</a>

评论

暂无评论