首页
/ Mole Release Flow 详解:Tag 驱动的 V* 发布流水线、三条分发渠道与再发布避坑指南

Mole Release Flow 详解:Tag 驱动的 V* 发布流水线、三条分发渠道与再发布避坑指南

2026-09-03 19:48:42作者:宣海椒Queenly

本文以 Mole 仓库中的发布流程技能文档 .claude/skills/release-flow/SKILL.md 为主体,完整拆解 Mole CLI 的发布体系:从大写 V 前缀 tag 如何触发 release.yml 构建 macOS 双架构二进制并生成带 provenance 的 GitHub Release,到 Nightly、GitHub stable、Homebrew core 三条分发渠道各自的触发条件与自动化边界,再到精选 release notes 的手动跟进流程和已知发布陷阱。读完本文,你能够按仓库现行规范独立完成一次 Mole 版本的发布前检查、打 tag 发布、资产验证、脚本通道自更新冒烟测试与 release notes 发布。

发布架构总览:Tag 驱动 + 三条分发渠道

Mole 的发布是纯粹的 tag 驱动流程release.yml 工作流监听 'V*'(大写 V)tag push 事件,在 macOS 上构建 amd64 和 arm64 二进制,生成 SHA256SUMS,附加构建溯源(build provenance),创建一个不带 notes 的 GitHub Release,随后自动向 Homebrew core 发起 PR。release notes 是工作流之后的手动跟进动作。

技能文档将仓库的三条分发渠道定义如下:

