发布体系:桌面、移动端、Web 与 OTA 如何同时交付
一、发布是架构的一部分
根目录的 workflow 不只是 CI 检查,还定义了版本、制品、渠道和平台责任:
build-desktop.yml/ 相关工作流:Electron 构建、商店和发布制品;build-android.yml、build-ios.yml:移动原生构建;publish-ota.yml:移动 JS bundle、manifest、策略和同步;tag.yml:从分支和版本信息创建 tag、触发构建;sync.yaml:release 分支同步回 dev;- PR 质量、翻译和相似 issue 工作流:社区与维护流程。
这说明 Folo 的版本不是一个 npm version 能解释的字符串,而是平台化的 release graph。
二、版本的三层语义
应用版本
例如 apps/desktop/package.json 的 version、runtimeVersion 和 mainHash 服务于桌面构建、运行时和代码完整性。
原生运行时版本
Expo OTA 不能更新原生模块不支持的 JS。runtime_version 是兼容性闸门,必须和 bundle 的发布版本分开。
分发渠道
OTA workflow 明确接收 production 或 preview channel。它影响 manifest、下载地址和更新策略,不应隐含在代码分支命名里。
三、OTA workflow 的安全链
publish-ota.yml 的核心过程是:
- 接收 release、runtime、channel 输入;
- 检查对应 mobile tag 存在;
- checkout tag;
- 安装 pnpm/Node 依赖;
expo export生成 bundle;- 构建
ota-release.json和压缩资产; - 上传 GitHub Release 资产;
- 调用同步端点通知 OTA 服务。
这样做比“直接从 main 打包”更可靠:发布内容由不可变 tag 固定,workflow 的输入决定运行时和渠道,制品可回溯到源码。
四、OTA 服务端的职责
apps/ota/src 按 route/lib 拆分,包含 manifest、versions、policy、download、assets、internal、store-version、code-signing、GitHub、R2 和 KV 适配。
OTA Worker 不只是一个文件下载器,它还要回答:
- 哪个客户端/平台可以看到哪个版本?
- 当前 runtime 是否兼容?
- 版本是否被 policy 禁用或强制升级?
- 资产来自 GitHub Release 还是 R2?
- 下载是否需要签名/校验?
- 同步操作是否可重试且幂等?
五、制品证明与签名
README 提到代码签名、Apple notarization、SignPath 和 GitHub artifact attestations。对桌面和移动客户端,构建成功并不等于用户能安全安装;发布链还要证明:
- 谁构建了制品;
- 制品来自哪个 commit/tag;
- 哪个平台签名;
- 更新下载的是不是 workflow 生成的文件。
六、为什么 tag workflow 需要分支语义
tag.yml 监听 main 和 mobile-main,根据分支和 release 信息决定 desktop/mobile 平台,再触发对应构建。sync.yaml 反过来把 release 分支的提交同步到 dev。
分支名在这里是产品发布状态的一部分,不只是 Git 协作偏好。改动发布分支、tag 规则或 workflow 输入时,必须把它当作状态机来评估。
七、Docusaurus 站点的发布
本拆解仓库使用独立的 deploy.yml:
- main push 或手动触发;
- Node 20 +
npm ci; npm run build生成静态build;upload-pages-artifact上传构建产物;deploy-pages发布到 GitHub Pages。
仓库设置需要将 Pages Source 设为 GitHub Actions。站点配置中的 url、baseUrl 和 projectName 必须与实际仓库名一致,否则静态资源会出现 404。
八、生产化检查清单
- tag 是否指向预期 commit;
- native runtime 和 OTA bundle 是否兼容;
- release asset 是否可下载、可校验;
- workflow 权限是否最小化;
- Pages 是否使用 Actions source;
- Docusaurus 的 baseUrl 是否与 repository name 一致;
- 构建失败时是否保留日志和可重试入口;
- 回滚是回到旧 tag、旧 bundle 还是旧 policy。