拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Devbox 发布全流程实战指南:基于 release.ts 的版本切割、发布说明编写与故障恢复

Devbox 发布全流程实战指南:基于 release.ts 的版本切割、发布说明编写与故障恢复
  • 开发工具
  • CLI

【免费下载链接】devbox

Instant, easy, and predictable development environments

项目地址:https://gitcode.com/GitHub_Trending/dev/devbox
点击查看免费下载

Devbox CLI 的每一次正式发布都走同一条固定管线:scripts/release.ts负责所有机械步骤(版本号校验、flake 同步、构建、打标、发布),而版本如何选择、说明如何撰写、何时上线,则依赖人的判断。本文以仓库内.agents/skills/release-devbox/SKILL.md为骨架,结合 scripts/release.ts 源码与 .github/workflows 下的 CI 配置,完整拆解"从devbox run release-changes到版本正式上线"的全过程:读完后你能独立完成一次发布评估、撰写符合 house style 的发布说明、以 Agent 或人机协作方式驱动发布,并在管线卡住时按症状表快速定位修复。

发布管线概览:两个入口与只读辅助命令

scripts/release.ts拥有整条发布流程的所有机械步骤及执行顺序(源码头部注释即声明了四个"load-bearing"顺序约束,后文会逐条展开)。发布者的工作集中在三件事:判断这次交付了什么、推荐合适的版本号、撰写标题与说明,并决定采用哪种方式发布。

管线共有两个入口,二者都遍历完整流程:

命令作用
devbox run draft-release构建发布产物,并以 draft(草稿)形式留档供审查
devbox run publish-release构建并直接上线,或补完一个已存在的草稿

另有两条只读辅助命令,随时可安全运行:

  • devbox run release-changes—— 展示自上个版本以来的提交与推荐的版本号;
  • devbox run release-status—— 报告某个版本的 tag、草稿、资产数量、CI 结果与flake.nix版本现状。

这些命令定义在 devbox.json 的shell.scripts中(draft-release、publish-release、release-changes、release-status),实际执行node scripts/release.ts --draft/publish/changes/status,多余参数会原样转发给脚本——因此一个中断的发布可以用devbox run publish-release --version 0.18.2这类命令在任意中间状态继续。

第一步:发布前看清这次会交付什么

发布任何版本之前,先运行:

devbox run release-changes

该命令会输出自上一个发布 tag 以来的提交,并按类型分组展示(breaking / features / fixes / other),同时给出推荐版本号,以及flake.nix是否需要同步 bump。在谈论任何关于发布的事情之前,都应该先读它。

从源码看,release-changes对应changesReport()(scripts/release.ts),其数据来源是commitsSince()与recommendVersion():

  • commitsSince()用git log --no-merges --format=%s%x1f%b%x1f%an%x1e拉取上次 tag 之后的提交,并通过 conventional commit 前缀(feat:、fix:等)与!标记或BREAKING CHANGE字样识别破坏性变更;
  • previousTag()按创建时间倒序取离 HEAD 最近、且非 edge 的 tag(0.0.0-edge.*这类每周快照 tag 永远不算"上一个版本",见isEdge()与 .github/workflows/cli-release.yml 中EDGE_TAG=0.0.0-edge.$(date +%Y-%m-%d)的生成逻辑)。

版本号推荐规则:pre-1.0 时代的 semver 取舍

Devbox 尚处于 1.0 之前,其自身的版本历史确立了两条推荐规则(对应recommendVersion(),scripts/release.ts):

  • 破坏性变更 → minor 升版:如0.17.5 → 0.18.0;
  • 其他一切 → patch 升版:例如0.17.4发布了新特性,仍按 patch 处理。

脚本会明确说出推荐的理由(1 breaking change since 0.17.5之类),但发布者仍应把推荐与实际 diff 对照做 sanity-check:如果这次发布整体移除了某个子系统,即使没有人写过feat!:,也值得升 minor。