渠道 发布内容 触发条件 自动化程度
Nightly(mo update --nightly 通过 install.sh 安装 main HEAD 任意 commit push 到 main 全自动;不涉及 tag 和 release
GitHub stable release amd64/arm64 二进制 + SHA256SUMS push 大写 V 前缀 tag release.yml 构建并创建 release;精选 notes 为手动跟进
Homebrew core 版本更新 PR 提交到 Homebrew/homebrew-core 同一个 V* tag 工作流 PR 自动发起;合并时机由上游决定

文档同时规定了一条人机协作纪律:在任何 release 相关任务开始时,必须先复述本次会触及哪些渠道、不会触及哪些渠道,并与 maintainer 确认后再行动。渠道范围只能由 maintainer 指定,禁止自行推断。 这条纪律防止了"顺手把 Nightly/Homebrew 也带上了"这类越界操作。

工作流 release.yml 的源码级拆解

结合 .github/workflows/release.yml 的实际内容,可以确认技能文档描述的每个环节都有对应实现:

1. 大小写敏感的 tag 过滤与版本一致性校验

工作流入口仅匹配 V* 前缀(见 release.yml#L3-L6)。tag push 后,Verify release tag matches source version 步骤会用 sed -n 's/^VERSION="\([^"]*\)"$/\1/p' mole 从入口脚本提取版本号,再断言 tag == "V" + 源码版本(见 release.yml#L29-L38)。当前仓库入口脚本 mole 中的版本声明为 VERSION="1.53.0"mole#L57),因此下一次 stable 发布必须先把这一行 bump 到目标版本,否则 tag 校验会直接失败——这正是预检清单第 1 条 grep '^VERSION=' mole 的工程含义:本地 grep 与 CI 的 sed 校验针对同一行源码。

2. 双架构构建:纯 Go 交叉编译

构建矩阵在 macos-latest 上分别执行 make release-amd64make release-arm64。这两个 Makefile 目标(见 Makefile#L60-L70)刻意保持纯 Go 构建:CGO_ENABLED=0GOOS=darwin 交叉编译,链接参数 -s -w 去符号表。源码注释给出了原因:保持 pure-Go 是为了防止 macOS SDK 在 release runner 上通过 cgo 抬升 Mach-O 的最低 OS 版本。构建完成后还会执行 scripts/check_release_minos.sh 校验二进制的最小 OS 版本约束。

3. SHA256SUMS 与构建溯源

release job 汇总两个矩阵产物后,用 sha256sum 对所有资产生成 SHA256SUMSrelease.yml#L90-L99),随后通过 actions/attest-build-provenanceanalyze-darwin-*status-darwin-*binaries-darwin-*.tar.gzSHA256SUMS 统一附加 provenance(release.yml#L101-L108)。

这一步在技能文档里被赋予了发布阻断级的重要性:安装器是 fail-closed 的,一个缺少可读 SHA256SUMS 资产的 release 会让所有安装与 mo update 按设计中止——"missing checksums file is a release blocker, not a cosmetic gap"。仓库中对应的行为测试见 tests/install_checksum.bats

4. 创建无 notes 的 Release + 自动 Homebrew PR

Create Release 步骤显式设置 generate_release_notes: falsedraft: falseprerelease: falserelease.yml#L110-L118)。这就是"notes 必须在后续步骤用 gh release edit 补上、且绝不能用 create"的根因。

紧随其后的 update-homebrew-core job 则实现了文档表格中"Automatic PR"一栏:它下载 tag 的源码 tarball 计算 sha256,在 fork 上仅改写 formula 的 url 与其后的 sha256(bottle 块不动,因为 Homebrew 的 check-bottle-block CI 会拒绝改动它的 PR),复用既有分支则拒绝覆盖,最后用 Homebrew 模板正文创建或 PATCH 更新 Homebrew/homebrew-core PR,并轮询确认 PR 处于 open 状态(release.yml#L120-L269)。文档因此强调:Homebrew 是独立的下游闸门,只能在 core formula 更新后才验证,绝不能把脚本通道冒烟当作 Homebrew 就绪的证明。

发布前预检清单(Pre-flight Checklist)

技能文档给出的六条预检必须全部通过:

  1. grep '^VERSION=' mole 匹配新版本号;
  2. SECURITY_AUDIT.md 首行反映新版本号与日期;
  3. git status -s 为空,或只包含有意暂存的发布工作;
  4. git log origin/main..HEAD --oneline 只包含你打算随版本发布的 commit;
  5. ./scripts/check.sh --formatMOLE_TEST_NO_AUTH=1 MOLE_TEST_JOBS=2 BATS_FORMATTER=tap ./scripts/test.sh 均以 0 退出;
  6. go test ./cmd/...make build 均通过。

前两条对应仓库中真实存在的文件:scripts/check.sh(格式检查)与 scripts/test.sh(Bats 套件入口,BATS_FORMATTER=tap 输出机器可读结果,MOLE_TEST_NO_AUTH=1 关闭需要授权的测试)。关联的 release-notes 技能还补充了一句关键判据:"If any fail, stop. The notes can wait; a bad release tag cannot."——预检失败时 tag 不能推,notes 可以等。

打 Tag 与发布:命令、资产验证、脚本自更新冒烟

打 tag 的命令序列

git push origin main
git tag V<version>          # 大写 V;release workflow 忽略小写 v
git push origin V<version>

等待工作流完成后,在任何公告之前先验证 release 资产:

gh release view V<version> --json assets --jq '.assets[].name'

结果必须同时列出两个架构的二进制 SHA256SUMS。如前所述,由于安装验证是 fail-closed 的,缺少 checksums 文件意味着该 release 对脚本通道用户而言根本不可用。

脚本自更新冒烟(Script self-update smoke)

在发布 notes 或对外公告之前,技能文档要求执行一次脚本通道自更新冒烟:通过脚本渠道安装上一个 stable 版本,运行 mo update,确认 mo --version 输出候选版本。其原理在"发布陷阱"一节有呼应:脚本安装的客户端执行的是新 tag 里的 install.sh,因此这是唯一一条能真正验证存量用户升级路径的闸门——预检套件在 release 存在之前无法覆盖它。若冒烟失败,必须在任何人被告知"可以更新"之前先撤回 release(见下文"撤回并重新发布")。

精选 Release Notes:职责边界与格式纪律

职责边界:flow 管节奏,notes 管格式

release-flow 技能明确声明,精选 notes 流程(双语格式、用 gh release edit 而非 create、致谢块、六连 reaction)归 .claude/skills/release-notes/SKILL.md 所有;.agents/skills/release-notes 是指向该规范目录的 symlink(供 Codex 发现),其 Codex 专用调用策略在 agents/openai.yaml 中——文档特别警告不要用复制的镜像替换这个 symlink。flow 技能只保留引用,不复制格式细节,让 release-notes 技能保持 notes 格式的唯一事实来源。

release-notes/SKILL.md 的内容可以印证这套分工。发布动作是:

gh release edit V<version> --repo tw93/Mole \
  --title "V<version> <CodeName> <emoji>" \
  --notes-file <path-to-draft>

其"Format rules"一节列出的每条规则都注明"all are documented bugs that have shipped before"(每一条都是曾经真实上线过的事故),包括:

  • 正文 h1 只有 Mole:版本号、代号、emoji 只进 --title(如 V1.45.0 Quiet 🤫 这样的标题惯例),重复进正文标题此前已被明确否决;
  • 全篇禁用 em dash,改用逗号、句号、冒号、分号或括号;
  • 默认不放赞助者名单,只致谢本周期的 issue 报告者与 PR 贡献者(排除 tw93 和 bot);
  • 除标题的版本 emoji 外正文禁用 emoji,小节标题保持纯文本;
  • 不放内联 PR 引用、不放内联 @handle 致谢,人与 PR 只进专门的 Thanks 块;
  • 英文块在前、中文块在后,两个块编号顺序与条目数完全一致;
  • 条目按用户感知影响排序,而非 commit 时间序:headline 级变化在前,内部安全加固、性能、bugfix 在后;
  • 逐条验证 notes 中提到的命令在 HEAD 上真实存在——已删除的 mo check / mo doctor 曾险些作为"新功能"写进 notes;
  • 事故/排障类说明只允许"一句症状 + 一条命令"。

仓库内 docs/release-notes/V1.42.0.md 保存了历史版本 notes 的成稿,可作为上述结构的对照样本。

仪式锚点(Ritual anchors)

flow 技能保留了三个流程锚点:起草前,把最新 stable release 的正文当作硬性格式模板读取(gh release view <latest-tag> --json body);标题按仓库惯例取"代号 + emoji";发布后运行技能自带脚本补六连 reaction 并复读确认:

bash .claude/skills/release-notes/scripts/post-reactions.sh V<version>

注意该脚本位于技能目录内,不在顶层 scripts/ 下。查看 post-reactions.sh 源码可以看到它的防御性:拒绝小写 v 前缀的 tag(第 13–16 行,与 release.yml 过滤规则同构),依赖 gh CLI,通过 release ID 依次 POST +1laughhoorayheartrocketeyes 六个 reaction(第 29–32 行)。

发布专属陷阱(Release-only Pitfalls)

技能文档最后汇总的四条陷阱是本文的核心避坑清单,每一条都有仓库内的实现依据:

  1. gh release create 与工作流创建的 release 冲突:tag push 后 release 已存在,补 notes 必须用 gh release edit,永远不能用 create。依据见 release.yml#L110-L118 与 release-notes 技能"Publish"节的同一警告。

  2. tag 前缀大小写敏感release.yml 只过滤 'V*',推一个 v1.38.0 不会触发任何构建。release-notes 技能更进一步:小写 v tag 甚至可能指示一次打坏了的发布(botched tag)。

  3. 旧客户端从 release tag 而非 main 拉取 install.sh:自更新的 Mole 下载的是形如 raw.githubusercontent.com/tw93/mole/V<tag>/install.sh 的路径,而 tag 内容不可变。因此安装器/更新器 bug 只能通过新 tag 触达存量 stable 用户;修 main 只改变 Nightly,无法修复已发布的 stable 更新器。这条结论也解释了为什么"脚本自更新冒烟"必须放在新 tag 发布之后执行。

  4. 撤回并重新发布一个版本:完整步骤为 gh release delete V<old> --cleanup-tag(删除 release 与远端 tag)→ 删除本地 tag → 在推替代 tag 之前先关闭被取代的 Homebrew core PR 并附一行 supersede 评论(同一 formula 的 open PR 可能阻塞 brew bump-formula-pr)→ bump VERSIONSECURITY_AUDIT.md → 提交 release: V<new> → 打 tag → 走正常发布流程。新的 Homebrew core PR 会随新 tag 自动重新生成。

文档末尾还给出交叉引用:当发布工作触及 Shell 代码或测试时,应阅读 .claude/skills/bugs/references/shell-and-test-pitfalls.md 了解 Bash 3.2 数组、heredoc 输入、mock 绕过与 CI runner 的怪癖——这与仓库测试中大量 Bash 3.2 兼容性用例(如 tests/uninstall_scan_bash32.bats)相呼应。

小结:一次标准发布的时间线

把上述内容串起来,Mole 一次 stable 发布的完整时间线是:

  1. 渠道确认:与 maintainer 复述本次触及/不触及的渠道;
  2. 预检:bump moleVERSIONSECURITY_AUDIT.md 首行,跑齐六条预检;
  3. 打 taggit tag V<version> 并推送,等待 release.yml 的 build → release → update-homebrew-core 三 job 完成;
  4. 资产验证gh release view 确认双架构二进制与 SHA256SUMS 齐全;
  5. 脚本冒烟:旧版脚本安装 → mo update → 确认 mo --version
  6. 补 notes:按 release-notes 技能格式起草,gh release edit 发布,跑六连 reaction 脚本并复读确认;
  7. 下游观察:等待 Homebrew core PR 合并后再验证 brew 渠道,不手工重跑工作流。

这套流程的设计取向在源码中清晰可见:能用 CI 强制的(tag 与版本一致性、checksums、provenance、Homebrew formula 只改 url/sha)都在 release.yml 里失败即停;只能由人判断的(渠道范围、notes 措辞、reaction)则由两个技能文档以"单一事实来源 + 交叉引用"的方式约束,避免规则在多处漂移。

登录后查看全文
热门项目推荐
相关项目推荐