Flutter 生态包发布指南:自动 CI 发布、批量发布与坏版本恢复全流程
本文基于 Flutter 仓库中面向生态团队的发布文档 release/README.md(在 生态文档索引 中列为 "Releasing a Plugin or Package")编写,系统讲解 flutter/packages 仓库中插件与包的完整发布链路:自动发布 CI 的触发与判定规则、面向高频变更包的批量发布(Batch release)配置与流程、手动发布的标准操作与检查清单,以及已发布坏版本的修复与撤回策略。读完本文,你将能够理解一个包的版本变更如何最终到达 pub.dev,并掌握作为生态维护者应对发布失败、坏版本事故的完整处置方案。
总则:任何版本变更都应被发布
Flutter 生态的发布基线是一条简单原则:任何修改了包版本的 PR(这应该是绝大多数 PR)都应发布到 pub.dev。这条规则贯穿后文所有发布模式——无论是自动发布、批量发布还是手动发布,目标都是保证 pubspec.yaml 中的版本号与 pub.dev 上可安装的版本严格一致,让包的使用者(包括大量不经常更新传递依赖的客户端)能稳定解析到预期版本。
发布方式的选择取决于包的流量特征:
- 自动发布(Automatic release):默认模式,master 上每个包含版本更新的提交都会触发发布;
- 批量发布(Batch release):面向高流量包,把多个提交聚合成一次周期性发布,避免
CHANGELOG.md和pubspec.yaml频繁冲突、pub.dev 上版本号不断滚动。
自动发布(Automatic release)
flutter/packages 仓库中的包通过一个名为 release 的 GitHub Action 工作流自动发布。其工作机制是:
- 当 master 分支的某个提交包含一个或多个包的版本更新时,
releaseCI 会把新版本发布到 pub.dev,并向 GitHub 推送发布标签(tag); - 该 CI 在以下任一情况下视为通过:
- 发布流程成功;
- 该提交不包含任何版本更新(无事可做);
- 新版本此前已经发布过(幂等)。
运行时机与阻塞行为
有几个 CI 行为细节值得注意:
releaseCI 只在 post-submit 阶段运行,并且会等待其他所有 CI 任务全部通过后才开始——这保证了只有整体质量验证通过的提交才会被发布;- 与其他 CI 任务一样,
releaseCI 一旦失败会阻塞后续的 PR,因此发布失败必须及时处理(见下文"release CI 失败怎么办"); - 注意例外:
releaseCI 不会自动发布flutter_plugin_tools包(它本身就是发布工具链的一部分,单独管理)。
新包的首次发布与所有权转移
当一个包首次发布时,它会归属于发布器账户(publisher account),而不是 flutter.dev 认证发布者(verified publisher)。需要拥有发布器账户权限的团队成员登录 pub.dev,通过包页面的 Admin 标签页把包转移给认证发布者。这一步是所有新包接入生态发布体系的必经环节。
release CI 失败了怎么办
原文档给出了清晰的故障处置路径:
- 抖动(flake)场景(例如网络问题):团队成员可以直接重跑该 CI;
- 复杂场景:团队成员可以先手动发布相关包(流程见后文"手动发布"),再重跑 CI 使其通过;
- 最常见的失败原因其实是"其他测试任务先失败了",而不是发布本身出错。如果那次测试失败是抖动导致的,正确顺序是:先重跑失败的测试任务,待其变绿后再重跑
release。
批量发布(Batch release)
对于 PR 数量多的包,默认"每个提交发一次版"会带来两个问题:CHANGELOG.md 与 pubspec.yaml 的合并冲突不断,pub.dev 上版本持续滚动。这类包可以启用批量发布,把多个提交聚合成一次周期性发布。
硬性前提:启用批量发布的包必须处于正式版本号,不能是预发布版本(如 x.y.z-dev),因为批量发布工具链不支持预发布版本。
启用步骤
启用批量发布需要提交一个 PR,包含以下四处改动:
1. 在包根目录添加 ci_config.yaml:
release:
batch: true
这个文件也是判定包发布模式的依据——包根目录存在 ci_config.yaml 且设置了 release: batch: true 即视为批量发布,否则默认走自动发布。
2. 在包根目录创建 pending_changelogs 目录,并放入 template.yaml 模板文件:
# Use this file as a template to draft an unreleased changelog file.
# Make a copy of this file in the same directory, give it an appropriate name, and fill in the details.
changelog: |
- Can include a list of changes.
- with markdown supported.
version: <major|minor|patch|skip>
贡献者后续为每个 PR 复制该模板、重命名并填写变更说明;version 字段声明该变更期望的版本级别(major / minor / patch),skip 表示本次不升版本。
3. 在工作流目录添加名为 <package_name>_batch.yml 的工作流文件(package_name 为包名):
name: "Creates Batch Release for <package_name>"
on:
workflow_dispatch:
schedule:
# Run every Monday at 8:00 AM. Update cron as needed.
- cron: "0 8 * * 1"
jobs:
dispatch_release_pr:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f
with:
event-type: batch-release-pr
client-payload: '{"package": "<package_name>"}'
该工作流支持手动触发(workflow_dispatch)和定时触发(示例 cron 为每周一 8:00),通过 repository dispatch 事件 batch-release-pr 通知后续流程。
4. 为两个既有工作流添加分支触发条件——release_from_branches.yml 与 sync_release_pr.yml 的 on.push.branches 中都要加上:
on:
push:
branches:
- 'release-<package_name>-*'
合并该 PR 后,批量发布即配置完成。
批量发布的日常工作流
启用后,贡献者不应再直接修改 CHANGELOG.md 或 pubspec.yaml,而是为每个 PR 在 pending_changelogs 目录新增一个变更文件。之后的发布过程自动进行:
<package_name>_batch.yml中的定时任务触发一个指向release-<package_name>-<version>分支的新发布 PR;- 该 PR 把
pending_changelogs中的所有文件聚合成对CHANGELOG.md和pubspec.yaml的一次更新(版本号按各条目声明的级别推进); - 包属主(package owner)评审并合并该 PR;
- 合并动作触发
release_from_branches.yml,把包发布到 pub.dev; - 同时
sync_release_pr.yml创建一个指向main分支的"同步 PR",把发布分支上的变更同步回主干; - 包属主评审并合并同步 PR,周期结束。
这一设计的关键在于:变更在周期内以"独立文件"形式存在,几乎不可能产生冲突;版本号与 CHANGELOG 的写入被收敛到一次性的聚合 PR 中,实现了"高流量、低摩擦"的发布节奏。
手动发布(Manual release)
手动发布是兜底手段,只在自动发布被阻断时使用。典型触发场景是:带外破坏(out-of-band breakage)导致 post-submit 测试因与被发布 PR 无关的原因失败,从而卡住了本应自动发布的版本。文档同时指出,对于影响面较广(涉及很多插件)的 PR,revert 后重新落地是手动发布之外的一个值得优先考虑的替代方案。
发布前的三个检查
文档要求发布前逐条确认:
- post-submit CI 是否为绿? 如果因带外破坏而未全绿,也要确认后续存在一个"未改动任何与该插件相关内容"的绿色 post-submit。发布前必须检查 post-submit CI 状态;
- "发布即永久(Publishing is forever)":pub.dev 上的版本不可覆盖。虽然 bug 或破坏性变更理应已在 PR 评审中被捕获,但这是上线前最后一次的 revert 机会;
- "不要在周五发布":发布前考虑是否有较长的不可用时段即将到来。新版本可能存在 bug 或引发使用者提问,如果此时无法跟进,问题的发现与解决周期会被显著拉长。
标准操作步骤
使用仓库工具链 flutter_plugin_tools 完成发布:
git checkout <commit_hash_to_publish>——这应当就是你要发布的那个 PR 的提交,除非有非常充分的理由使用其他版本;- 确认
git status干净,且本地仓库没有多余文件(例如通过git clean -xfd清理); - 运行
flutter_plugin_tools的publish命令。该命令会检查上一步的清理状态,把新版本发布到 pub.dev,并按<package_name>-v<package_version>格式为提交打标签,再把标签推送到上游仓库。
完全手动备选方案
如果第 3 步中无法使用 flutter_plugin_tools,可以退化为纯手动三步:
- 用
dart pub publish把包更新推送到 pub.dev; - 用
git tag按<package_name>-v<package_version>格式为提交打标签; - 用
git push upstream <tagname>把标签推送到上游 master 分支。
恢复坏版本(Recovering from a bad release)
尽管有重重防护,破坏性问题仍可能在包发布之后才被发现。这里有一个与 flutter/engine、flutter/flutter 仓库根本不同的约束:已发布过破坏性变更的 PR 不能直接 revert——revert 会把包带回一个更早的版本号,而那个版本同样已经发布过,pub.dev 不允许重复发布同一版本号。
正确的修复路径是:
- 以一个新版本落地修复:视具体情况选择"revert 并带上版本号和 CHANGELOG 更新"或"正向修复(fix-forward)";
- 可选:撤回坏版本。如果坏版本发布在最近七天内,
flutter.dev发布者组成员可以通过包 pub.dev 页面的 Admin 标签页撤回(retract)该版本。
撤回这一步在两种情况下尤其有价值:
- 坏版本的问题与错误的 Flutter/Dart 版本约束有关(例如包依赖了新版 Flutter/Dart 的功能,却没有设置对应的最低版本约束):即使后续发布了修正约束的新版本,仍在使用旧版 Flutter 的客户端可能继续解析到坏版本,撤回可以杜绝这种解析结果;
- 作为修复版本推广期间的即时止损,防止在修复版本铺开前更多用户被波及。
文档特别强调:撤回应当与发布修复版本同时进行,而不是替代它——这样已经被破坏的用户才能顺畅地到达一个可用状态。
相关联的生态发布流程
理解发布链路后,以下同仓库文档可作为延伸阅读,它们与发布过程直接衔接:
- Updating-Packages-repo-for-a-stable-release.md:描述每个 stable Flutter 版本发布后如何更新 flutter/packages 仓库的 stable 版本钉扎、Flutter-Dart 版本映射、N-1/N-2 遗留分析测试与最低 Flutter 版本约束——这是"版本发布"在 SDK 维度的配套动作,与包维度的 pub.dev 发布互为表里。文档给出了具体命令示例,例如用仓库工具批量抬升最低 SDK 版本:
以及用dart run script/tool/bin/flutter_plugin_tools.dart update-min-sdk --flutter-min=3.44.0update-release-info --version=next只更新受影响包的发布说明; - contributing/README.md:定义进入发布流水线的上游规则——版本与 CHANGELOG 更新规范、
## NEXT段的使用、批量发布包pending_changelogs变更文件的写入要求,以及破坏性变更的"分批落地"策略(先临时加publish_to: none隔离不可发布状态,再集中落地破坏性变更,最后统一升版); - Package-migration-to-1.0.0.md:解释包跨越 1.0.0 里程碑时的生态摩擦——由于 pub 对 0.x 版本采用语义偏移,从
0.x.y到1.0.0属于主版本跃迁,文档建议依赖方在过渡期使用>=0.x.y+z <2.0.0的宽约束而非^1.0.0,以减少生态碎片化。这直接影响发布时约束字段的写法决策。
从本仓库结构看,Flutter 还有另一条独立的"发布"通道:dev/bots/prepare_package.dart 负责把 Flutter git 仓库打包成 SDK 分发包(要求完整 40 位 --revision、--branch 等参数),dev/bots/unpublish_package.dart 则负责从云存储移除已发布的 SDK 归档并回滚各 channel 的元数据——两者面向的是 Flutter SDK 本身的 dev/beta/stable 通道分发,与本文讨论的包发布(pub.dev)是两套机制,阅读时应注意区分。
小结
Flutter 生态的包发布体系可以概括为三层:默认层是 master 提交触发的自动发布 CI,保证"改版本即发布"且发布前置所有 CI 验证;效率层是批量发布,用 ci_config.yaml、pending_changelogs 与三个协作工作流把高频变更收敛为周期性聚合发布;兜底层是手动发布与坏版本恢复,前者以"post-submit 全绿、发布即永久、避免周五发布"为检查清单,后者以"新版本修复 + 七天内撤回"双管齐下。三者共同保障了 pub.dev 上版本、仓库 tag 与 CHANGELOG 三者的一致性。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00