发布新版本(Release)
Usage4Claude 采用 CI 自动发布:向 main push 一个满足条件的 commit 后,
GitHub Actions(.github/workflows/release.yml)自动完成构建、签名、发 Release、
更新 Sparkle 更新源。你(Claude)的职责是准备好发版材料并引导用户完成触发,
不代替用户执行发版 commit 与 push。
若根目录存在
NEXT_RELEASE.md,说明本次发版有一次性特殊情况,先读它再按本流程走。
必须先记住的架构事实
- 两份发布材料,各司其职(详见下一节分工表):
CHANGELOG.md— 完整技术档案 + 版本号权威源,不进 Sparkle。docs/RELEASE_NOTES.md— 面向用户的发布说明,CI 提取当前版本段落后同时注入 Sparkle 弹窗(appcast.xml的<description>)和 GitHub Release 正文。
- 触发条件:commit message 含
[release]/[RELEASE],push 到main,且本次改动 包含CHANGELOG.md或docs/RELEASE_NOTES.md(发版通常两者都改)。 - Sparkle 弹窗内容来自 docs/RELEASE_NOTES.md(不是 CHANGELOG)。用户在应用内“检查更新” 看到的就是 RELEASE_NOTES 当前版本段落。
- docs/RELEASE_NOTES.md 必须有当前版本段落:否则 Sparkle/Release 正文会为空。CI 的
validate 阶段会
grep "^## [X.Y.Z]"fail-fast,但应发版前就写好。 - Build 号自动跟随:
CURRENT_PROJECT_VERSION = $(MARKETING_VERSION),Build 恒等于 Version。只改 Version,绝不手动固定 Build,否则 Sparkle 认不出新版本。 - appcast.xml 由 CI 维护,绝不手改。
- 详细背景见
docs/DAILY_RELEASE_WORKFLOW.md、docs/SPARKLE_SETUP.md。
两份材料的分工(关键)
| CHANGELOG.md | docs/RELEASE_NOTES.md | |
|---|---|---|
| 定位 | 完整技术档案 + 版本号权威源 | 面向用户的发布说明 |
| 收录范围 | 所有改动,含内部重构、CI、安全加固 | 只留用户可感知的现象 |
| 措辞 | 可保留技术细节(JWT、actor、base64url 等) | 口语化,去技术词 |
| 致谢 | 不加 | 在相关条目末尾加 (thanks @author, #N) |
| CI 喂给 | 无(纯档案;validate 从它提版本号) | Sparkle 弹窗 + GitHub Release 正文 |
| 何时写 | 发版前 | 发版前(不是发布后精修) |
规则细节见
docs/CHANGELOG_AND_RELEASE_NOTES_GUIDELINES.md。
流程
1. 收集自上个 tag 以来的变更
git fetch origin # 先核对远程,避免发版已在别处完成
LAST_TAG=$(git describe --tags --abbrev=0) # 上一个发布 tag,如 v3.3.0
git log "$LAST_TAG"..HEAD --oneline # 变更概览
git log "$LAST_TAG"..HEAD --format='=== %h ===%n%B' # 完整 message(判断影响面必读)
git log "$LAST_TAG"..HEAD --merges --format='%h %s' # 合并的 PR(用于致谢)
逐条阅读完整 message,区分:用户可感知的现象 vs 纯内部改动。两类都进 CHANGELOG, 但只有前者进 RELEASE_NOTES。
2. 决定版本号
读 CHANGELOG.md 顶部当前版本,按语义化递增:
| 改动类型 | 递增 | 例 |
|---|---|---|
| 仅 Bug 修复 | patch | 3.3.0 → 3.3.1 |
| 含新功能 | minor | 3.3.0 → 3.4.0 |
| 破坏性变更 | major | 3.3.0 → 4.0.0 |
版本号不确定时用 AskUserQuestion 让用户确认。
3. 编写两份发布材料
3a. CHANGELOG.md(完整技术档案)
- 在文件顶部(
# Changelog与首个## [之间)插入新版本段落,日期用当天。 - 收录所有改动,按
Added/Changed/Fixed/Security分类。 - 新功能的后续修改/优化/bug 修复并入该功能条目,不在 Fixed 里重复列出。
- 每个变更点一条,不同变更点只出现一次,简洁不赘述。
- 更新文件底部版本链接:新增
[X.Y.Z]: https://github.com/f-is-h/Usage4Claude/releases/tag/vX.Y.Z
3b. docs/RELEASE_NOTES.md(面向用户 + 致谢)
- 在文件顶部插入
## [X.Y.Z] - 当天日期段落(结构同 CHANGELOG)。 - 只保留用户可感知的现象,去技术词、口语化。
- 收集本版本对应的 已合并 PR 与 已解决 Issue 及作者,条目末尾致谢
(thanks @author, #N):
只对确已合并的 PR / 确已解决的 Issue 致谢。未合并的 PR、仍 Open 且本次并未真正 修复的 Issue 不致谢,避免误导用户(硬规则)。gh pr view <n> --repo f-is-h/Usage4Claude --json number,title,author,state gh issue view <n> --repo f-is-h/Usage4Claude --json number,title,author,state
两份写入文件的都是英文;同时在对话里给用户中文对照(不写进文件)。写好后用 AskUserQuestion 让用户确认草稿再继续。
4. 更新 Xcode 版本号
两处 MARKETING_VERSION(Debug/Release)都要改:
sed -i '' 's/MARKETING_VERSION = <旧版本>;/MARKETING_VERSION = <新版本>;/g' \
Usage4Claude.xcodeproj/project.pbxproj
grep -n "MARKETING_VERSION" Usage4Claude.xcodeproj/project.pbxproj # 确认两处都改了
CHANGELOG 版本与 Xcode 版本必须完全一致,否则 CI 的 verify_version.sh 会失败。
RELEASE_NOTES 也必须有同一版本段落(CI validate 会 fail-fast)。
5. 编译验证
xcodebuild -project Usage4Claude.xcodeproj -scheme Usage4Claude -configuration Release build 2>&1 | tail -5
看到 ** BUILD SUCCEEDED ** 后,核对产物版本号:
APP=$(find ~/Library/Developer/Xcode/DerivedData -name Usage4Claude.app -path '*/Release/*' | head -1)
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$APP/Contents/Info.plist" # 应与 Version 相同
6. 发版 commit + push(commit 由用户手写)
发版 commit message 不走日常 COMMIT_MESSAGE_GUIDELINES 那套,由用户手工编写。
你只提供草稿供参考,不擅自 commit/push——git add/commit/push 需用户确认后执行。
- 只写一行标题,不要正文。 CI 只取第一行(去掉
[release]前缀)作为 GitHub Release 标题;正文不被使用(Release 正文来自 RELEASE_NOTES),写了也是浪费。 - 格式:
[release] vX.Y.Z - 简短标题
- 触发前自检:commit 含
[release]、本次改动含CHANGELOG.md/docs/RELEASE_NOTES.md、目标分支main。
推送后 CI 触发。
7. 监控 CI
gh run list --workflow=release.yml --limit 3
gh run watch # 或看 https://github.com/f-is-h/Usage4Claude/actions
CI 三段:validate(版本校验 + RELEASE_NOTES 段落校验)→ build(构建签名,约 8 分钟)→ release(发 Release + 推 appcast.xml 回 main)。失败常见原因:版本号不一致、RELEASE_NOTES 缺当前版本段落、CHANGELOG 版本已发布过。
8.(可选)发布后装饰 GitHub Release 页面
CI 已用 RELEASE_NOTES 自动发布了面向用户的 Release,且 Sparkle 弹窗同源——通常无需再做。 若想让 GitHub Release 页面更精致(大标题、总览段落、emoji),可发布后手工编辑网页。
- 这属于 GitHub 页面装饰,不回流 Sparkle(Sparkle 已在发布时拿到 RELEASE_NOTES 段落)。
- 改的是对外公开页面,先与用户确认再执行。
- 页面正文结构 = 「RELEASE_NOTES 段落」+
---+ Installation +---+ Full Changelog, 只改第一个---上方那段,下方模板不要动。用gh release edit会整体替换正文,需先gh release view vX.Y.Z --json body -q .body取回完整正文改上半段后整体回填。
发版前的安全测试(不真正发布)
按影响面从小到大三档,用于在正式发版前验证:
① 本地预览(零风险,什么都不发)
swift test # 单元测试
xcodebuild -project Usage4Claude.xcodeproj -scheme Usage4Claude \
-configuration Release build # 能否编译
.github/scripts/verify_version.sh verify CHANGELOG.md Usage4Claude.xcodeproj # 版本号一致性
.github/scripts/generate_release_notes.sh \
.github/RELEASE_TEMPLATE.md <版本> /tmp/rn_preview.md docs/RELEASE_NOTES.md # 预览 Release 正文
./scripts/build.sh --config Release # 本地打 DMG,验证打包脚本
generate_release_notes.sh 输出里第一个 --- 上方那段,同时也是 Sparkle 更新弹窗
会显示的内容(都来自 RELEASE_NOTES)——发版前务必看一眼措辞是否面向用户。
② test-release 分支(CI 构建冒烟,不发布)
- 把改动 push 到
test-release分支(commit 仍含[release])。 - CI 跑 validate + build:在 CI 环境编译、导入证书、Sparkle 签名、打 DMG。
- 跳过 release job(
is_test=true)→ 不打 tag、不发 Release、不碰 appcast、不 push main。 - 用途:验证 CI 能否构建出签名 DMG(本地过 ≠ CI 过,证书/Sparkle 私钥只在 CI secrets)。 DMG 作为 artifact 可下载。几乎无需清理。
③ workflow_dispatch dry_run(端到端演练,产草稿)
- 在 Actions 页面对 main 手动运行 workflow,勾选
dry_run。 - 跑完整三段,但:tag=
test-v<版本>、Release 走--draft(公众不可见)、appcast 只打印不 push。 - 用途:演练整条发布链路(含 release job 的 tag/release/appcast 逻辑)而不影响用户。
- 需清理:
gh release delete test-v<版本> --yes且git push origin :refs/tags/test-v<版本>。 - 注意:dry_run 必须在 main 触发才有端到端效果;在 test-release 触发会退化成 ②。
选择:只验发布材料 → ①;验 CI 构建 → ②;验整条链路 → ③。
红线清单
- 发版 commit 与 push 由用户执行,你只准备材料 + 提供草稿。
- 发版前
CHANGELOG.md与docs/RELEASE_NOTES.md都要有当前版本段落。 - CHANGELOG 版本号与 Xcode
MARKETING_VERSION必须一致。 - 只改 Version,不碰 Build 号(自动跟随 MARKETING_VERSION)。
- 不手改 appcast.xml(CI 维护)。
- 致谢只给确已合并/解决的 PR/Issue。
- 已发布版本的 CHANGELOG / RELEASE_NOTES 段落不回改(否则与已发内容、Sparkle 说明不一致)。
- 冒烟测试若需杀进程,用
kill <PID>而非killall(避免误伤用户在跑的正式版实例)。
相关文档
docs/DAILY_RELEASE_WORKFLOW.md— 日常发版流程全文docs/CHANGELOG_AND_RELEASE_NOTES_GUIDELINES.md— CHANGELOG 与 RELEASE_NOTES 编写规范docs/COMMIT_MESSAGE_GUIDELINES.md— 日常 commit 规范(发版 commit 不适用)docs/SPARKLE_SETUP.md— Sparkle 自动更新机制.github/workflows/release.yml— CI 发布流水线.github/RELEASE_TEMPLATE.md— Release 正文固定模板(Installation 等)