如何发布 Deno 版本:start_release 工作流与 crates 发布流水线的源码级解析
本文以 Deno 仓库自带的 发布说明 为主线,完整讲清从触发 start_release GitHub Actions 工作流、获取 Gist 版发布清单,到版本 bump、crates 发布、打 tag、验证产物资产的全流程。读完本篇后,你能独立执行一次 Deno CLI 版本发布,并且理解 tools/release/ 目录下每个自动化脚本在背后做了什么。
发布流程总览:四步入口与自动化管线
tools/cut_a_release.md 给出的官方入口非常简短,核心只有四步:
- 打开
start_release这个工作流(对应仓库中的 start_release 定义 与生成的 start_release.generated.yml); - 选择
main分支和发布类型(release kind),然后运行该工作流; - 等待 “Create Gist URL” 这一步完成,并查看它的输出,得到 Gist 的 URL;
- Fork 这个 Gist,并按 Gist 中生成的清单逐条执行。
从源码结构看,整个发布过程是一条“工作流 + 脚本 + 模板”的组合管线:
start_release 工作流(人工触发)
└─ Create Gist URL 步骤
└─ tools/release/00_start_release.ts
├─ 从 cli/Cargo.toml 读取当前版本
├─ 计算下一个版本号(patch/minor/major/alpha/beta/rc)
└─ 渲染清单模板 → 创建私有 Gist(release_版本号.md)
按 Gist 清单继续:
Phase 1 version_bump 工作流
└─ tools/release/01_bump_crate_versions.ts 改版本、更新 Releases.md
└─ tools/release/02_create_pr.ts 开 draft PR
Phase 2 cargo_publish 工作流
└─ tools/release/03_publish_crates.ts 按依赖顺序 cargo publish
└─ tools/release/04_post_publish.ts 创建 v$VERSION tag、回传 main
└─ tools/release/05_create_release_notes.ts 生成 GitHub Release 说明
之后 更新官网 / 文档 / Docker / PyPI / MDN,最后“解锁”仓库
第一步:运行 start_release 工作流
工作流输入与运行环境
.github/workflows/start_release.ts 是该工作流的生成脚本(生成的 YAML 文件头部标注 GENERATED BY ./start_release.ts -- DO NOT DIRECTLY EDIT,即 YAML 由脚本生成,不应直接编辑)。它定义了一个 workflow_dispatch 事件,唯一的输入参数是:
| 参数 | 类型 | 取值 | 说明 |
|---|---|---|---|
releaseKind |
choice(必填,默认 patch) |
patch / minor / major |
本次发布的版本号递增方式 |
工作流的 job 配置要点:
- 运行在
ubuntu-24.04上,超时 30 分钟; - 通过
denoland/setup-deno@v2安装deno-version: v2.x(注意:发布工具链本身就要求较新的 Deno 2.x 来运行 tools/release/ 下的 TypeScript 脚本); - 环境设置
RUST_BACKTRACE: full与RUSTC_FORCE_INCREMENTAL: 1,便于失败时排查。
“Create Gist URL” 步骤的实际执行内容
工作流最后一步(也就是原文档让你等待并查看输出的那一步)执行的命令是:
./tools/release/00_start_release.ts --${{github.event.inputs.releaseKind}}
这一步用 secrets.DENOBOT_GIST_PAT 作为 GITHUB_TOKEN,并透传 github.actor 作为 GH_WORKFLOW_ACTOR(后续脚本开 PR 时会 cc @触发者)。
第二步:Gist 是怎么生成的——00_start_release.ts 源码解析
tools/release/00_start_release.ts 是整个管线的版本计算中枢,主要做了三件事:
1. 读取当前版本并计算下一个版本
getCliVersion() 从仓库中的 cli/Cargo.toml 用正则 ^version\s*=\s*"([^"]+)"$ 提取 version 字段作为当前 CLI 版本。随后 getNextVersion()(见 00_start_release.ts 第 39–61 行)根据命令行参数决定递增策略:
--patch/--minor/--major:按 semver 常规递增;--alpha/--beta/--rc(预发布):若当前版本不是预发布版(例如2.7.0),先递增 minor(2.7.0 → 2.8.0-alpha.0);若当前版本是另一种预发布类型(例如3.0.0-alpha.12要发 beta),先去掉旧 prerelease 标识再递增(→ 3.0.0-beta.0);- 没有任何参数时抛出
Missing argument。
2. 选择清单模板并替换变量
buildDenoReleaseInstructionsDoc()(见 00_start_release.ts 第 67–85 行)按是否为预发布选择模板:
模板中的占位符会被统一替换:
| 占位符 | 替换值 | 含义 |
|---|---|---|
$BRANCH_NAME |
v<major>.<minor> |
发布冻结的分支名(如 v2.7) |
$VERSION |
计算出的完整新版本号 | 如 2.7.1 |
$MINOR_VERSION |
<major>.<minor> |
维护分支名 |
$PAST_VERSION |
当前 CLI 版本 | 上一个已发布版本 |
3. 创建私有 Gist 并打印 URL
非 dry-run 时,脚本通过 Octokit 执行 POST /gists 创建一个非公开 Gist,描述为 Deno CLI v$VERSION release checklist,文件名为 release_<版本号>.md,内容即渲染后的清单。最后在日志中打印 Gist 的 html_url,并提示:
Please fork the gist and follow the checklist.
这也解释了原文档第 4 步“按 Gist 里的说明操作”的由来——清单第一行就是 “Fork this gist and follow the instructions there”。本地调试时可以用 --dry-run 参数(00_start_release.ts 第 16–17 行)直接把渲染后的清单打印到 stdout,而不创建 Gist。
第三步:按 Gist 清单执行正式版发布
以下以正式版清单 release_doc_template.md 为准完整梳理各阶段。
Pre-flight:发布前检查与分支冻结
清单要求在整个发布过程中,$BRANCH_NAME(即 v<major>.<minor> 分支)必须冻结、禁止合入任何提交,直到发布结束。检查项包括:
- 确保持有
denoland/deno、denoland/dotcom(官网)、denoland/deno_docker、denoland/deno-docs等仓库的 fork 与本地克隆; - 检查 deno.land 的基准测试页面,确认近期没有性能回退;
- 在公司
#cli频道发出“上锁”公告(模板给出的示例文案):
:lock:
@here
Deno v$VERSION is now getting released.
denoland/deno is now locked.
*DO NOT LAND ANY PRs*
Release checklist: <LINK TO THIS FORKED GIST GOES HERE>
Phase 1:版本 bump(version_bump 工作流)
在 CLI 仓库的 Actions 中运行 version_bump 工作流(仓库内对应 version_bump 生成脚本 与 version_bump.generated.yml):
- 点击 “Run workflow”,选择
main分支; - 选择发布类型
patch或minor(正式清单;预发布清单则选alpha/beta/rc); - 等待工作流完成,它会自动打开一个 Pull Request。审阅、必要时修改后合并;
- 清单特别强调:⛔ 不要手动创建 release tag——打 tag 是后续 CI 自动完成的。
工作流背后:01_bump_crate_versions.ts 实际改了哪些文件
失败兜底手册中给出的手动命令就是 tools/release/01_bump_crate_versions.ts,它完整暴露了 version bump 阶段的全部改动:
- 三个核心 crate 同步版本(第 51–62 行):递增
cli(即deno)crate 的 patch/minor/major 版本,然后把同一版本写入deno_runtimecrate 以及denolib crate 的 cli/lib/version.txt; - 所有 CLI 依赖的内部 crate 递增 minor(第 64–71 行),但
deno_v8是特例——它在根 Cargo.toml 中以v8 = { package = "deno_v8", ... }的改名字段声明,通用逻辑按 crate 名匹配不到它,源码里用一个renamedCrates集合单独处理,手工正则改写根 Cargo.toml 与其自身 manifest 的 version(第 90–121 行); - 强制更新锁文件:
cargo update --workspace; - 二进制版本自检:
assertDenoBinaryVersion()会真实执行cargo run -p deno -- -v并比对输出与预期版本号,不一致直接退出码 1(第 214–222 行); - 自动更新 Releases.md:从上游 tag 之间取 git log(patch 版本与 minor 版本取 log 的范围不同,minor 版本会剔除上一个 minor 已有的提交),major/minor 版本还会在条目头部加上博客链接(第 135–190 行);
- 递增 CI 缓存版本号:读取 .github/workflows/ci.ts 中的
const cacheVersion = N;并 +1,保证每次发布后 CI 缓存失效重建(第 192–212 行)。
bump 完成后由 tools/release/02_create_pr.ts 收尾:创建名为 release_<major>_<minor>_<patch>(版本号中的 . 换成 _)的分支、提交并推送,然后通过 GitHub API 打开一个 draft PR,PR 正文自带检查项:
Bumped versions for <version>
Please ensure:
- [ ] Crate versions are bumped correctly
- [ ] Releases.md is updated correctly (think relevancy and remove reverts)
Phase 2:发布(cargo_publish 工作流)
在 cargo_publish 工作流 上对 Phase 1 所用的同一分支运行并等待完成。清单给出的失败兜底步骤(先重试,因为该工作流设计为可重入;仍失败则手动执行 03_publish_crates.ts,或补打 v$VERSION tag)对应脚本逻辑:
- 按依赖顺序发布:03_publish_crates.ts 先用
getCratesPublishOrder()对 CLI 的依赖 crate 拓扑排序,逐个执行cargo publish --no-verify;源码注释解释了--no-verify的原因:这些 crate 单独构建时依赖deno_core默认 features 关闭,deno_v8门面选不到 engine 会命中compile_error!,只有顶层denocrate(其v8/quickjsfeatures 选择 engine)才会走完整校验;最后发布denocrate 本体(第 14–30 行); - 打 tag 并回传 main:04_post_publish.ts 创建并推送
v<version>tag(若已存在则跳过),并在 patch 发布场景下把发布 commit cherry-pick 回main开一个 draft PR,保证main上的版本号与已发布版本一致(第 33–114 行); - 生成 Release 说明:05_create_release_notes.ts 从 Releases.md 提取最新版本条目,写到
target/release/release-notes.md,供 GitHub draft release 使用。
tag 推送会触发第二次 CI 运行,自动在 GitHub 上创建 draft release,并把构建产物上传到 dl.deno.land。
产物数量验证:46 个资产与 48 个 zip
清单要求人工核对两个硬性指标(这是原文档中具体、可验证的验收点):
- GitHub release draft 上
v$VERSION有 46 个 assets; dl.deno.land的release/v$VERSION目录下有 48 个 zip 文件。
核对通过后,在 GitHub 上正式发布(publish)该 release。
生态仓库与文档的后续更新
发布本体完成后,清单还列出一连串“外围”步骤,每个都是独立仓库的 workflow + 自动 PR:
- 官网(deno.com):运行 dotcom 仓库的
update_version.yml工作流自动开 PR,合并之; - 文档站(docs.deno.com):运行 deno-docs 仓库的
update_versions.yml工作流自动开 PR,合并之; - Docker 镜像:运行 deno_docker 的
version_bump工作流,合并其 PR;然后创建不带v前缀的$VERSIONtag(注意与 CLI 的v$VERSIONtag 不同),触发镜像发布 CI,确认成功; - PyPI:先跑 deno_pypi 的
version-bump.yml并合并 PR,再跑release.yml触发发布 CI,确认新版本可安装; - MDN:若本次版本新增或启用了 JavaScript / Web API,检查 MDN browser-compat-data 是否已同步;
deno upgrade横幅(可选):制作纯文本的banner.txt,上传到dl.deno.land的release/v$VERSION/目录下,用户执行deno upgrade时即可看到(用于提示破坏性变更或必须执行的新命令)。
收尾:解锁仓库与回滚预案
完成后在 #cli 频道发布“解锁”公告(release_doc_template.md 第 144–156 行给出的模板文案,含 “denoland/deno is now unlocked / You can land PRs now / Deno v$VERSION has been released”)。
如果发布中途出错,清单给出两条回滚路径:
- 把
dl.deno.land/release-latest.txt改回上一个版本(该文件决定“latest”指向哪个版本); - Revert dotcom 仓库的自动 PR,防止
setup-deno这个 CI 安装器把未发布的版本当作默认版本拉取。
预发布(alpha / beta / rc)与正式版的差异
预发布走的是同一套管线,但模板换成 prerelease_doc_template.md,关键差异有:
- version_bump 的类型选择是
alpha/beta/rc,手动兜底命令为./tools/release/01_bump_crate_versions.ts --alpha(或--beta/--rc); - 没有 cargo_publish 阶段,取而代之的是
create_prerelease_tag工作流(仓库内对应 create_prerelease_tag 生成脚本 与 create_prerelease_tag.generated.yml):直接创建并推送 tag,触发 CI 构建产物; - 产物数量指标不同:GitHub draft 上 28 个 assets,
dl.deno.land上 30 个 zip; - release 必须以 pre-release 形式发布;
- 没有官网 / 文档 / PyPI / MDN / upgrade 横幅等步骤,只更新 Docker 镜像,最后同样发“解锁”公告。
关键版本来源与自检点汇总
| 事实 | 来源(仓库内路径) |
|---|---|
| 当前 CLI 版本 | cli/Cargo.toml 的 version 字段(00_start_release.ts 第 87–100 行 从这里读取) |
| 运行时/库版本落点 | cli/lib/version.txt(由 01_bump_crate_versions.ts 第 62 行 写入) |
| 二进制版本正确性 | cargo run -p deno -- -v 输出比对(01_bump_crate_versions.ts 第 214–222 行) |
| 发布说明内容 | Releases.md 最新版本条目 → target/release/release-notes.md(05_create_release_notes.ts) |
| 正式版产物验收 | 46 个 GitHub assets + 48 个 zip(release_doc_template.md 第 83–87 行) |
| 预发布产物验收 | 28 个 GitHub assets + 30 个 zip(prerelease_doc_template.md 第 75–79 行) |
| 回滚开关 | dl.deno.land/release-latest.txt + dotcom 自动 PR 的 revert(release_doc_template.md 第 158–164 行) |
需要强调的是适用前提:以上流程针对的是官方 denoland/deno 仓库的维护者发布操作,依赖仓库 secrets(如 Gist PAT)、GitHub Actions 权限、crates.io 发布权限以及 dl.deno.land 存储桶访问权;普通使用者只需等待 release 发布后通过官方渠道升级即可。若需要本地演练模板渲染,可使用 00_start_release.ts 的 --dry-run 查看将写入 Gist 的完整清单内容。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00