Wave Terminal 发布流程全解:从版本号管理、CI 构建到多渠道分发与自动更新
【免费下载链接】wavetermAn open-source, AI-integrated, cross-platform terminal for seamless workflows项目地址: https://gitcode.com/GitHub_Trending/wa/waveterm
本篇技术指南以 Wave Terminal(Wave)开源仓库的 RELEASES.md 为骨架,完整还原该项目从"版本号提升"到"用户终端自动更新"的整套发布工程体系:SemVer 版本管理、GitHub Actions 三个核心工作流、electron-builder 打包配置、S3 产物中转、包管理器分发以及基于 electron-updater 的双通道自动更新。读完本文,你将能复现 Wave 的发布流水线,并理解其每一个环节在仓库源码中的具体落地实现,可直接迁移到自己的 Electron + Go 混合架构项目中。
发布流水线总览
Wave Terminal 的发布过程由一条高度自动化的流水线驱动,整体分为五个阶段:
- 版本提升(Bump Version):手动触发工作流,按语义化版本规则修改
package.json中的版本号,并打上对应的 Git tag。 - 构建产物(Build Helper):tag 推送后自动触发跨平台构建矩阵,编译 Go 后端与 Electron 前端,签名、公证并生成各平台安装包。
- 草稿发布(Draft Release):构建完成后自动在 GitHub 创建草稿 Release,上传全部产物。
- 测试与发布(Publish):维护者下载草稿产物本地测试,通过后编辑 Release 说明并点击 Publish。
- 分发(Distribution):发布动作触发 Publish Release 工作流,将产物从 staging S3 桶拷贝到 release 桶,同时向 Homebrew、WinGet、Chocolatey、Snap 等渠道推送。
三个阶段之间通过 Git tag 与 GitHub Release 事件解耦,任何一步失败都可以在对应环节单独重试,这正是这套流水线稳定性的关键设计。
版本号体系:SemVer 与-beta预发布标识
Wave 的版本号遵循语义化版本(SemVer)规范,形如0.14.5、0.11.1-beta.0。当前仓库 package.json 中记录的版本为0.14.5,但版本号只是入口,真正的版本计算逻辑集中在 version.cjs 中。
version.cjs 的版本计算规则
version.cjs 是一个可独立执行的 Node 脚本,其核心逻辑位于 version.cjs#L33-L67:
- 无参数执行:直接输出当前版本,供构建脚本读取。
none:不改变主版本号。若同时传入0/false且当前版本带-beta后缀,则执行semver.inc(VERSION, "patch")将0.11.1-beta.1这类版本提升为0.11.1(先去掉预发布标识,再补一次 patch 递增,避免与已存在的正式版冲突,见 version.cjs#L44-L46)。patch/minor/major:调用semver.inc递增对应段位;当标记为预发布时,会自动加上pre前缀与beta标识,得到0.11.2-beta.0这类版本。true/1:仅递增预发布号,例如0.11.1-beta.0→0.11.1-beta.1,对应 version.cjs#L63 的semver.inc(VERSION, "prerelease", null, "beta")。
脚本执行后会直接改写package.json并输出新版本号。在本地你可以通过 Task 命令复现这一逻辑:
# 输出当前版本 task version # 将版本提升为下一个 patch 预发布版(如 0.14.5 -> 0.14.6-beta.0) task version -- patch true # 把预发布版提升为正式版(如 0.14.6-beta.0 -> 0.14.6) task version -- none falseBump Version 工作流的两个输入
.github/workflows/bump-version.yml 定义了名为Bump Version的手动触发(workflow_dispatch)工作流,提供两个输入:
| 输入项 | 可选值 | 默认值 | 含义 |
|---|---|---|---|
| SemVer Bump | none/patch/minor/major | none | 按语义化版本规则递增主版本号 |
| Is Prerelease | true/false | true | 是否作为预发布版;为true时追加-beta.X后缀,为false时移除该后缀 |
工作流内部通过task version -- <bump> <is-prerelease>计算新版本(见 bump-version.yml#L60-L63),随后使用 GitHub App 签发的 Token 调用 GitHub API 将package.json的版本变更直接提交到受保护分支,并创建v<新版本>的 Git tag。注意这里的提交与 tag 是签名的,且绕过了分支保护——这是通过actions/create-github-app-token换取专用机器人身份实现的(见 bump-version.yml#L31-L39)。
三种典型发布场景
结合 RELEASES.md 的官方说明,版本提升的组合策略如下:
- 正式版发布后开启新一轮预发布:SemVer Bump 设为预期的版本段位(
patch/minor/major),Is Prerelease 设为true。例如从0.14.5得到0.15.0-beta.0。 - 在同一版本号下推进预发布:SemVer Bump 设为
none,Is Prerelease 设为true,将0.11.1-beta.0递增为0.11.1-beta.1。 - 将预发布版提升为正式版:SemVer Bump 设为
none,Is Prerelease 设为false,移除-beta.X后缀。
Build Helper:跨平台构建、签名与产物上传
tag 创建后,Build Helper工作流(.github/workflows/build-helper.yml)会自动启动。它监听v[0-9]+.[0-9]+.[0-9]+*格式的 tag 推送(见 build-helper.yml#L8-L10),并同时支持手动触发。
平台构建矩阵
工作流使用 GitHub Actions matrix 策略并行构建四个目标(见 build-helper.yml#L20-L33):
| 平台 | Runner | 产物 |
|---|---|---|
| darwin | macos-latest | macOS arm64 + x64(zip、dmg) |
| linux (x64) | ubuntu-latest | deb、rpm、snap、AppImage、pacman、zip |
| linux (arm64) | ubuntu-24.04-arm | 上述 Linux 格式的 arm64 版本 |
| windows | windows-latest | nsis、msi、zip |
构建环境使用 Go 1.25.6 与 Node 22(见 build-helper.yml#L13-L14),Linux 与 Windows 平台还会额外安装 Zig 编译器用于 CGO 静态链接。
构建链:task package
每个平台最终执行的构建入口是 Taskfile.yml 中的package任务(见 Taskfile.yml#L124-L132),其内部执行链为:
clean → npm:install → build:backend → build:tsunamiscaffold → npm run build:prod → npm exec electron-builder- build:backend:编译两个 Go 组件——
wavesrv(终端后端服务)与wsh(Wave Shell 客户端)。wsh会为 darwin/linux/windows 三平台共 8 种架构组合并行产出(见 Taskfile.yml#L298-L332)。 - build:tsunamiscaffold:构建 Tsunami 前端脚手架并复制到
dist/tsunamiscaffold。 - npm run build:prod:通过 electron-vite 以生产模式构建 Electron 主进程与前端代码。
- npm exec electron-builder:调用 electron-builder.config.cjs 生成各平台可分发安装包。
签名与公证
RELEASES.md 明确指出,Build Helper 会对 macOS 包进行签名与公证(notarization),并对 Windows 包进行代码签名。具体到工作流实现:
- macOS:通过
CSC_LINK、APPLE_ID、APPLE_APP_SPECIFIC_PASSWORD、APPLE_TEAM_ID等环境变量驱动 electron-builder 完成签名与公证,且由于公证可能偶发失败,Darwin 构建被包在nick-fields/retry中最多重试 3 次(见 build-helper.yml#L135-L150)。 - Windows:使用 DigiCert Signing Manager(Keylocker)方案,先安装 Keylocker KSP 工具链,再通过
SM_CODE_SIGNING_CERT_SHA1_HASH等变量配置签名(见 build-helper.yml#L98-L125)。这一变量同时被 electron-builder.config.cjs#L6-L7 读取,决定是否启用 Windows 签名。
产物去向
构建完成后,产物被上传到两个位置(见 build-helper.yml#L161-L173):
- S3 staging 桶:
s3://waveterm-github-artifacts/staging-w2/<version>,对应 Taskfile.yml 中ARTIFACTS_BUCKET: waveterm-github-artifacts/staging-w2(见 Taskfile.yml#L13)与artifacts:upload任务。 - GitHub Actions Artifact:作为
create-release作业的输入,用于组装草稿 Release。
create-release作业随后将所有平台的 zip、dmg、exe、msi、rpm、deb、pacman、snap、AppImage 等产物挂载到一个draft(草稿)Release上,并依据 tag 是否包含-beta标记该 Release 为预发布(见 build-helper.yml#L192-L198)。
本地复现打包:task package与构建前置条件
如果你希望在本地复现 Build Helper 的打包过程,可以参考 BUILD.md 准备环境并执行:
# 首次克隆后安装依赖(npm install + go mod tidy + docs 依赖) task init # 生产构建并打包,产物输出到 make/ 目录 task package需要注意的本地构建约束:
- Linux:需要安装
zip、Zig 编译器;若要在 ARM64 上打包,还需通过 Gem 安装fpm(因为 electron-builder 内置的 FPM 版本不含 Linux ARM 目标)。对应命令为USE_SYSTEM_FPM=1 task package。 - Windows:同样需要 Zig 编译器用于 CGO 静态链接。
- NodeJS:要求 22 LTS;Task 通过
arduino/setup-task在 CI 中安装,本地则需自行安装 Task 命令行工具。
package任务在 CI 中还额外设置了USE_SYSTEM_FPM: true与SNAPCRAFT_BUILD_ENVIRONMENT: host环境变量,以使用系统级 FPM 与宿主环境构建 Snap(见 build-helper.yml#L128-L133)。
测试新版本:草稿 Release 与本地下载
Build Helper 完成时会在 GitHub Releases 页面生成草稿,维护者可以据此下载产物进行本地测试。除从网页下载外,仓库还提供了 Task 任务从 S3 staging 桶拉取全部产物:
# 下载指定版本的产物到 artifacts/<version>/ task artifacts:download:<version> -- --profile <aws-profile>该任务需要本地配置一个对 S3 桶具有读写权限的 AWS CLI profile。其实现位于 Taskfile.yml#L391-L399,会先将本地artifacts/<version>目录清空,再执行aws s3 cp s3://waveterm-github-artifacts/staging-w2/<version>/ ... --recursive。
发布正式版本:Publish Release 工作流
测试通过后,维护者需要:
- 在 Releases 页面找到对应版本的草稿;
- 点击右上角铅笔图标编辑草稿,完善 Release 说明与变更日志;
- 滚动到底部点击Publish发布。
Publish 动作会触发 .github/workflows/publish-release.yml(监听release: [published]事件),此后一切自动化、无需人工介入:
- 发布到 release 桶:执行
task artifacts:publish:<version>,将产物从staging-w2/<version>拷贝到dl.waveterm.dev/releases-w2(RELEASES_BUCKET,见 Taskfile.yml#L14)。release 桶正是 electron-updater 与各包管理器读取的官方发布源。 - 发布 Snap:分别对 amd64 与 arm64 的
.snap文件执行snapcraft upload。预发布版仅发布到beta频道,正式版同时发布到beta与stable频道(频道判定逻辑见 Taskfile.yml#L421)。 - 提交 WinGet 更新:仅对正式版执行
wingetcreate update CommandLine.Wave,向 WinGet 仓库提交版本升级 PR(见 publish-release.yml#L77-L96)。
自动更新机制:electron-updater 与双频道发布
Wave 使用electron-updater,其默认依赖已声明在 package.json 的electron-updater条目中。
更新元数据与检查频率
RELEASES.md 说明:每次发布都会生成指向当前频道最新版本的YAML 文件(包含文件大小与校验和),应用每小时检查一次这些文件。源码实现印证了这一点:
- electron-builder.config.cjs#L17 设置
generateUpdatesFilesForAllChannels: true,为每个频道生成更新元数据文件。 - electron-builder.config.cjs#L120-L123 将
publish.provider配置为generic,URL 指向https://dl.waveterm.dev/releases-w2——即 release S3 桶。 - emain/updater.ts#L126-L134 中,
start()每隔 600000ms(10 分钟)检查一次定时器是否到期,真正执行检查时再校验autoupdate:intervalms(默认 3600000ms,即 1 小时)是否已过去,兼顾了系统休眠导致定时器失效的场景。
更新渠道与防降级
Wave 的 beta / stable 双渠道完全由版本号驱动:带-beta.X后缀的版本进入beta渠道,正式版本进入latest渠道。渠道解析逻辑位于 emain/updater.ts#L19-L36:
- 读取应用资源目录下的
app-update.yml(由 electron-builder 在打包时生成)获取内置渠道; - 读取用户配置
autoupdate:channel; - 若用户尚未设置该配置,或用户配置为
latest但实际安装的是beta构建,则自动将配置写为beta——这是为了防止用户从 beta 版降级回稳定版而丢失数据。
用户侧配置项
RELEASES.md 提到用户可通过autoupdate:channel设置选择更新渠道。完整的相关配置项记录在 docs/docs/config.mdx 的配置总表中:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
autoupdate:enabled | bool | true | 是否启用更新检查(需重启应用生效) |
autoupdate:intervalms | float64 | 3600000 | 两次更新检查之间的等待毫秒数(需重启应用生效) |
autoupdate:installonquit | bool | true | 退出时是否自动安装已下载的更新(需重启应用生效) |
autoupdate:channel | string | "latest" | 更新渠道:"latest"(稳定版)或"beta"(更新更频繁) |
emain/updater.ts的构造函数完整消费了这四个配置:intervalms控制检查频率、autoCheckEnabled控制总开关、autoInstallOnAppQuit传给autoUpdater(见 emain/updater.ts#L47-L62)。下载完成后应用会弹出系统通知,点击后弹出"Restart / Later"对话框,用户确认重启即完成安装(见 emain/updater.ts#L181-L210)。
支持自动更新的包格式
自动更新仅对特定分发格式生效:DMG、AppImage、RPM、DEB,而 Windows 的所有分发格式(nsis、msi 等)均支持自动更新。这与 electron-builder.config.cjs 中配置的构建目标一一对应(mac 产 dmg/zip、linux 产 deb/rpm/snap/AppImage/pacman、win 产 nsis/msi/zip)。
包管理器多渠道分发
除应用内更新外,Wave 还将正式版同步到主流包管理器,形成多渠道分发矩阵:
Homebrew(macOS)
Homebrew 维护着一个Autobump 机器人,会定期检查 Wave 的发布源,发现新的正式版后自动更新 Cask 配方。RELEASES.md 说明项目通过在 autobump 名单中登记来启用这一机制,Cask 配方为wave.rb。
WinGet(Windows)
WinGet 通过 PR 管理包版本。Wave 在 Publish Release 工作流中调用微软提供的wingetcreate工具(对应 Taskfile.yml#L430-L437 的artifacts:winget:publish:*任务),以专用机器人账号向 WinGet 仓库提交升级 PR,通常一天内被合并。该任务对预发布版直接跳过(通过 status 退出码控制),仅正式版才会提交。
Chocolatey(Windows)
Chocolatey 社区提供了一套 PowerShell 模块用于发布。Wave 的做法是维护一个独立的wavetermdev/chocolatey仓库,其中包含发布脚本与每日运行的工作流:每天检查新版本、校验 SHA、验证安装可行性,然后推送新版本并回写更新后的包清单。由于社区审核周期较长,更新通常需要数周才会被接受。
Snap(Linux / macOS)
Snap 通过snapcraft构建并发布到 Snap Store。Publish Release 工作流对所有 beta 与正式版执行该步骤(见 .github/workflows/publish-release.yml 中的publish-snap-amd64与publish-snap-arm64作业):beta 版只发布到beta频道,正式版发布到beta+stable两个频道,且即时生效。Snap 的打包参数(core22基础、classic 限制模式、原生 Wayland 支持)定义在 electron-builder.config.cjs#L110-L115。
electron-builder 配置深度解读
electron-builder.config.cjs 是打包的最终裁决者,RELEASES.md 特别强调了两处非常规配置:
Go 二进制必须排除出 ASAR
ASAR 归档内的文件对 NodeJS 而言不是真正的"文件",无法通过 Shell 命令直接执行。由于 Wave 的wavesrv、wsh是需要在运行时被 Electron 主进程以子进程方式调用的 Go 原生二进制,它们必须留在 ASAR 之外。配置如下(见 electron-builder.config.cjs#L43-L46):
asarUnpack: [ "dist/bin/**/*", // wavesrv 和 wsh 二进制 "dist/schema/**/*", // Monaco 编辑器使用的 schema 文件 ],同时files过滤规则也做了配合:dist/bin目录只打包当前架构的wavesrv.${arch}*与所有wsh*,排除掉其他架构的冗余二进制(见 electron-builder.config.cjs#L21-L26)。
node_modules 整体排除
由于 Vite 已在构建期将前端依赖全部打包进产物,electron-builder.config.cjs通过"!node_modules"将整个node_modules排除在打包之外(见 electron-builder.config.cjs#L32),同时关闭了npmRebuild、nodeGypRebuild。唯一例外是monaco-editor——它体积庞大且包含 web worker,需要保留以便按需加载。
其他关键配置
- 产物命名:
${productName}-${platform}-${arch}-${version}.${ext},Linux 与 Snap 使用单独的命名模板(见 electron-builder.config.cjs#L16、#L79、#L114)。 - macOS 目标:zip + dmg,arm64 与 x64 双架构;最低系统版本 10.15.0;通过
extendInfo声明了联系人、相机、麦克风、日历、位置等隐私权限用途文案(见 electron-builder.config.cjs#L47-L77)。 - Linux 目标:zip、deb、rpm、snap、AppImage、pacman 六种格式;desktop 条目关键词为
developer;terminal;emulator;;并通过--enable-features UseOzonePlatform --ozone-platform-hint auto参数启用原生 Wayland 支持(见 electron-builder.config.cjs#L78-L94)。 - RPM 构建细节:通过 fpm 参数
--rpm-rpmbuild-define _build_id_links none移除/usr/lib/.build-id/链接,避免与其他 Electron 应用(如 Slack)产生冲突(见 electron-builder.config.cjs#L116-L119)。 - macOS 通用二进制权限修复:
afterPack钩子会在打包 macOS universal 二进制后,将app.asar.unpacked/dist/bin下所有wavesrv*文件的权限恢复为0o755(rwxr-xr-x),规避通用二进制打包导致的权限丢失问题(见 electron-builder.config.cjs#L124-L140)。 - extraResources:将 Tsunami 脚手架(
dist/tsunamiscaffold)作为额外资源随应用分发(见 electron-builder.config.cjs#L34-L39)。
结语
Wave Terminal 的发布体系是一个"小团队 + 强自动化"的工程范本:版本号由单一脚本(version.cjs)驱动、构建由单一入口(task package)驱动、分发由 GitHub Release 事件驱动,S3 staging/release 双桶隔离了"构建验证"与"对外发布"两个阶段,而双频道更新机制则让 beta 用户与稳定版用户各取所需。如果你正在为自己的 Electron 项目设计发布流水线,不妨以 RELEASES.md、Taskfile.yml、electron-builder.config.cjs 三份文件为起点,逐段对照本文复刻一套属于你自己的版本发布闭环。
【免费下载链接】wavetermAn open-source, AI-integrated, cross-platform terminal for seamless workflows项目地址: https://gitcode.com/GitHub_Trending/wa/waveterm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考