SeatFlow GitHub Actions 工作流详解:单元测试、工作流 YAML 校验、多平台构建、发布与 OSS 分发、delta 链与缓存、WASM 在线版发布、Worker 密钥轮换
本页目录
全部文档
CI/CD 工作流
SeatFlow 使用 GitHub Actions 实现持续集成与持续发布。仓库中共有六条工作流,覆盖代码校验、工作流 YAML 校验、多平台构建、桌面发布与 OSS 分发、WASM 在线版发布和 Worker 密钥轮换。
工作流总览
| 文件 | 触发条件 | 用途 |
|---|---|---|
unit-tests.yml |
push(.cs/.csproj/.slnx/.axaml 路径);pull_request(全量);workflow_call |
全量构建 + 条件测试(含 WASM 目标构建) |
release.yml |
push 到 main 且仅 version.json 变更;手动触发 |
版本预检 → 4 平台构建(稳定版:OSS 同步打包历史 + Action 缓存;预发布:拉取最新 full 基线)→ vpk 打包 → 暂存 artifacts |
actionlint.yml |
push / pull_request(.github/workflows/** 变更) |
校验工作流 YAML(钉版 actionlint 1.7.7 + SHA256 校验) |
publish.yml |
workflow_run(Release SeatFlow 完成后);手动触发 |
下载产物 → 创建 GitHub Release(稳定版同步 OSS) |
publish-web.yml |
workflow_run(Publish SeatFlow 完成后,main) |
正式版 WASM 构建 → OSS 上传 → KV 切换 → 清理旧版本 |
worker-secret-sync.yml |
每周一 03 UTC;手动触发 | Cloudflare Worker OSS 密钥轮换,失败自动开 Issue |
push version.json (main)
→ release.yml(preflight → build: win/linux/macos × vpk;稳定版 OSS 历史同步 + 缓存)
→ workflow_run → publish.yml(GitHub Release + OSS)
→ workflow_run → publish-web.yml(仅正式版 → online.seatflow.work)
手动:
Actions UI → release.yml(workflow_dispatch,按 pre-release 构建)
Actions UI → publish.yml(workflow_dispatch,选择构建 run,始终 pre-release)
1. 单元测试工作流 (unit-tests.yml)
触发条件
on:
push:
paths: ['**.cs', '**.csproj', '**.slnx', '**.axaml']
pull_request:
workflow_call:
- push:任何 C# 源码(
.cs)、项目文件(.csproj、.slnx)或 Avalonia 标记文件(.axaml)的变更都会触发;路径过滤避免 README/文档变更浪费 CI。 - pull_request:不加路径过滤,所有 PR 全量触发。
- workflow_call:作为可复用工作流供其他工作流调用。
并发控制
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
同一分支/PR 上如果有新提交,旧的运行中 job 会被自动取消,节省 runner 时间。
增量测试策略
工作流先安装 wasm-tools 并执行一次全量 dotnet build(SeatFlow.Browser 在解决方案内,构建需要该 workload),再用 dorny/paths-filter@v4 按变更路径条件运行测试:
| 变更路径 | 运行的测试 |
|---|---|
src/SeatFlow.Core/** |
Core.Tests |
src/SeatFlow.Infrastructure/** |
Infrastructure.Tests |
src/SeatFlow.Application/** |
Application.Tests |
tests/SeatFlow.Core.Tests/** |
Core.Tests |
tests/SeatFlow.Infrastructure.Tests/** |
Infrastructure.Tests |
tests/SeatFlow.Application.Tests/** |
Application.Tests |
依赖传递规则:修改 Core 层会同时触发 Infrastructure 和 Application 的测试(因为上层依赖 Core);修改 Infrastructure 层同理会触发 Application 测试。这体现在 job 的条件判断中:
# Core.Tests 运行条件
- if: steps.filter.outputs.core == 'true'
|| steps.filter.outputs.infra == 'true'
|| steps.filter.outputs.tests-core == 'true'
# Infrastructure.Tests 运行条件
- if: steps.filter.outputs.core == 'true'
|| steps.filter.outputs.infra == 'true'
|| steps.filter.outputs.tests-infra == 'true'
# Application.Tests 运行条件
- if: steps.filter.outputs.core == 'true'
|| steps.filter.outputs.infra == 'true'
|| steps.filter.outputs.app == 'true'
|| steps.filter.outputs.tests-app == 'true'
执行流程
checkout (fetch-depth: 0)
→ 路径过滤 (dorny/paths-filter)
→ setup-dotnet 10.0.x
→ 安装 wasm-tools workload
→ dotnet build (全量)
→ dotnet test --no-build (按条件逐项目运行)
Presentation.Tests 暂未纳入 CI:过滤器中有
pres(src/SeatFlow.Presentation.Avalonia/**)但工作流只运行 Core/Infrastructure/Application 三个测试项目,tests/SeatFlow.Presentation.Tests(Headless UI,67 用例)当前仅参与全量构建验证。因此改动 UI 层后必须在本地执行全量dotnet test(4 个项目共 448 用例)。
2. 发布构建工作流 (release.yml)
触发条件
on:
push:
branches: ['main']
paths: ['version.json']
workflow_dispatch:
inputs:
version: # 覆盖 version.json 的发布版本号
suffix: # 可选后缀(beta.1 / rc),自动按 pre-release 处理
- 自动触发:向
main分支推送仅version.json的变更(版本号 bump)——其他文件变更不会触发发布。 - 手动触发:
workflow_dispatch指定版本号与可选后缀,作为预发布构建。
Job 0: preflight — 版本预检
- 禁止发布已存在的 Release tag(
v{version})。 - 版本号必须大于当前最新正式 Release(
packaging.version.Version比较),否则中止且不触发构建。
Job 1: build — 多平台构建 + Velopack 打包
矩阵策略(4 个 RID,fail-fast: false):
| RID | 运行环境 | 入口/图标 | 产物 |
|---|---|---|---|
win-x64 |
ubuntu-latest | SeatFlow.exe / installer.ico |
SeatFlow-{ver}-Setup.exe |
linux-x64 |
ubuntu-latest | SeatFlow / SF_icon_mini_background.png |
SeatFlow-{ver}-linux-x64.AppImage |
osx-x64 |
macos-latest | SeatFlow / 运行时由 PNG 生成 installer.icns |
.pkg / .dmg |
osx-arm64 |
macos-latest | 同上 | .pkg / .dmg |
执行步骤:
checkout → setup-python → setup-dotnet 10.0.x
→ 安装系统依赖 (Linux: squashfs-tools, zstd / macOS: zstd)
→ 安装 vpk(钉版 1.2.161,与客户端 Velopack 包一致)
→ 【稳定版】restore 打包历史缓存 → pip install oss2 → 从 OSS 同步历史到 publish/history
→ dotnet publish (自包含, -r {rid}, -o publish/tmp)
→ 【预发布】拉取上一版本做 delta 基线 (scripts/ci/fetch_previous.sh,读 UPDATE_FEED_URL)
→ (macOS) 由 PNG 生成 icns
→ vpk pack(稳定版输出 publish/history;预发布输出 publish/out)
→ 暂存本次产物到 publish/dist(稳定版随后 save 打包历史缓存)
→ 上传 artifacts (publish/dist/*)
vpk pack 关键参数:
| 参数 | 说明 |
|---|---|
--packId SeatFlow |
包 ID |
--packVersion |
取 preflight outputs,保证整条链版本一致 |
--channel {rid} |
每个 RID 独立更新通道 |
--delta BestSpeed |
Zstandard 增量;BestSize(bsdiff)与客户端 Update.exe 不兼容 |
--noPortable |
仅 Windows:不生成便携版 |
--keyId/--keyFile/--keyPassword |
可选签名参数,secrets 为空时跳过签名 |
产物暂存与上传:pack 完成后由 scripts/ci/stage_velopack_artifacts.sh 把本次版本的安装包、SeatFlow-{ver}-{rid}-full/delta.nupkg 与 channel 文件复制到 publish/dist/,artifacts 只上传 publish/dist/*(if-no-files-found: error 保证产物缺失时报错);稳定版随后从历史目录清理安装包、Portable.zip、assets.*.json,保持缓存精简。
增量链与打包历史(稳定版)
vpk pack 写出的 releases.{channel}.json 会列出打包目录中的全部 nupkg,客户端据此把「本机版本 → 目标版本」之间的 delta 按版本顺序串联应用(超过 10 跳或 delta 总大小超过 full 时退回全量)。因此要让落后多个版本的用户也能增量更新,打包目录必须保留历史 delta:
- OSS 为唯一真源:稳定版构建在 pack 前运行
scripts/ci/sync_velopack_history.py,把updates/下本通道的 nupkg 同步到publish/history——保留全部历史 delta + 最新 2 个 full,并做双向对账(下载缺失/大小不符者并校验 size + MD5/ETag;删除本地不在保留集的 nupkg,防止未发布版本混入 feed)。 - Action 缓存仅加速:
publish/history通过actions/cache的 restore/save 子动作跨运行缓存(key 含run_id/run_attempt,restore-keys 按通道前缀滚动);缓存失效时自动从 OSS 全量回填;保存失败(如超限)不阻塞发布。 - 失败策略:同步失败硬失败(本次不发版),避免发布断链的 feed。
- delta 生成:每次 pack 只生成「上一版 full → 新版本」一个 delta;缺口(有 full 但无指向它的 delta)仅记录在同步日志中,不回填历史。
2026-09 之前 Worker 对
.nupkg直链返回 403,fetch_previous一直静默失败,因此 OSS 上 1.4.x / 2.0.0 没有任何历史 delta;增量链自 2.1.0 起积累。
该工作流不创建 GitHub Release、不上传 OSS——发布动作由
publish.yml接力完成。
3. 发布工作流 (publish.yml)
触发条件
on:
workflow_run:
workflows: ['Release SeatFlow']
types: [completed]
workflow_dispatch:
inputs:
run_id: # 使用哪个 release.yml 构建运行(默认最新成功运行)
version: # 与构建一致的版本号
suffix: # 可选后缀
| 模式 | 条件 | 行为 |
|---|---|---|
| 自动 | workflow_run.event == 'push' 且构建成功 |
读取 version.json(与构建同源),正式版发布 latest 并同步 OSS |
| 手动 | workflow_dispatch |
必须指定构建运行,始终按 pre-release 处理,不上传 OSS |
非 push 触发的构建(如手动构建)完成时也会唤醒本工作流,但会被 if 条件直接跳过,避免红色失败记录。
执行步骤
checkout → setup-python
→ 解析元数据 (run_id / version / is_pre)
→ Pre-flight:tag 不得存在、版本必须大于最新正式 Release
→ 下载 artifacts (pattern: release-*,merge-multiple)
→ 版本化安装程序文件名 (SeatFlow Setup.exe → SeatFlow Setup-{ver}.exe;已含版本号则跳过)
→ 校验 RELEASE.md 存在
→ 组装 Release 正文:RELEASE.md + SHA256 校验表
→ 上传 OSS(仅自动 push + 稳定版;见 scripts/ci/upload_oss.py)
→ gh release create v{version}(稳定版=latest,预发布=--prerelease)
OSS 上传:仅当自动链路且版本号不含 - 时执行;凭证缺失时跳过 OSS 仅创建 GitHub Release。
发布触发链路
修改 version.json → push main
→ release.yml 构建(preflight + vpk)
→ publish.yml 自动发布 latest + OSS
→ publish-web.yml 自动发布在线版(仅正式版)
或手动:
Actions UI → release.yml(workflow_dispatch)→ publish.yml(选 run,pre-release)
4. 在线版发布工作流 (publish-web.yml)
桌面正式版发布成功后自动接力,将 WASM 静态站发布到 https://online.seatflow.work。
门禁 gate
- 仅当上游
Publish SeatFlow是 push 触发的自动链路成功运行时放行(conclusion == 'success' && event == 'workflow_run')。 - 读取
version.json,版本号含-(预发布)则stable=false,deploy-web跳过。 - 手动发布不进入本流水线,未发布的 wasm 由开发者本地自测。
部署 deploy-web
checkout (head_sha) → setup-python/dotnet
→ 安装 wasm-tools workload
→ dotnet publish src/SeatFlow.Browser -c Release -o web-out
→ 产物校验:index.html / dotnet.js 存在,
dotnet.native.*.wasm > 5MB 且日志无 "won't be linked in"(否则白屏)
→ 上传 OSS online_worktable/<version>/(原文件 + .br,--no-switch)
→ 读取当前 KV current(记录回滚版本)
→ KV 原子切换到新版本(--switch-only)
→ 清理旧版本 --keep 5(current 指向的版本永不删除)
→ 失败时打印回滚命令
回滚(秒级,需 Cloudflare 凭证):
CF_ACCOUNT_ID=... CF_API_TOKEN=... \
python3 scripts/ci/upload_web_oss.py --version <旧版本号> --switch-only
5. Worker 密钥同步 (worker-secret-sync.yml)
- 触发:每周一 03 UTC(北京时间 11)定时 + 手动触发;
concurrency不取消进行中的运行。 - 动作:执行
scripts/ci/rotate_worker_secrets.py,轮换 Cloudflare Worker 使用的 OSS 密钥。 - 失败处理:
if: failure()时自动创建带ops标签的 Issue(含运行链接),便于追踪。
6. 工作流 YAML 校验 (actionlint.yml)
- 触发:push / pull_request 且变更命中
.github/workflows/**。 - 动作:下载钉版 actionlint 1.7.7(SHA256 校验)后运行
./actionlint -shellcheck= -pyflakes= -color .github/workflows/*.yml。 - 作用:拦截「YAML 合法但 Actions 拒绝」的写法(典型如表达式中未加引号的
#被当成注释截断,导致整个运行以 workflow file issue 失败且不启动任何 job)。
发布门禁
release.yml的preflightjob 保证:tag 不重复、版本单调递增。publish.yml在下载产物前重复同样的 tag/版本预检,避免旧构建被误发布。- 分支保护建议将
unit-tests的 job 设为 required status check,保证main合并前构建与测试通过。
版本号管理
发布链路由 version.json 驱动,平时由 scripts/version.py 管理:
# 查看当前版本
python3 scripts/version.py show
# bump 补丁版本(同步 version.json / about.json 等 15+ 处)
python3 scripts/version.py bump-app patch --force
version.json中的commitId/buildDate由构建/发布脚本生成,不要手动修改。本地dotnet r任务仅限开发使用,禁止在 CI/CD 中调用。
环境变量与密钥
GitHub Secrets
| 密钥 | 用途 | 工作流 |
|---|---|---|
OSS_KEY_ID |
阿里云 OSS AccessKey ID | release(稳定版历史同步)/ publish / publish-web / worker-secret-sync |
OSS_KEY_SECRET |
阿里云 OSS AccessKey Secret | 同上 |
CF_ACCOUNT_ID |
Cloudflare 账号 ID(KV / Worker API) | publish-web / worker-secret-sync |
CF_API_TOKEN |
Cloudflare API Token | publish-web / worker-secret-sync |
VPK_KEY_ID / VPK_KEY_FILE / VPK_KEY_PASSWORD |
Velopack 代码签名(可选,缺省跳过签名) | release |
GitHub Variables
| 变量 | 用途 | 工作流 |
|---|---|---|
OSS_ENDPOINT |
OSS endpoint(如 oss-cn-hongkong.aliyuncs.com) |
release / publish / publish-web / worker-secret-sync |
OSS_BUCKET |
OSS bucket 名称 | release / publish / publish-web / worker-secret-sync |
UPDATE_FEED_URL |
预发布 delta 基线更新源(稳定版已由 OSS 同步取代) | release |
CF_WORKER_SCRIPT |
需要轮换密钥的 Worker 脚本名 | worker-secret-sync |
CF_API_BASE |
Cloudflare API 基础地址(可选覆盖) | worker-secret-sync |
release.yml的 build job 直接读取仓库级OSS_KEY_ID/OSS_KEY_SECRET(secrets)与OSS_ENDPOINT/OSS_BUCKET(vars)做历史同步;publish.yml仍在 EnvironmentOSS中执行上传(该环境 branch policy 仅允许main),两者互不影响。
工作流级环境变量
env:
DOTNET_NOLOGO: 'true' # 禁用 .NET 横幅
DOTNET_CLI_TELEMETRY_OPTOUT: 'true' # 禁用遥测
PROJECT_PATH: 'src/SeatFlow.Desktop'
CONFIGURATION: 'Release'
手动触发发布
发布(publish.yml)
- 进入仓库 Actions 页面,选择 Publish SeatFlow workflow
- 点击 Run workflow,填写
run_id(可留空取最新成功构建)、version、可选suffix - 手动发布始终为 pre-release 且不上传 OSS
构建(release.yml)
手动触发 Release SeatFlow 并填写版本号/后缀,用于构建验证或预发布产物;构建成功后自动链路的 publish.yml 会跳过手动构建(避免误发 latest)。
故障排查
release.yml:vpk 命令不可用
错误信息:"dotnet tool restore" to make the "vpk" command available.
原因:仓库 dotnet-tools.json 中声明了 vpk 为本地工具,但 CI 未 restore。解决:workflow 中已使用 dotnet tool install -g vpk --version 1.2.161 全局安装钉版(与客户端 Velopack 包版本一致),并直接调用 vpk(而非 dotnet vpk)绕过本地 manifest 解析。
release.yml:稳定版同步打包历史失败
Sync Velopack history from OSS 步骤非零退出会硬失败、不发版。检查仓库级 OSS_KEY_ID/OSS_KEY_SECRET(secrets)与 OSS_ENDPOINT/OSS_BUCKET(vars)是否有效;可本地用主仓库 scripts/ci/sync_velopack_history.py --dry-run 预演。
Delta 未生成或链不完整
同步日志若列出「缺口(有 full 无 delta)」,说明对应历史版本没有 delta(不会自动回填)。仅当上一版 full 存在于打包目录时,vpk pack 才会生成 prev → 新版本 的 delta;客户端因链路超过 10 跳或缺口而退回全量下载属预期行为。
publish.yml:文件未附带版本号
OSS/GitHub Release 的安装程序文件名缺少版本号(如 SeatFlow Setup.exe 而非 SeatFlow Setup-2.1.0.exe)。
原因:曾缺失版本号补齐步骤。已通过 “Version installer filenames” 步骤修复——对不含版本号的文件名自动插入版本号,已含版本号的跳过。
publish-web.yml:白屏 / 部署异常
dotnet.native.*.wasm小于 5MB 或日志出现won't be linked in→ 构建缺少wasm-tools(workflow 已前置校验)。- 线上冒烟已移除:Cloudflare Bot 管理会对 GitHub Runner 的请求返回 403,轮询
X-Online-Version无法自证版本且会空转超时;部署正确性改由上传完整性校验(key/大小全量比对)与 KV 写入成功保证。 - 部署后站点仍显示旧版本 → 检查 KV
current是否写入成功、Worker 是否正常与传播延迟;失败时 workflow 会打印上一版本的回滚命令。 br协商或 MIME 校验失败(人工验证时)→ 检查 Worker 回源与 OSS 对象是否完整。
worker-secret-sync.yml:密钥轮换失败
工作流会自动创建 ops 标签的 Issue 并附运行链接;检查 Cloudflare / OSS 凭证配置与 CF_WORKER_SCRIPT 变量是否覆盖目标 Worker。
UI 改动未触发测试
unit-tests.yml 当前不运行 Presentation.Tests(见第 1 节警告);若希望纳入,需要在 workflow 中新增 dotnet test tests/SeatFlow.Presentation.Tests --no-build 步骤并在路径过滤中加入对应条目。
相关文档
- 构建与发布 (13-building-publishing.md) — 本地构建命令、发布脚本、版本真相源
- 版本号与迁移 (10-version-migration.md) — version.py 使用、迁移系统
- 测试指南 (12-testing.md) — 4 个测试项目、Headless UI 与脚本测试
- 主仓库
.github/docs/RELEASE_FLOW.md— 发布流水线权威说明(delta 链、缓存、Secrets/Vars) - 主仓库
docs/WebDeployment.md— 在线版托管、KV 切换与排障