此外,脚本对版本号格式有严格校验(validateVersion(),scripts/release.ts):tag 是裸 semver,不带前导v(即0.18.0而非v0.18.0),预发布版本通过-后缀标记(如0.18.0-dev),Edge 快照使用0.0.0-edge.<日期>。

第二步:决定如何发布——三种方式,让用户选择

不要替用户假设发布方式。脚本运行时会显式提供三种选项:

  1. 先出草稿(devbox run draft-release)——构建全部产物并停在草稿阶段。适合发布说明需要人工评审、或发布要跟公告协同的场景,这是默认的安全路径;
  2. 直接发布(devbox run publish-release)——走同一条管线,随后直接上线。适合常规的 patch 发布;
  3. 发布一个已存在的草稿(devbox run publish-release)——若已有草稿滞留,命令会列出它们并提供补完选项;可先用devbox run release-status确认现状。

publishFlow()(scripts/release.ts)正是这样实现的:没有传入--version时,它会先gh release list拉取最近的草稿清单(listDrafts()过滤isDraft项),然后让用户选择"发布某个草稿"还是"从零开始新发布";选中已有草稿后进入resumeDraft(),脚本会先根据 tag 是否已推送、资产数是否为空,动态推算还需要执行多少步,再精准续跑。

第三步:撰写发布标题与发布说明(house style)

