scripts/release/README.md
Easydict 的发布流程由 asc workflow 编排。该工作流将构建、公证、打包、GitHub
和 Sparkle 等阶段拆分为多个小步骤,支持断点恢复,同时提供一条命令执行完整的发布流程。
旧版单体脚本保留为 release-easydict-legacy.sh,可作为临时备用方案使用,但新版工作流不会调用它。
dev 仍然是日常开发分支。发布流程会以本地 dev 的已提交内容为基线,在独立的临时 worktree 中合并远程 dev,再以同步结果和远程 main 的合并结果开始:
origin/dev、origin/main 和各个 Tag。dev 提交创建 detached 临时 worktree,并在其中将 origin/dev fast-forward 或合并到本地 dev 源;冲突时暂停并保留该临时 worktree。release/sync-<version> worktree。origin/main 提交合并到该 worktree。这样可以保留本地 dev 上尚未推送的提交,同时吸收远程 dev 的更新;也可以找回误合并到 main、
但尚未进入 dev 的更改。发布流程不会切换或合并当前 checkout;当前分支上的未提交修改不会进入发布,
发布只使用已提交的本地 dev。原子推送还能避免 dev 和 main 被分别更新。发布自动化自身的代码必须
已经提交并存在于合并后的历史中。
如果同一版本之前的发布尝试留下了干净但过期的临时 worktree,新的 prepare、draft 或 release
运行会在没有远程版本 Tag 的情况下自动归档旧 worktree、状态和产物,然后从最新的本地 dev 重建。
有未提交修改、已推送版本 Tag 或已进入远程发布阶段时,流程会停止并保留现场。
安装并认证以下工具:
asc。create-dmg。gh)。generate_appcast 工具,以及保存在 Keychain 中的 ed25519 密钥。工作流要求为 asc 配置 App Store Connect API 认证,并通过以下命令验证:
asc auth status --validate
gh auth status
默认情况下,工作流会先从 PATH 中查找 generate_appcast,然后查找 Sparkle 的 Xcode 包产物。
如果该工具位于其他位置,可以设置 GENERATE_APPCAST 指向可执行文件。密钥等机密信息应保存在
Keychain 或工具自身的凭据存储中,不会写入仓库或发布元数据。
在仓库根目录执行:
./scripts/release/release-easydict.sh release 2.22.0
默认发布频道为 beta。如果要发布稳定版本:
./scripts/release/release-easydict.sh release 2.22.0 --channel stable
可以指定发布说明文件和构建号:
./scripts/release/release-easydict.sh release 2.22.0 \
--notes /absolute/path/release-notes.md \
--build-number 64
如果不指定 --build-number,工作流会自动递增 Xcode 构建号。版本号必须高于 Sparkle feed 中的最新版本,
构建号也必须高于 feed 中的最新构建号。
如果希望在各阶段之间进行人工检查,可以使用较小的工作流:
# 构建、签名、公证、打包、生成 appcast,并在本地完成验证。
./scripts/release/release-easydict.sh prepare 2.22.0
# 准备发布、同步发布引用,并创建经过验证的 GitHub Draft Release。
./scripts/release/release-easydict.sh draft 2.22.0
# 发布已有的、经过验证的 Draft Release,安装 appcast,并执行远程验证。
./scripts/release/release-easydict.sh publish 2.22.0
如果准备发布的是稳定版本,需要在单独执行 publish 命令时传入相同的 --channel stable 参数。
如果最新的 GitHub Release 条目仍是当前版本的 Draft,且该版本尚未进入公开
appcast.xml,可以显式废弃旧 Draft 并从最新本地 dev 重建:
./scripts/release/release-easydict.sh draft 2.22.0 --replace-draft
该参数只适用于 draft,不能与 --build-number 同时使用。工作流会冻结旧 Draft
数据库 ID 和 Tag OID,将旧本地状态移入临时恢复目录,并将构建号设置为以下三者
最大值加一:旧 Draft 构建号、当前项目构建号、公开 appcast 最新构建号。旧 Draft
的完整 GitHub JSON 也会随临时恢复状态保留,供失败诊断或人工回滚使用。
新产物完成签名、公证和本地验证前,远端 Draft 和 Tag 保持不变。随后工作流会再次
核对旧 Draft 仍是页面最新条目、内容未被修改且 Tag OID 未变化,再使用 lease 原子
替换 dev、main 和版本 Tag,删除精确匹配的旧 Draft,并创建、验证新 Draft。
成功后旧工作树、旧产物、旧 skill 状态和临时恢复分支会自动删除;失败时保留临时
恢复目录,并要求使用输出的运行 ID 执行 resume,不会再次递增构建号。
发布新的 beta 时,publish 会自动将 feed 中排在当前版本之后的第一条 beta 提升为 stable。
例如发布 2.22.0 beta 时:
sparkle:channel=beta,GitHub Release 保持 prerelease。sparkle:channel,对应 GitHub Release 移除 prerelease。上一 beta 版本会在修改任何公开状态前写入 .tmp/release/<version>/state/channel-transition.env。
因此失败后的 resume 会继续使用同一个上一版本,不会根据后来变化的 feed 重新选择。没有上一 beta
时该步骤安全跳过;--channel stable 保持原有行为,不执行 beta 轮换。
只预览确切的 asc 执行计划而不执行发布步骤:
./scripts/release/release-easydict.sh release 2.22.0 --dry-run
asc 会将运行状态记录在 Git 忽略的 scripts/release/runs/ 目录下。修复临时问题后,使用 asc 输出的运行 ID
继续执行:
./scripts/release/release-easydict.sh resume <run-id>
发布状态和产物会保存在 .tmp/release/<version>/ 中,用于审计和恢复。成功发布后只会移除隔离的 Git worktree;
成功完成 --replace-draft 后还会移除被替换 Draft 的临时本地备份。
每个阶段都设计为可以安全重试,或者在替换已有远程资产或 feed 条目之前安全失败。
发布命令会把终端输出分成两类:
asc 的机器可读结果 JSON 不再直接铺满终端,而是保存到
.tmp/release/<version>/logs/workflow-<run-id>.json。详细日志保存在同一目录的 workflow-<run-id>.log,以及各个高噪声命令对应的步骤日志中。
签名、公证票据、Gatekeeper、DMG 校验和 xcodebuild export 等命令成功时只显示摘要;失败时会显示
日志路径和最后 40 行,便于快速定位。scripts/release/runs/ 仍然保存 asc 的原始运行状态,继续恢复时使用其中的
run ID:
./scripts/release/release-easydict.sh resume <run-id>
release 工作流按以下顺序执行检查点:
dev 和远程 main 合并到隔离的 worktree。asc xcode archive 归档,并使用 xcodebuild 导出。appcast.xml。dev、main 和带注释的版本 Tag。公开 feed 只有在 GitHub Release 发布后才会更新,因此不会提前宣传不可下载的归档文件。在此之前发生失败时, 流程会留下 GitHub Draft Release 和可恢复的本地状态,而不会留下一个发布了一半的 feed。
asc-workflow.json:工作流图和各个检查点。release-easydict.sh:稳定的命令行入口。release-common.sh:路径、发布配置和安全辅助函数。release-preflight.sh:本地环境和发布状态检查。release-branch-sync.sh:隔离 worktree 以及分支、Tag 同步。release-build.sh:版本更新、归档和导出阶段。release-package.sh:公证、ZIP、DMG 和校验和阶段。release-appcast.sh / release-appcast.py:Sparkle 生成和严格的 feed 验证。tests/test_release_appcast.py:beta 轮换和旧条目保护的行为测试。release-github.sh:幂等的 Draft Release/正式发布和资产验证。release-verify.sh:本地产物和最终远程状态验证。export-options.plist:Developer ID 导出配置。仓库和团队默认值可以通过 release-common.sh 中的环境变量覆盖,但正常的 Easydict 发布除了版本号、频道和发布说明外,
通常不需要其他参数。
dev/main 合并冲突:在版本更新或推送前暂停。