发布
当变更合并到主分支时,Tuist 会自动发布新版本。CLI、服务器和 Kura 缓存服务器都通过发布渠道(canary、候选版本、稳定版)进行交付,以确保推荐的安装方式保持稳定且更新节奏平缓;生成候选版本并将其提升为稳定版是每周有计划地执行的步骤。应用持续发布(iOS 应用仅持续部署到 TestFlight,详见此处)。
概览#
我们发布以下主要组件:
- Tuist CLI - 命令行工具,通过 canary、候选版本和稳定渠道交付
- Tuist Server - 后端服务,采用与 CLI 相同的渠道模型交付
- Kura - 缓存服务器,采用与 CLI 相同的渠道模型交付
- Tuist App - macOS 和 iOS 应用,持续发布(iOS 应用仅持续部署到 TestFlight,详见此处)
每次推送到 main 都会为 CLI、服务器和 Kura 发布 canary 预发布版本。只有当维护者将经过充分测试的候选版本提升为稳定版时,才会为这些组件中的每一个切割稳定版本,并且每周的计划会同步触发所有三个发布流程(参见发布渠道)。应用在每次推送到 main 时都会发布稳定版本。
工作原理#
1. 提交规范#
我们使用约定式提交来组织提交信息。这使得我们的工具能够理解变更的性质、确定版本升级并生成相应的变更日志。
格式:type(scope): description
提交类型及其影响#
| 类型 | 描述 | 版本影响 | 示例 |
|---|---|---|---|
feat | 新功能或能力 | 次版本号升级 (x.Y.z) | feat(cli): add support for Swift 6 |
fix | 错误修复 | 修订版本号升级 (x.y.Z) | fix(app): resolve crash when opening projects |
docs | 文档变更 | 不发布 | docs: update installation guide |
style | 代码风格变更 | 不发布 | style: format code with swiftformat |
refactor | 代码重构 | 不发布 | refactor(server): simplify auth logic |
perf | 性能改进 | 修订版本号升级 | perf(cli): optimize dependency resolution |
test | 测试新增/变更 | 不发布 | test: add unit tests for cache |
chore | 维护任务 | 不发布 | chore: update dependencies |
ci | CI/CD 变更 | 不发布 | ci: add workflow for releases |
对于 CLI,这些升级决定了 canary 版本和变更日志分组。稳定的 CLI 版本是手动切割的次版本线 (X.Y.0),而稳定修订版 (X.Y.Z) 仅来自向后移植,因此 feat 或 fix 会通过下方的渠道流程触达 CLI 用户,而不是立即作为稳定版本发布。
破坏性变更#
破坏性变更会触发主版本号升级 (X.0.0),并应在提交正文中注明:
feat(cli): change default cache location
BREAKING CHANGE: The cache is now stored in ~/.tuist/cache instead of .tuist-cache.
Users will need to clear their old cache directory.
2. 变更检测#
每个组件都使用git cliff来:
- 分析自上次发布以来的提交
- 按范围过滤提交(cli、app、server)
- 确定是否存在可发布的变更
- 自动生成变更日志
3. 发布流水线#
当检测到可发布的变更时:
- 版本计算:流水线确定下一个版本号
- 变更日志生成:git cliff 根据提交信息创建变更日志
- 构建流程:构建并测试组件
- 制品生成:生成特定于发布的资产,例如 CLI 捆绑包、校验和以及通过
tuist --experimental-dump-help生成的tuist.spec.jsonCLI 规范 - 创建发布:创建包含制品的 GitHub 发布
- 分发:将更新推送到包管理器(例如,CLI 的 Homebrew)
对于应用,每次合并都会产生一个稳定版本。对于 CLI、服务器和 Kura,同样的自动流水线仅生成canary预发布版本;候选版本和稳定版本在每周的发布列车上切割(参见发布渠道)。
4. 范围过滤#
每个组件仅在存在相关变更时才发布:
- CLI:带有
(cli)范围或无范围的提交 - App:带有
(app)范围的提交 - Server:带有
(server)范围的提交
发布渠道#
CLI、服务器和 Kura 不会在每次推送到main时都提升为稳定版本。每个组件都通过三个渠道交付,以确保推荐的安装保持稳定且更新缓慢,而早期采用者则保留每次提交的构建。面向用户的文档位于CLI 发布渠道和服务器发布渠道;本节是维护者操作手册。
每个组件都使用相同的三个渠道:
| 渠道 | 版本 | 切割方式 |
|---|---|---|
| Canary | X.Y.0-canary.N | 自动执行,在每次影响组件的推送到main时触发(<component>-release.yml;对于 Kura,当kura/变更时由server-production-deployment.yml调用) |
| 候选版本 | X.Y.0-rc.N | 每周一,或手动在releases/<component>-<major>.<minor>.x分支上执行(<component>-rc.yml) |
| 稳定版 | X.Y.Z | 每周一,或手动通过提升经过充分测试的候选版本(<component>-promote.yml);补丁通过<component>-backport.yml |
Canary 和 RC 构建作为 GitHub 预发布版本发布(从不标记为“Latest”,从不移动 GHCR 上的:latest,从不推送到 CLI 的 Homebrew),因此包管理器和镜像解析器仅在明确选择加入时才会获取它们。Canary 始终针对下一个未发布的次版本:一旦切割了 RC 线,main的 canary 就会推进到下一个次版本。
托管的 Tuist 不等待稳定列车:服务器按提交部署,托管的 canary 和生产环境运行最新的 Kura canary。部署级联在部署之前会先发布该 canary 本身,因此kura-release.yml没有自己的触发器。仅对 Kura 发布工具进行的变更(mise/tasks/kura/**、kura-*.yml工作流)不会切割 canary。
你永远不会手动挑选版本号。每个渠道的下一个版本都由mise/tasks/<component>/release/channel-version.sh从现有的 git 标签派生而来,下面的工作流会调用它。
发布分支按组件进行命名空间隔离(releases/server-X.Y.x、releases/kura-X.Y.x、CLI 的releases/X.Y.x),因此这三个组件的线路绝不会在同一主版本上冲突。
每周计划#
每周一 UTC 时间 06:00,三个组件的提升作业同步运行:CLI 提升至稳定版、服务器提升至稳定版和Kura 提升至稳定版。每个作业都会提升尚未稳定的最新 RC 线路,通常是前一周一切割的线路。如果没有这样的线路,则该作业不发布任何内容。
当提升完成时,无论结果如何,其匹配的候选版本工作流(cli-rc.yml、server-rc.yml、kura-rc.yml)都会通过workflow_run触发,并从main切割下一条线路。
如果任何预定运行失败或被取消,都会向 Slack 发送消息。当发布分支在其最新 RC 之后有提交时,提升会失败。要发布该线路,请在其上切割一个新的 RC 并手动提升。否则,下周一将提升更新的线路。
切割候选版本#
要在每周计划之外切割新线路(请替换下方组件的工作流名称):
- 使用空的
branch输入运行 <组件>候选版本工作流(例如server-rc.yml)。 - 它会发布
X.Y.0-rc.1,并在成功后在构建的提交处创建受保护的releases/<component>-X.Y.x分支。
要将关键修复或回归引入正在测试的线路,请通过 PR 将其拣选到发布分支上(与回退流程相同;CI 从不执行拣选),然后迭代 RC:
- 从
releases/<component>-X.Y.x分出分支,拣选修复,向发布分支打开 PR,解决其中的任何冲突并合并。 - 再次使用
branch=releases/<component>-X.Y.x运行候选版本工作流。它将从分支 HEAD 发布X.Y.0-rc.(N+1)。
测试线路处于功能冻结状态:只有关键修复和回归才会进入该线路。
提升至稳定版#
要在每周计划之外提升线路,需在其 RC 经过充分测试后:
- 使用
branch=releases/<component>-X.Y.x运行 <组件>提升至稳定版工作流(例如server-promote.yml)。 - 它会使用
make_latest=true发布带有语义化版本标签的镜像,移动 GHCR 的:latest标签和 GitHub 的“Latest”指针。CLI 还会更新 Homebrew 公式。
除非分支 HEAD 正好是最新 RC 指向的提交,否则提升操作将拒绝运行。因此,如果在最后一个 RC 之后有任何修复合并到该线路上,你必须切割一个新的 RC 并让其经过测试,然后才能作为稳定版发布。
将修复回退到稳定线路#
一旦线路稳定,就使用仅限 CLI 的回退发布工作流(cli-backport.yml)来发布补丁:通过 PR 将修复拣选到releases/X.Y.x上,然后使用该分支运行工作流以切割X.Y.(Z+1)。回退操作永远不会移动“Latest”指针或 Homebrew 公式。同时维护两条线路:当前线路接受回归和安全修复,前一条线路仅接受关键和安全修复。其他错误修复不会回退,而是随下一个次版本一起发布。
对于服务器和 Kura,请使用服务器回退发布(server-backport.yml)或Kura 回退发布(kura-backport.yml)。提升操作仅发布 RC 的X.Y.0;它无法修补已稳定的线路。
- 从所选
X.Y线路上最新发布的稳定组件标签开始,并向其现有的releases/server-X.Y.x或releases/kura-X.Y.x分支打开一个精准修复 PR。较新的次版本或主版本发布不会阻止对维护中线路的修补。请勿包含无关的主分支变更。 - 审查并合并源 PR。获取远程发布分支并确认其最终合并尖端,包括组件支持的平台以及 TLS 正/负控制。通过预合并头并不等同于对其他压缩或合并提交的确认。
- 在获得单独的发布批准后,从 main分派匹配的回退工作流,将
source_sha设置为该确切确认的合并尖端,并将base_tag设置为当前稳定标签,例如[email protected]或[email protected]。工作流会派生出X.Y.(Z+1);操作人员不选择次版本升级或提升 canary 内容。
规划会刷新确切的远程分支和基础标签,要求所选源等于分支尖端,拒绝未更改的源或组件之外的变更、其信任策略输入和验证,并锁定解析后的基础提交。源自 main 的控制会在版本化镜像和 GitHub 发布之前立即重复这些检查。如果源分支、稳定基础或可用版本发生更改,请停止并重新确认,而不是替换为新源。
回退使用现有的多平台发布器,而不移动 GitHub 的“Latest”指针或 GHCR :latest。发布与托管部署是分开的:这些工作流不会提升托管服务器或 Kura 集群,不会绕过 canary 验收,也不会确认已安装的运行时。构建助手和 Dockerfile 仍然来自所选的稳定源。这些控制是发布关卡,而非无需凭据的构建作业;构建可以在最终关卡之前上传未标记的镜像摘要。镜像和 GitHub 发布不是原子的:如果在发布镜像标签后后续关卡失败,请检查部分发布并保留其原始源和工件。切勿替换现有发布或在其标签下使用不同的字节。
编写良好的提交消息#
由于提交消息直接影响发布说明,因此编写清晰、描述性的消息非常重要:
建议:#
- 使用现在时:“add feature”而非“added feature”
- 简洁但具有描述性
- 当变更特定于组件时,包含范围
- 在适用时引用问题:
fix(cli): resolve build cache issue (#1234)
避免:#
- 使用模糊的消息,如“fix bug”或“update code”
- 在一次提交中混合多个不相关的变更
- 忘记包含破坏性变更信息
破坏性变更#
对于破坏性变更,请在提交正文中包含BREAKING CHANGE::
feat(cli): change cache directory structure
BREAKING CHANGE: Cache files are now stored in a new directory structure.
Users need to clear their cache after updating.
发布工作流#
CLI、服务器和 Kura 各自拥有一套渠道工作流,遵循相同的结构:
<component>-release.yml- 在每次触及组件的推送到main时发布 canary(Kura 的是一个可重用工作流,当kura/变更时由server-production-deployment.yml调用)<component>-rc.yml- 每周一切割一个候选版本(通过提升后的workflow_run触发),或手动切割或迭代一个<component>-promote.yml- 每周一 UTC 时间 06:00 将待处理的 RC 提升至稳定版,或手动提升经过测试的 RC<component>-build-publish.yml- 上述三个工作流调用的共享构建和发布流水线
CLI、服务器和 Kura 还拥有<component>-backport.yml,用于在稳定线路上切割补丁。组件的所有工作流都通过一个<component>-publish并发组进行序列化,因此版本解析和标记永远不会竞争。Kura 是个例外:其 canary 使用kura-canary-publish,而其 RC、提升和回退使用kura-stable-publish,因此长时间的稳定构建永远不会阻碍部署级联。标签命名空间是不相交的,因此这两个组不会冲突。
Helm Chart 和基础设施镜像(CAPI operator、runners controller、xcresult processor image、linux runner image 等)仍通过.github/workflows/server-production-deployment.yml发布。它在推送到main时运行,并为托管部署执行 canary → 验收测试 → 生产环境的级联流程,同时在这些基础设施组件变更时发布它们。App、缓存、Gradle 插件、skills、Noora 以及独立的基础设施控制器各自拥有专用的*-release.yml工作流。它们共享变更检测机制:mise/tasks/release/components.json声明每个组件的标签前缀和包含路径,git cliff 将匹配的提交转换为发布说明。
CLI 构建(cli-build-publish.yml)还会生成并发布一个tuist.spec.json工件(由tuist --experimental-dump-help生成),以便下游工具可以使用该命令接口。
监控发布#
你可以通过以下方式监控发布:
- GitHub Releases 页面
- GitHub Actions 选项卡中的工作流运行记录
- 每个组件目录中的变更日志文件
优势#
这种持续发布方法提供:
- 快速交付:变更在合并后立即触达用户
- 减少瓶颈:无需等待手动发布
- 清晰沟通:根据提交消息自动生成变更日志
- 流程一致:所有组件采用相同的发布流程
- 质量保证:仅发布经过测试的变更
故障排除#
如果发布失败:
- 检查失败工作流的 GitHub Actions 日志
- 确保你的提交消息遵循约定格式
- 验证所有测试是否通过
- 检查组件是否成功构建
对于需要立即发布的紧急修复:
- 确保你的提交具有明确的作用域
- 合并后,监控发布工作流
- 对于服务器或 App,修复将在下次推送时交付;如有必要,触发手动发布
- 对于 CLI,修复会自动进入 canary 渠道。若要将其交付给稳定版用户,请将其回退到维护中的稳定线路(参见将修复回退到稳定线路)
App Store 发布#
虽然 CLI 和服务器遵循上述持续发布流程,但由于 Apple 的 App Store 审核流程,iOS App是个例外:
- 手动发布:iOS App 发布需要手动提交至 App Store
- 审核延迟:每次发布都必须经过 Apple 的审核流程,可能需要 1-7 天
- 批量变更:多次变更通常打包在一次 iOS 发布中
- TestFlight:Beta 版本可能在 App Store 发布之前通过 TestFlight 分发
- 发布说明:必须根据 App Store 指南专门编写
iOS App 仍然遵循相同的提交约定并使用 git cliff 生成变更日志,但实际向用户发布的频率较低且为手动安排。