当由真人驱动时,脚本会在$EDITOR中打开模板让用户撰写发布说明(editText(),scripts/release.ts,模板中以#开头的指导行会被剔除,不会混入最终说明)。当由 Agent 驱动时,直接自己写好并作为 flag 传入——这正是通过 Agent 走这条流程的意义所在。

获取原始素材:GitHub 自动生成的 changelog

gh api repos/jetify-com/devbox/releases/generate-notes \ -f tag_name=<version> -f previous_tag_name=<prev> -f target_commitish=main --jq '.body'

stepNotes()(scripts/release.ts)在无--notes-file时正是调用这一 API,把生成的 changelog 作为种子文本塞进编辑器。注意:这份 dump 只是PR 标题的罗列,必须改写成面向用户的语言,绝不能原样发布。

发布说明的 house style 模板

仓库以0.17.4确立、0.18.0为最干净范本的发布说明风格如下(以下为完整模板,发布时按实际内容填充):

## What's Changed ### 💥 Breaking Changes * **What was removed** — what users must do instead, by @author (#1234) ### ✨ New Features * **Short bold lead-in** — what changed and why a user cares, by @author (#1234) ### 🐛 Bug Fixes * Plain one-liners for small fixes, by @author (#1234). * **Group related fixes** — combine several PRs into one bullet when they share a root cause (#1234) and (#1235), by @author. ### 🧹 Maintenance * Dependency bumps, CI work, docs. Group aggressively; nobody reads this section line by line. ## New Contributors * @newperson made their first contribution in #1234 **Full Changelog**: 仓库 compare 页 `<prev>...<version>`

撰写时必须遵守的关键规则:

  • 以影响开头,而不是提交标题。"Shells in paths with spaces now work" 远胜于 "quote the shellrc source guard";
  • 破坏性变更排在最前,并明确告知替代方案。对于移除功能的版本,这整段就是核心叙事——直说删掉了什么、用什么替代,或没有替代;
  • 只保留有内容的分区,空分区一律删除;
  • 保留署名。每条 bullet 都以by @author及其 PR 链接结尾;
  • **代码用普通反引号,不要写成\``**。没有任何下游会重新解释正文——[.goreleaser.yaml](https://link.gitcode.com/i/c8c9719c915b0e66ebbe3fae7eb81339) 中announce.discord.enabled: false,Discord 播报器已被关闭——而 Markdown 会把`渲染成字面反引号而非代码片段。这正是0.17.4已发布说明中到处是多余反斜杠的原因。脚本的unescapeBackticks()([scripts/release.ts](https://link.gitcode.com/i/08820894d90c61f2883dc63b8be43cc8#L708-L715))会兜底把漏进去的`还原为 ````,但撰写时不要主动写。

标题惯例

默认标题就是裸版本号(如0.18.0)。若发布有明确主题,允许加简短后缀:0.18.0 — Devbox goes fully local。

在运行任何命令之前,必须把标题与完整说明展示给用户并获得明确签字确认——这是上线前的最后一道检查点,stepConfirm()(scripts/release.ts)会回显版本、标题、上一个版本、当前 commit、发布结果(draft 或 live)以及完整说明全文,交互模式下等待y/N确认。

第四步:运行发布——Agent 驱动与关键参数

将说明保存到.context/release-notes-<version>.md后,运行:

node scripts/release.ts --draft \ --version 0.18.0 \ --title "0.18.0" \ --notes-file .context/release-notes-0.18.0.md \ --yes

把--draft换成--publish即直接上线。脚本支持的全部 flag(scripts/release.ts 的 usage 输出):

Flag作用
--version <x.y.z>要发布的版本,裸 semver,不带前导v;跳过版本提示
--title <string>发布标题;跳过标题提示
--notes-file <path>发布说明文件路径;跳过$EDITOR编辑
--yes跳过所有确认(用于脚本化运行)
--skip-cli-tests即使main上最近一次cli-tests失败也继续发布(详见下文)

以 Agent 身份驱动时的四条注意事项:

  • --yes是必传的。脚本的确认提示依赖 TTY(reader()检测!process.stdin.isTTY时直接报错 "this step needs an answer but stdin is not a terminal — pass the value as a flag instead"),而 Agent 的 shell 没有 TTY——没有--yes会停在需要人工作答的步骤。但它必须在用户签字确认之后才传,因为它跳过的是用户的检查点,而不是给自己加一道检查;
  • 该命令会阻塞约 15 分钟等待cli-release工作流跑完(stepWait()默认RELEASE_WAIT_TIMEOUT_MS为 1 小时,轮询gh run list找到 tag 触发的cli-release运行后执行gh run watch --exit-status)。应当后台运行并定时回来查看,而不是让调用超时;
  • 它是可恢复的。每一步都是幂等的,中途失败只需修复原因后重跑同一条命令,脚本会从断点继续而非重复工作(totalSteps会依据现有状态动态重算);
  • --skip-cli-tests是把上膛的枪。它只跳过"检查main上最近一次cli-tests是否红灯"这一步(stepCheckMainCI(),scripts/release.ts);cli-release工作流本身仍会跑完整测试套件,不绿则构建失败。仅在检查本身失真时(例如你确认是 flake)才使用,不能用来硬闯真正红灯的main。

关于该 flag 的细节补充:只有"已完成且失败"的运行才会阻断发布;仍在排队或进行中的运行(flake bump PR 合并后紧接的常见状态)不会阻塞——等待它只会让同一套测试多跑一遍,真正的裁判是cli-release自己。

flake.nix 版本同步:自动化的 bump PR

如果flake.nix需要升版,脚本会提议一步到位完成整个流程(stepFlakeBump()/openFlakeBumpPR(),scripts/release.ts):

  1. 将 flake.nix 中的lastTag = "0.18.4"改写为新版本号;
  2. 运行devbox run update-hash刷新vendor-hash(该脚本先在临时目录go mod vendor,再用nix hash path计算哈希写入vendor-hash文件,见 devbox.json 的shell.scripts.update-hash);
  3. 运行nix flake update刷新flake.lock;
  4. 提交到bump-flake-<version>分支并推送;
  5. gh pr create打开 PR(标题为chore(release): bump flake lastTag to <version>,正文说明版本漂移的危害);
  6. 切回main,保持工作树干净,然后exit 0 停下——这是计划内的暂停而非失败——并打印继续执行的精确命令,例如devbox run publish-release --version 0.18.2。

为什么必须有这一步:flake.nix在 Nix 构建中通过lastTag固定自身版本字符串(flake.nix,version = "${lastTag}"),而没有任何机制把 git tag 同步进lastTag。历史上它曾在0.17.3卡住、完整经历了0.17.4与0.17.5两次发布,导致 Nix 构建报告的是旧版本号。脚本现已能自动检测此漂移并代开 bump PR。

两个关键约束:

  • bump PR 必须经审查并合并到main,之后才能推 tag,否则 tag 对应的提交会带着错误的版本字符串发布(goreleaser 通过-X go.jetify.com/devbox/internal/build.Version={{.Version}}注入版本,见 .goreleaser.yaml,而 tag 名决定了{{.Version}});
  • 合并后运行打印出的命令即可续跑:--version跳过版本提示,preflight 会重新拉取新main继续。若 PR 仍开着就重跑,脚本会立即停下并给出 PR 链接(openBumpPR()),而不是开第二个 PR。

Preflight:为什么它对 checkout 如此挑剔

Preflight(stepPreflight()/syncMain(),scripts/release.ts)对本地 checkout 的校验是刻意的,因为tag 会落在当前 HEAD 上。规则如下:

  • 必须位于main分支,且工作树干净——脏工作树意味着 tag 会在未提交改动之外被打出;
  • main仅仅是落后于origin/main且干净时,脚本自动 fast-forward(这是最常见情况:你在浏览器里合并完 bump PR 就回到终端,直接 pull 即可);
  • 其他任何情况——错误分支、脏工作树、本地独有提交、与远程分叉——都会给出具体报错和修复命令后停止,例如:在非main分支上时提示git switch main;本地领先时提示git log --oneline origin/main..HEAD查看本地独有提交,或git reset --hard origin/main丢弃它们。理由很直白:发布的是 origin 上经过审查的内容,本地未被审查的提交被打进 tag 是不可接受的。

Preflight 还会校验git、gh可用且已认证(gh auth status),并用--force拉取远端 tag(历史上有少量 devbox tag 被上游改写,不强推拉取会报 "would clobber existing tag")。

为什么顺序必须是这个顺序

这四个顺序约束是脚本存在的根本原因,不要试图绕过(源码头注释与 .github/workflows/cli-release.yml 的 "Attach artifacts" 步骤注释相互印证):

  • 先建草稿,再推 tag。.goreleaser.yaml 中release.disable: true意味着 goreleaser 只构建dist/;上传工作由cli-release工作流用gh release upload完成,按tag查找草稿。无草稿时它才用 GitHub 自动生成的说明新建一个草稿。goreleaser 曾自行上传,但它按title匹配草稿,于是任何带真实标题的发布都会匹配失败,被静默创建第二个草稿——说明变成裸 commit-SHA 列表。这正是0.17.5发布说明的来源,也是0.18.0卡住的根因;
  • 构建完成后才能发布。docker-image-release工作流监听 release 事件并立即下载发布 tarball 打进镜像(.github/workflows/docker-image-release.yml,DEVBOX_USE_VERSION指向 tag 并从 release 拉取二进制)。在cli-release上传完资产之前发布,会导致 Docker 构建失败——0.17.3与0.17.5恰好都是从 GitHub UI 直接发布(UI 会一步同时完成建 tag 与发布)踩了同一个坑;
  • flake bump 先于cli-tests检查。bump 必须合入main,这会重新触发main上的cli-tests——因此 bump 之前读到的 CI 结果描述的是"不会发布的那个提交"。先解决 bump,也能避免红灯的main掩盖"需要 bump PR"这一事实;
  • 用本地凭据发布,而非 CI。GitHub 不会触发由GITHUB_TOKEN发起的事件所对应的 workflow,这正是 CI 创建的 edge 发布从不触发docker-image-release的原因,也意味着发布不能只是 CI 中的一个步骤——tag 必须由scripts/release.ts从本地推送(stepTag()执行git push origin refs/tags/<version>,触发真实的 push 事件)。

卡住时:用 release-status 诊断并修复

devbox run release-status一条命令汇总:版本号、上一个 tag、flake.nix的lastTag、tag 是否已推送、release 是否存在(draft / prerelease / 资产数)、以及cli-release运行状态(scripts/release.ts 的statusReport())。

常见症状对照表:

症状原因修复
cli-release从未启动tag 推送未落地重跑同一条命令
cli-release在tests阶段失败main是红灯(见下)修复测试后重跑
草稿 0 个资产上传步骤未运行或失败检查cli-release的 "Attach artifacts" 步骤后重跑
已发布但安装器仍服务旧版本cli-post-release失败或仍在运行gh run list --workflow=cli-post-release.yml
Docker 构建失败资产上传前就发布了通过workflow_dispatch携带 tag 重跑docker-image-release

关于安装器新版本的机制(对应 .github/workflows/cli-post-release.yml):cli-post-release在 release 的released事件触发,且会先用int128/wait-for-workflows-action等待同一 tag 的cli-release成功(防止把失败构建提升为稳定版),然后把版本号写入s3://releases.jetpack.io/devbox/stable/version——这才是安装器读取的稳定版本指针。因此发布动作完成 ≠ 安装器立刻服务新版本,直到该 job 结束前get.jetify.com仍会服务旧版本。

已知故障清单:发布前先确认这两件事

以下两个问题需要在责怪发布流程本身之前先核实:

  • main曾在 2026-07-02 至 #2951 期间为红灯。起因是 macOS 上的zig-hello-world示例测试失败:build.zig使用了 Zig 0.12 之前的 API,而devbox.lock固定了 zig 0.11.0;升级到 zig 0.16 后修复。红灯的main会阻断所有发布——cli-release以测试套件为门槛——所以要先查当前状态,而不是想当然;
  • flake.nix会漂移。它曾停在0.17.3跨越了0.17.4、0.17.5两次发布。脚本现在能自动捕获并为发布者代开 bump PR(见上文)。

全管线速览:十一步的骨架

fullFlow()(scripts/release.ts)把一次从零开始的发布固定为 11 步:Preflight → 选择版本 → flake bump → 检查cli-tests→ 标题 → 说明 → 评审确认 → 建草稿 → 推 tag → 等cli-release→ 发布(或输出 "Done" 收尾)。resumeDraft()则只执行尚未完成的那几段。每一步都以[n/11]编号打印,配合✓(成功)、!(警告)、红色error:(失败)与黄色stopped:(计划内暂停,exit 0)四种输出语义,任何时刻都知道管线走到哪里、为何停下、如何继续。

结语

Devbox 的发布流程把"人该做的判断"(版本语义、说明措辞、上线时机)与"机器该做的机械劳动"(校验、构建、打标、上传、等待)严格分离:scripts/release.ts用固定顺序与幂等步骤保证每一次发布可复现、可中断、可恢复,而 .github/workflows 下的cli-release、cli-post-release、docker-image-release与cli-tests工作流分别承担构建上传、稳定版提升、镜像发布与测试门槛。理解了0.17.3、0.17.4、0.17.5与0.18.0这几次发布留下的教训——按 title 匹配草稿的重复发布、上传前发布导致的 Docker 失败、lastTag漂移、以及 UI 一步式发布——就能在任何一次新版本切割时做出正确的顺序决策,并在一半卡住时用release-status快速定位、用同一条幂等命令原地续跑。

  • 开发工具
  • CLI

【免费下载链接】devbox

Instant, easy, and predictable development environments

项目地址:https://gitcode.com/GitHub_Trending/dev/devbox
点击查看免费下载

相关推荐

上一篇:DDrawCompat终极指南:如何在Windows 10/11上完美运行经典游戏
下一篇:如何5分钟掌握FanControl:Windows风扇调速终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表