Deno 版本发布全流程解析:从 release_doc_template 清单到仓库内发布自动化脚本
【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno
Deno 的每一次正式发布都不是靠人工“手动敲命令”完成的,而是围绕一份可执行的发布清单(release checklist)展开:冻结分支、提升版本号、发布 crates.io 依赖、触发 GitHub 发布草稿,再到更新官网、文档站、Docker 镜像与 PyPI 包。本篇以仓库中的发布清单模板 tools/release/release_doc_template.md 为主体,结合同目录下的自动化脚本与.github/workflows中的工作流定义,拆解这份清单背后“每一步实际发生了什么”,帮助读者理解 Deno 从代码冻结到二进制分发、再到紧急回滚的完整工程链路。
发布清单是如何生成的:从模板到 Gist
清单模板并不是孤立存在的一份文档。Deno 的发布入口是start_release工作流,其触发逻辑记录在 tools/cut_a_release.md 中:选择main分支与发布类型(patch / minor / major),运行工作流,等待 "Create Gist URL" 步骤输出一个 Gist 链接,然后按 Gist 中的清单操作。
生成这份 Gist 的脚本是 tools/release/00_start_release.ts,它做三件事:
- 读取当前版本:解析 cli/Cargo.toml 顶层的
version字段得到当前 CLI 版本; - 计算下一个版本:根据传入的
--patch/--minor/--major(或--alpha/--beta/--rc)参数按 SemVer 规则推导下一个版本号; - 填充模板并创建 Gist:读取 tools/release/release_doc_template.md(预发布则使用 tools/release/prerelease_doc_template.md),替换其中的占位符后创建私有 Gist。
模板中的占位符与替换规则在源码中一一对应:
| 模板占位符 | 替换来源 |
|---|---|
$VERSION | 计算出的下一个完整版本号(如2.30.0) |
$BRANCH_NAME | v$major.$minor(如v2.30),即发布期间冻结的分支 |
$MINOR_VERSION | 前两位版本号(major.minor) |
$PAST_VERSION | 当前cli/Cargo.toml中的旧版本 |
也就是说,开发者在执行清单时看到的v$VERSION、$BRANCH_NAME等变量都已被脚本渲染为真实值。
Pre-flight:冻结分支与准备工作
清单的第一阶段(Pre-flight)要求在整个发布期间$BRANCH_NAME分支保持冻结、不接受任何新提交,并完成以下准备:
- 准备好以下仓库的 fork 与本地克隆:
denoland/deno(CLI 主仓库)、denoland/dotcom(官网)、denoland/deno_docker(Docker 镜像)、denoland/deno-docs(文档站); - 检查基准测试面板,确认近期没有性能回归;
- 在公司内部
#cli频道发出锁定公告(:lock:+@here+ “DO NOT LAND ANY PRs”),并附 Gist 清单链接。
“冻结分支”这条规则的工程含义是:发布过程中的版本号 bump、Releases.md更新等提交都直接落在该分支上,如果此时有 PR 合入,会污染发布提交的历史并干扰后续forward_v$VERSION回合(cherry-pick 回 main)的冲突判断。
Phase 1:Bumping versions —— 版本号提升到底改了什么
清单第一阶段要求运行version_bump工作流(选main分支,选patch或minor),等待其自动开出 PR,审查后合并,并特别强调:不要手动创建 release tag,tag 会在后续流程中自动生成。
该工作流的生成本体是 .github/workflows/version_bump.ts(生成产物为version_bump.generated.yml),其调用的核心脚本是 tools/release/01_bump_crate_versions.ts。从源码看,一次版本号提升实际包含 6 个动作:
1. 提升 CLI 版本与核心 crate
脚本通过 tools/release/deno_workspace.ts 中的DenoWorkspace类定位三类关键 crate:
deno(CLI 本体,即 cli/Cargo.toml);denort(runtime crate,对应 runtime/ 目录);deno_lib(lib crate,对应 cli/lib/ 目录)。
执行--patch/--minor/--major参数时,脚本先对 CLI crate 调用increment,然后把denort强制设置为与 CLI 相同的版本,并将新版本写入deno_lib的version.txt(即 cli/lib/version.txt),保证三者版本严格对齐。
2. 提升所有依赖 crate 的 minor 版本
getCliDependencyCrates()会枚举 CLI 的仓库内下游依赖(并排除test_server、test_macro、test_util三个纯测试 crate),对每个 crate 执行 minor 提升。
有一个值得注意的例外:renamedCrates集合中包含deno_v8。因为根 Cargo.toml 中它以v8 = { package = "deno_v8", ... }这种“依赖键名 ≠ crate 名”的方式声明,而发布自动化是靠“行首 crate 名”匹配依赖条目的,匹配不到就会报错。因此脚本用incrementRenamedCrateMinor()专门处理:用正则分别改写根Cargo.toml中的package = "deno_v8"依赖条目版本与其自身 manifest 的版本号(对应 libs/deno_v8/)。
3. 更新 lockfile 并验证二进制版本
cargoUpdate("--workspace")刷新 Cargo.lock;随后assertDenoBinaryVersion()会实际执行cargo run -p deno -- -v,把输出与期望版本比对,不一致则直接Deno.exit(1)——这是对“版本号真的编译进了二进制”的硬校验。
4. 提升 CI 缓存版本
bumpCiCacheVersion()会把 .github/workflows/ci.ts 中的const cacheVersion = N;自增 1,然后重跑该生成脚本刷新ci.generated.yml。其目的是让每个新版本强制使用全新的 cargo 构建缓存目录,避免跨版本缓存污染。
5. 自动更新 Releases.md
updateReleasesMd()会获取上次 tag 到当前的git log(自动区分 minor 与 patch 发布的历史区间),写入仓库根的 Releases.md;若本次是 minor/major 版本提升(releaseHasBlogPost()判定 major 或 minor 位发生变化),还会在条目前加上官方博客链接文案。若自动更新失败,脚本会打印手动兜底命令:git log --oneline VERSION_FROM..VERSION_TO。
6. 失败兜底路径
清单中<details>里的 Failure Steps 说明了工作流失败时的人工路径:checkout 发布分支,手动运行./tools/release/01_bump_crate_versions.ts,确认 crate 版本与Releases.md正确后自行开 PR 继续流程——这与脚本本身是“可本地重跑”的设计(文件头 shebang 为deno run -A --lock=tools/deno.lock.json)相吻合。
PR 的创建由 tools/release/02_create_pr.ts 完成:它创建release_X_Y_Z分支(点号替换为下划线)、提交并推送,然后以草稿 PR形式打开,PR 描述中内置了“crate 版本是否正确、Releases.md是否相关且移除了 revert”两个审查项,并附上拉取分支的git fetch upstream ... && git checkout -b ...命令。
Phase 2:Publish —— crates.io 发布与 GitHub 发布草稿
清单第二阶段要求运行cargo_publish工作流(在 Phase 1 相同的分支上),其核心脚本是 tools/release/03_publish_crates.ts:
- 用
getCratesPublishOrder()对依赖 crate 做拓扑排序,按依赖顺序逐个cargo publish; - 依赖 crate 使用
--no-verify参数发布。源码注释解释了原因:这些 crate 单独构建时无法完成deno_v8引擎特性选择(引擎选择在denocrate 顶层的v8/quickjsfeatures 决定),cargo 独立 tarball 校验会触发compile_error!;而denocrate 本身仍走完整校验(await cliCrate.publish()); - 整个流程包在
try/finally中,结束时发出系统蜂鸣(\x07)提示操作者成功或失败。
发布完成后,清单要求验证两点(⛔标记,代表硬性检查项):
- GitHub 上
v$VERSION发布草稿包含46 个 assets; - 对象存储(dl.deno.land 对应的 R2 桶)中该版本目录下有48 个 zip 文件。
tag 的创建实际上发生在后续的 tools/release/04_post_publish.ts:它拉取远端 tag 列表,若v$VERSION不存在则创建并推送——这正是 Phase 1 清单中“不要手动打 tag”的原因:tag 由 CI 在 crates 发布成功后自动创建,而 tag 又会触发第二轮 CI 生成 GitHub 发布草稿。
该脚本还实现了补丁版本回合:若当前分支不是main(即 patch release),会基于origin/main创建forward_v$VERSION分支,cherry-pick 发布提交;若产生冲突则提交带冲突的工作树并在 PR 描述中标注 “THIS PR HAS GIT CONFLICTS THAT MUST BE RESOLVED”,保证版本 bump 提交最终回到 main 分支,避免 main 上的下一个版本 bump 与历史版本不一致。
发布草稿的说明文本则由 tools/release/05_create_release_notes.ts 生成:它取 Releases.md 中最新一条版本的文本,写入target/release/release-notes.md供 GitHub 发布草稿使用。
下游生态更新:官网、文档站、Docker 与 PyPI
清单的后半部分覆盖 CLI 之外的分发渠道,每个渠道的机制略有不同:
deno.com 与 docs.deno.com
两者都是“跑一个工作流 → 自动开 PR → 人工审查合并”的三步走:官网的update_version.yml(denoland/dotcom仓库)与文档站的update_versions.yml(denoland/deno-docs仓库)。它们把站点展示的“最新版本号”指向新 tag,通常还会被setup-deno等 CI 动作消费。
deno_docker
运行deno_docker仓库的version_bump工作流,审查并合并其开的 PR,然后在镜像仓库上创建不带v前缀的$VERSIONtag(注意与主仓库v$VERSION的命名差异),tag 触发镜像构建发布的 CI,验证其成功即可。
deno_pypi(pip 安装的 deno)
deno_pypi仓库需要跑两个工作流:先version-bump(开 PR,审查合并),再release工作流触发发布,最终验证新版本已出现在 PyPI 的deno项目页。
MDN browser-compat-data
如果本次发布新增或启用了 JavaScript / Web API,需要同步更新mdn/browser-compat-data。清单给出的保守策略是:拿不准就跳过(并联系对应维护者确认),体现了“清单允许在证据不足时选择安全路径”的设计。
deno upgrade 升级横幅
清单提供了一个可选的发布后公告机制:在deno upgrade执行时向用户打印一段纯文本提示。操作方式是创建一个banner.txt(内容必须为 plaintext),上传到对象存储中release/v$VERSION/banner.txt路径。适用场景是希望提醒用户“需要执行某个命令才能享受新功能”或“存在破坏性变更”的版本。
收尾与 Downgrade:回滚预案
清单的 “All done!” 阶段要求回到内部频道发出解锁公告(:unlock:+ “You can land PRs now” + 版本发布完成通知),标志$BRANCH_NAME分支解冻。
模板最后给出了明确的Downgrade 预案(In case something went wrong):
- 把 dl.deno.land 的
release-latest.txt改回上一个稳定版本号——这是用户侧deno upgrade与自动安装脚本读取“最新稳定版”的依据,改回它即可让流量回到旧版本; - revert 官网(dotcom 仓库)中更新版本号的 PR,防止
setup-deno等 GitHub Action 拉取到问题版本。
这两步只动“版本指针”而不动已发布二进制,因此是整个流程中代价最低的止血手段。
发布自动化的目录地图
将模板与仓库脚本对照,tools/release/目录下的脚本按执行顺序可理解为一条完整流水线:
| 阶段 | 脚本 | 职责 |
|---|---|---|
| 启动 | 00_start_release.ts | 计算下一版本,填充 release_doc_template.md / prerelease_doc_template.md,创建发布清单 Gist |
| 版本 bump | 01_bump_crate_versions.ts | 提升deno/denort/deno_lib及依赖 crate 版本、更新 lockfile、CI 缓存版本、Releases.md,并编译验证 |
| 开 PR | 02_create_pr.ts | 创建release_X_Y_Z分支并打开草稿 PR |
| 发布 | 03_publish_crates.ts | 拓扑排序后逐个cargo publish |
| 发布后 | 04_post_publish.ts | 创建v$VERSIONtag;patch 版本时 cherry-pick 回合到 main |
| 发布说明 | 05_create_release_notes.ts | 从 Releases.md 生成 GitHub 发布草稿说明 |
| 公共库 | deno_workspace.ts、deps.ts | 封装仓库/crate 定位逻辑;deps.ts转发自jsr:@deno/rust-automation提供 Git、PR、SemVer 等基础能力 |
此外,promote_to_release.ts 与 promote_to_release_windows.ts 负责预发布(alpha/beta/rc)候选版本向正式发布的晋级,upload_version_file.ts 与 purge_cdn_cache.ts 则负责分发侧的版本文件上传与 CDN 缓存清理;.github/workflows/下的start_release.ts、version_bump.ts、cargo_publish.ts、post_publish.ts、promote_to_release.ts等脚本则是这些步骤对应的 CI 入口(各带一份.generated.yml产物)。
小结
这份清单模板表面上是“打勾式”的操作手册,实际上它把 Deno 发布流程中的顺序依赖(先 bump 后 publish,先 publish 后打 tag,先 tag 后发布草稿)、硬性校验点(46 个 assets、48 个 zip、cargo run -v版本比对)和失败/回滚路径(Failure Steps、Downgrade)都显式化了。结合 tools/release/ 中的脚本源码可以看到:模板中每一步人工动作背后都有一个可重跑的自动化脚本兜底,人工介入被收敛到“审查 PR、验证产物、发布草稿”这几个真正需要判断的环节——这也是一个大型运行时项目能在多版本、多分发渠道(二进制、crates.io、Docker、PyPI)下保持发布一致性的工程基础。
【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考