
Release Workflow
- 31 installs
- 543 repo stars
- Updated August 5, 2026
- cat-xierluo/legal-skills
Run the full GitHub release cycle: version bump, CHANGELOG sync, release notes, tagging, CI build monitoring, release verification, and history cleanup.
About
A full GitHub release workflow covering version management, CHANGELOG sync, release-notes writing, tag creation, CI build monitoring, release verification, and history cleanup for apps, CLIs, web apps, and libraries. A developer uses it to publish a new version or troubleshoot a failed release or CI, while rejecting the tag-to-test-CI anti-pattern.
- Version, CHANGELOG, tag, and release-notes flow
- CI build monitoring and failure troubleshooting
Release Workflow by the numbers
- 31 all-time installs (skills.sh)
- Ranked #151 of 248 Release Management skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cat-xierluo/legal-skills --skill release-workflowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| repo stars | ★ 543 |
| Last updated | August 5, 2026 |
| Repository | cat-xierluo/legal-skills ↗ |
What it does
Run the full GitHub release cycle: version bump, CHANGELOG sync, release notes, tagging, CI build monitoring, release verification, and history cleanup.
Files
Release Workflow
软件项目的全流程发布工作流。适用于 GitHub 上的任何类型项目。
适用场景
GitHub 项目的完整发布周期:从版本号确定到 CI 构建验证。CI 故障排查(references/ci-troubleshooting.md)和特定项目类型指南(references/ 下各文档)作为发布流程的补充参考。
项目配置
config/projects.yaml 集中管理各项目的发布配置(仓库、平台、自动更新、排除产物等)。发布时先读取对应项目配置,按配置决定构建矩阵和预期产物。模板见 config/projects.example.yaml。
发布前检查
| 检查项 | 说明 |
|---|---|
| 工作区干净 | git status 无未提交变更 |
| 版本号一致 | 所有版本号文件(package.json / Cargo.toml / pyproject.toml 等)与 CHANGELOG.md 最新条目一致 |
| CHANGELOG 已更新 | 包含目标版本的结构化条目 |
| CI 工作流存在 | .github/workflows/ 中有 release 相关工作流且 tag 触发配置正确 |
任一条件不满足,先修复再继续。
⚠️ Release ≠ 测试 — 强制约束
打 tag / 创建 GitHub Release 是把版本号给真实用户,不是 CI 验证机制。把 release workflow 当作"看 CI 跑没跑通"或"我下载个 artifact 自己测一下"是反模式,必须禁止。
为什么是绝对规则
- Actions 配额是有限共享资源。单次跨平台 release(macOS × N + Windows + Linux)通常消耗 300-600 配额分钟,macOS runner 是 10× 费率,贡献最大。
- 错把 release 当测试的隐性成本:
- GitHub Release 一旦创建(即使是 draft)就被计入资产历史,污染 release feed
- tag 推送后 commit 被人看到会误以为已发布
- 自动更新用户可能在升级检查时看到不稳定的版本
- 配额快速耗尽,真正紧急的 hotfix 反而跑不动 CI
- 过去能这么干不代表现在该这么干。GitHub 免费配额调整、macOS runner 涨价都发生过,使用模式必须随成本变化更新。
禁止的反模式
| 反模式 | 表现 | 为什么错 |
|---|---|---|
| 把 tag 当 smoke test | "我改了一行,打个 tag 看看 CI 跑不跑得通" | 一次 release 吃掉 300+ 配额分钟,5 次测试 = 一月配额清零 |
| 用 release 验证构建产物 | "我想看 .dmg 长什么样,必须跑 release" | 应该用专门的 preview / draft build workflow(见下) |
| 同一天 / 24h 内发多个 patch | v0.3.16 / 17 / 18 一天内连发,各是同一个 bug 的连续小修 | 全部攒到下次一起发,成本立省 60%+ |
| draft release 当"先跑一次试试" | "我先 draft release 看 artifact 行不行" | draft 一样跑完整 CI,一样消耗配额,一样污染 release 历史 |
| 单平台 dry-run 验构建 | "先跑 Linux dry-run 看看,不发全平台" | dry-run 一样消耗 CI 时间,开了口子就停不下来;改走 preview workflow |
| 小改动发 patch | "我改了 typo / 改了一行文档,必须 vX.Y.Z" | 纯 typo / 文档小改 / 单文件改动不构成发版理由,合并到下个有实质内容的版本 |
| "已经打 tag 了,跑都跑了" | "v0.3.22 tag 已经推上去了,CI 反正也在跑" | "已经做了"不是继续做的理由;记录这次浪费并阻止下次重复 |
正确做法
A. 想验证 CI 跑不跑得通 / 看构建产物长什么样?
- 用
pull_request触发的 preview workflow(可只跑 ubuntu / 单一平台,几十分钟完成) - 或在 main 上用
workflow_dispatch手动触发 dry build,不触发 release workflow - 这两种都不消耗 macOS 高倍率配额,artifact 只对自己可见
B. 真的有用户能拿到的修复要发?
- 等攒到 3-5 个实质修复(bug fix / feature / 性能 / 兼容性改动)
- 一次性打 tag 发版,只发一次
- CHANGELOG 必须有结构化条目,不能空
- 距离上次 tag 至少 24 小时(防止把单个 hotfix 拆成多个 patch)
打 tag 前强制自检
打 tag 之前,先回答五个问题:
1. 这是给真实用户装的,还是只给自己看 artifact? 2. CHANGELOG 已经有结构化的本版本条目(不是空、不是单行 typo)? 3. 距上次 tag ≥ 24 小时? 4. 本次累计有 ≥ 1 个实质修复 / 特性 / 改动(纯文档 / typo / 单行 README 修改不算)? 5. 如果上述任一不满足:能合并到下次发版吗?
任一答"否"或"不知道":不要打 tag,改走 preview workflow 或合并到下次。
借口反驳表
| 借口 | 现实 |
|---|---|
| "我就看一眼,tag 一下马上回滚" | tag 推送已经触发了完整 CI,回滚 tag 不能退款 Actions 分钟 |
| "用户催着要" | 用户不知道你的 Actions 配额,告诉 ta 合并到明天的成本和时间,让 ta 选 |
| "反正之前都这么干" | 之前能用不等于现在合理,这正是 91% 配额的直接成因 |
| "只有 release workflow 跑完整矩阵" | 加一个 preview workflow(成本是 release 的 10-20%),不要用 release 凑合 |
| "draft release 不算正式发布" | draft 一样跑完整 CI、一样消耗配额、一样污染 release 历史 |
| "小改动发 patch 很常见" | 纯 typo / 文档 / 单行不构成发版理由,合并到下个有实质内容的版本 |
| "我已经打 tag 了,跑都跑了" | "已经做了"不是继续做的理由;记录这次浪费,阻止下次重复 |
| "单平台先 dry-run 一下" | dry-run 一样消耗 CI 时间,开了口子就停不下来;改走 preview workflow |
| "这次不一样,这次真的需要发" | SemVer 的 patch 版本本来就允许累积;下次发版不是更优解吗 |
红灯(看到任一就停)
- 同一工作日内想发第二次 tag
- 距上次 tag < 24 小时
- CHANGELOG 没有本版本的结构化条目就想发
- 想用 "draft release" 当测试
- 想用
workflow_dispatch触发 release workflow 当测试(应该触发独立的 preview workflow) - 本次只有 typo / 文档 / 单行修改
- macOS 10× 配额当月累计用量已 > 70%
以上任一出现:删掉 tag(如已打),改走 preview workflow 或合并到下次。
发布流程
第 1 步:确定版本号
从用户处获取或从 CHANGELOG.md 读取目标版本号。
统一所有版本号文件(按项目类型选取):
- Node.js 项目:
package.json→version - Rust 项目:
Cargo.toml→version - Python 项目:
pyproject.toml→version - 桌面应用:对应配置文件(如 Tauri 的
tauri.conf.json) - CHANGELOG.md → 最新
## [x.y.z]条目
版本号规则(SemVer):
| 类型 | 示例 | 适用场景 |
|---|---|---|
| PATCH | 0.3.7 → 0.3.8 | Bug 修复、小改进 |
| MINOR | 0.3.x → 0.4.0 | 新功能、向后兼容 |
| MAJOR | 0.x → 1.0.0 | 重大架构变更、破坏性改动 |
第 2 步:生成 Release Notes
信息来源有两个,必须综合使用:
来源 1 — `CHANGELOG.md`:结构化的变更分类(Added / Changed / Fixed 等)
来源 2 — `git log`:两个 tag 之间的 commit 历史,补充上下文和细节
# 获取上一个 tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
# 查看 commit 历史
git log ${PREV_TAG}..HEAD --oneline
# 查看详细变更(含 PR 链接)
git log ${PREV_TAG}..HEAD --format="- %s (%h)"综合两个来源,按模板组织 Release Notes。模板和格式指南见 references/release-notes-guide.md。如果 config/projects.yaml 中存在 release_notes.profile,优先使用项目配置指定的结构;未配置时按项目类型选择默认结构。
第 3 步:提交并打 Tag
# 确保所有变更已提交
git status
# 打 tag
git tag "vX.Y.Z"
# 推送 tag 触发 CI
git push origin "vX.Y.Z"如果有同名旧 tag(如发布失败后重试):
git push origin :refs/tags/vX.Y.Z
git tag -d vX.Y.Z 2>/dev/null
git tag vX.Y.Z
git push origin vX.Y.Z第 4 步:监控 CI 构建
# 查看构建状态
gh run list --limit 3
# 各平台 job 状态
gh run view <RUN_ID> --json jobs --jq '.jobs[] | "\(.name): \(.conclusion)"'
# 失败日志
gh run view <RUN_ID> --log-failed项目类型的特定构建产物和验证方法,见 references/ 下对应文档。
第 5 步:更新 Release Notes
CI 构建成功后,用第 2 步准备的草稿更新 GitHub Release:
gh release edit vX.Y.Z --repo <owner>/<repo> --notes "$(cat <<'EOF'
<Release Notes 内容>
EOF
)"Release Notes 正文不要再写 # <项目名> vX.Y.Z 或其他重复版本标题;GitHub Release 页面自身已经显示标题,正文应直接从摘要、升级提示或 Highlights 开始。
第 6 步:验证
# 检查产物是否完整
gh release view vX.Y.Z --json assets --jq '.assets[].name'对照 config/projects.yaml 中该项目的配置检查: 1. 预期产物是否齐全(根据 platforms 和 auto_update 推导) 2. exclude_assets 中列出的产物是否意外出现 3. 产物命名是否符合规范 4. Release Notes 是否符合 release_notes.required_sections 和 release_notes.always_include 约束
第 7 步:清理
- 删除失败的 Actions runs:
gh run delete <ID> - 清理旧的 draft release(如有)
- 确认镜像同步是否成功(如已配置)
特定项目类型指南
| 项目类型 | 参考文档 |
|---|---|
| Tauri 桌面应用 | references/tauri-release.md |
检查清单
打 tag 前(强制) — 见上文 ## ⚠️ Release ≠ 测试 — 强制约束:
- [ ] 这是给真实用户装的,不是只给自己看 artifact
- [ ] CHANGELOG 有结构化的本版本条目
- [ ] 距上次 tag ≥ 24 小时
- [ ] 本次有 ≥ 1 个实质修复 / 特性 / 改动
- [ ] 已通过五问自检
发布完成后确认:
- [ ] 所有平台 / 矩阵构建全部成功
- [ ] GitHub Release 产物完整
- [ ] Release Notes 已更新,且正文没有重复的版本标题
- [ ] 镜像同步成功(如已配置)
- [ ] 旧的失败 Actions runs 已清理
- [ ] 项目文档已更新(TASKS / DECISIONS / CHANGELOG 等)
- [ ] tag 指向正确的 commit
变更日志
[1.2.0] - 2026-06-08
新增
- 新增
## ⚠️ Release ≠ 测试 — 强制约束章节:把 release workflow 当作 CI 验证机制("打 tag 看一下")是反模式,强制禁止。 - 新增打 tag 前五问自检清单:是否给真实用户、CHANGELOG 是否就绪、距上次 tag 是否 ≥ 24h、是否有实质改动、能否合并到下次。
- 新增反模式表(7 类禁止行为)+ 借口反驳表(9 类常见借口)+ 红灯列表(7 类立即停止信号)。
description触发词补充:"Actions 配额告急"、"短时间内多次发版"、"打 tag 看一下"等反模式场景。
变更
- 发布完成检查清单拆分为"打 tag 前(强制)"和"发布完成后"两段,强制自检放在前。
- 适用场景从"完整发布周期"扩展为"包含反模式识别和拒绝"。
触发背景
Folia 项目在 2026-06 账单周期(6/1-6/30)使用 1825/2000 Actions 分钟(91%),根因是把 release workflow 当作 CI 验证机制使用:6/1 一天发 3 个 patch 版本,22 天发 15 个版本,其中大部分是"看一下 build 行不行"而非真实用户发布。
[1.1.2] - 2026-06-01
变更
- 固定桌面应用 Release Notes 结构为摘要、Highlights、新增、变更、修复、Warning、下载和完整变更日志。
- 新增
release_notes项目配置示例,用于为 Folia 等项目指定专门的 Release Notes profile 和必备分区。
[1.1.1] - 2026-06-01
变更
- Release Notes 模板移除正文顶部的版本标题,避免与 GitHub Release 页面标题重复。
- 发布完成检查清单增加“正文没有重复版本标题”的要求。
[1.1.0] - 2026-05-20
变更
- SKILL.md 从 Tauri 专用改为通用发布工作流,适用于桌面应用、CLI 工具、Web 应用、库/SDK 等任何 GitHub 项目
- Tauri 特定内容下沉到
references/tauri-release.md - CI 故障排查改为通用指南,不再绑定 Tauri
- 新增
references/release-notes-guide.md:Release Notes 撰写指南(含模板、设计决策、不同项目类型适配)
新增
references/tauri-release.md新增「常见配置问题与优化」章节(6 个问题),来源于 Funes 项目审查references/tauri-release.md参考项目表格增加 Folia 和 Funes 对比
[1.0.0] - 2026-05-20
新增
- SKILL.md:7 步发布流程 + Release Notes 模板
- references/ci-troubleshooting.md:CI 故障排查
- 通过 Folia v0.3.7 发布验证全流程
# 项目 Release 配置模板
# 复制此文件为 projects.yaml 并填入实际项目信息
your-app:
# GitHub 仓库(owner/repo)
repo: owner/repo
# 项目类型:tauri | cli | web | library
type: tauri
# 是否启用 Tauri 内置自动更新
# true → 保留 .tar.gz / .sig / latest.json
# false → 仅保留安装包(.dmg / .exe)
auto_update: true
# Windows 安装格式(仅 tauri 类型)
# exe → NSIS 安装器(推荐,大多数项目使用)
# msi → MSI 安装器
# 不要同时发布两种格式
windows_format: exe
# 构建平台
# 可选值:macos-aarch64, macos-x64, windows-x64, linux-x64
platforms:
- macos-aarch64
- macos-x64
- windows-x64
# 排除的产物(glob 模式),在验证和清理阶段使用
exclude_assets: []
# 示例:
# exclude_assets:
# - "*.msi"
# - "*.msi.sig"
# Release Notes 结构配置
# profile 可选:desktop-standard | cli-standard | library-standard | web-standard
release_notes:
profile: desktop-standard
language: zh-CN
required_sections:
- Highlights
- 下载
optional_sections:
- 新增
- 变更
- 修复
always_include:
- no_duplicate_title
- summary_blockquote
- full_changelog_link
- auto_update_assets_note
warning:
include_when:
- macos_unsigned
- terminal_command_required
- breaking_change
- security_notice
MIT License
Copyright (c) 2026 杨卫薪律师(微信ywxlaw)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CI/CD 故障排查手册
通用 GitHub Actions 发布工作流的常见问题和解决方案。
1. 跨平台原生绑定缺失
症状:macOS 构建成功,Windows 构建安装依赖时报找不到平台特定的原生模块。
原因:npm/bun 的 optional dependencies bug(npm/cli#4828)。在一个平台生成的 lock file 不包含其他平台的可选依赖。
解决方案:
- 改用 pnpm(推荐):pnpm 在每个 CI runner 上独立解析 optional dependencies
- 或在 Windows 步骤中显式安装缺失的包:
npm install @package/win32-x64-msvc
2. rm -f 在 PowerShell 报错
症状:Windows runner 上 rm -f file 报 -f 参数歧义。
原因:PowerShell 的 rm 是 Remove-Item 别名,-f 被解析为 -Filter。
解决方案:使用跨平台兼容命令,或用条件判断 if: runner.os == 'Windows' 分开处理。
3. tag 触发的重复构建
症状:移动 tag 后同时触发新旧两个构建。
解决方案:添加 concurrency 配置:
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: true4. GitHub token 缺少 workflow 权限
症状:推送 .github/workflows/ 文件被拒绝。
解决方案:
gh auth refresh -h github.com -s workflow5. 构建超时
症状:Rust 编译或 npm install 超过 GitHub Actions 默认 6 小时限制。
解决方案:
- 启用依赖缓存(
Swatinem/rust-cache、actions/cache) - 使用
CARGO_INCREMENTAL: 0和CARGO_TERM_COLOR: always优化 Rust 编译 - 拆分构建矩阵为多个独立 job
6. Tauri TAURI_SIGNING_PRIVATE_KEY Secret 解析失败(minisign 解不出)
症状:Build Tauri bundle 阶段(cargo tauri build 触发 tauri-plugin-updater 自动签名)报:
Error failed to decode secret key: incorrect updater private key password:
Missing encoded key in secret key原因:TAURI_SIGNING_PRIVATE_KEY env var 被多层 base64 编码了。cargo tauri signer generate 输出的 .key 文件本身已经是一层 base64(348 字节单行),如果 Secret 灌之前再 cat | base64 -w0 又包一层(double-base64),minisign 解码就拿到 base64 字符流,不是合法 minisign 私钥 blob。
解决方案:
- Secret 直接灌文件原文(一层 base64):
gh secret set TAURI_SIGNING_PRIVATE_KEY < ~/.tauri/faropdf.key- 验证:本地试签一遍能跑通即正确:
TAURI_PRIVATE_KEY="$(cat ~/.tauri/faropdf.key)" \
TAURI_PRIVATE_KEY_PASSWORD="..." \
cargo tauri signer sign /tmp/test.txt
# 应该输出 "Your file was signed successfully"- 相关:
tauri.conf.json的pubkey字段也要求base64(2 行 minisign 公钥文件内容)(含untrusted comment: minisign public key: <KEYNUM>header 行),不是.pub文件第二行原文RWS8...。详见references/tauri-release.md§3 密钥链
7. Tauri pubkey 字段格式错
症状:Build Tauri bundle 阶段报:
Error failed to decode pubkey: failed to decode base64 pubkey:
failed to convert base64 to utf8: invalid utf-8 sequence of 1 bytes from index 2或后续报:
failed to convert updater pubkey: Missing encoded key in public key原因:Tauri CLI 的 decode_key 函数(crates/tauri-cli/src/helpers/updater_signature.rs)对 pubkey 字段值先 base64-decode 再 UTF-8 转换,然后 PublicKeyBox::from_string 解析。
- 填原文
RWS8WkTIW8ht2pmQPiablJPY8vRrsXleS6NxLsalJ/Tyn+1tKpHGxREc→ base64-decode 得到二进制 minisign 公钥 bytes(不是 UTF-8)→str::from_utf8失败 - 填
base64(RWS8...)单行(缺 minisign 2 行 header)→ base64-decode 通过但from_string拿到单行不合法 box →into_public_key报 "Missing encoded key in public key"
解决方案:字段值 = base64(2 行 minisign 公钥文件内容):
PUBKEY_B64=$(cat ~/.tauri/faropdf.key.pub | base64 -d | base64 -w0)
# 验:echo -n "$PUBKEY_B64" | base64 -d 应回显两行(含 untrusted comment + RWS8...)写入 tauri.conf.json 的 plugins.updater.pubkey。
8. Windows runner 多行 cargo tauri build \ 反斜杠被 PowerShell 吃掉
症状:Windows runner 的 Build Tauri bundle step 报:
ParserError: ...ps1:3
Line | 3 | --target x86_64-pc-windows-msvc \
| ~
| Missing expression after unary operator '--'.原因:Windows runner 默认 shell 是 PowerShell 7 (pwsh.EXE),\ 反斜杠在 PowerShell 里不是行续字符。下一行 --target 被解析成 PowerShell 表达式 -- unary operator。macOS / Linux 默认 bash 续行正常,没暴露。
解决方案:在 Build Tauri bundle step 显式 shell: bash(GitHub Actions Windows runner 自带 Git Bash):
- name: Build Tauri bundle
env: ...
shell: bash # ← 关键:Windows 也走 Git Bash,跟 macOS / Linux 一致
run: |
cargo tauri build \
--target ${{ matrix.target }} \
--bundles ${{ matrix.bundles }}9. Tauri updater manifest URL 子目录错(create-updater-manifest.mjs 之类自写脚本)
症状:release assets 都在 release 根目录(如 FaroPDF_0.1.0_amd64.AppImage),但 latest.json 的 url 指向子目录:
"url": "https://github.com/.../releases/download/0.1.0/faropdf-linux-x64/appimage/FaroPDF_0.1.0_amd64.AppImage"updater 客户端按这个 url 拉会 404(releases/download/.../<name> 不带子目录)。
原因:
softprops/action-gh-release@v2用files: artifacts/**/*.dmgglob 上传时只用 basename(actions/download-artifact@v4拉到本地时按 artifact 名分子目录,但上传时软化)- 自写 manifest 脚本用
relative(releaseDir, file)算 url,把 artifact 名子目录带进去了
解决方案:自写 manifest 脚本里 url 用 basename(file),不要用 relative(releaseDir, file):
// 错的
const url = buildAssetUrl(args.repo, args.tag, relative(releaseDir, file));
// 对的
import { basename, ... } from "node:path";
const url = buildAssetUrl(args.repo, args.tag, basename(file));10. 重新发布后 CDN 同步延迟(公共 URL 5-15 分钟 404)
症状:gh release view / gh release download 能正常列出 / 下载 assets(走 GitHub API),但 curl https://github.com/.../releases/latest/download/latest.json 公共 CDN URL 一直 404。
原因:GitHub release asset 的 CDN 同步到公共 releases/download/... URL 需要 5-15 分钟(gh CLI/API 用的是另一个 endpoint,先于公共 CDN 生效)。
解决方案:
- 临时验证用
gh release download或gh api repos/<owner>/<repo>/releases/tags/<tag>走 API 路径 - 等 15 分钟后再用 curl 测公共 URL
- 客户端(tauri-plugin-updater)第一次检查更新失败时会有 fallback 重试机制,不影响最终升级
- 不要因为 curl 404 就立刻删 release 重发——asset 已经在 release 上了,重发反而引入新 asset hash 不一致
Release Notes 撰写指南
调研来源
综合以下项目的 Release Notes 实践:
| 项目 | 类型 | 特点 |
|---|---|---|
| Zettlr | Electron Markdown 编辑器 | 自然语言概述 + 分类条目 + PR 链接 |
| Clash Verge Rev | Tauri 桌面应用 | 中文撰写 + emoji 分节 + 平台下载链接 |
| SiYuan | Electron 笔记应用 | shields.io badge + issue 链接式 |
| bat | Rust CLI 工具 | 极简分类 + @贡献者 |
| Typst | Rust 排版系统 | 叙述式安全问题 + 贡献者致谢 |
| Claude Code | CLI 工具 | 高频发布,平铺条目 |
| NiceHash | 桌面应用 | 安装指引优先 + 安全验证 |
参考样例:
- Clash Verge Rev v2.5.1: https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v2.5.1
- Zettlr v4.5.0: https://github.com/Zettlr/Zettlr/releases/tag/v4.5.0
- Obsidian v1.12.7: https://github.com/obsidianmd/obsidian-releases/releases/tag/v1.12.7
- Tauri CLI v2.11.2: https://github.com/tauri-apps/tauri/releases/tag/tauri-cli-v2.11.2
结构选择
发布时先读取 config/projects.yaml:
- 配置了
release_notes.profile:按项目配置指定的结构生成。 - 未配置:桌面应用默认用
desktop-standard,CLI / 库 / Web 按下方适配规则简化。
固定结构
desktop-standard
适用于 Tauri / Electron 桌面应用,尤其是需要用户下载安装包、处理 Gatekeeper / SmartScreen / 终端命令提示的项目。结构固定如下:
1. 一句话摘要:正文第一行,使用 blockquote,不写版本标题。 2. ## Highlights:2-5 条用户最关心的变化。 3. ## 新增:仅在有新增功能时出现。 4. ## 变更:仅在有行为、流程、配置、依赖或发布链路变化时出现。 5. ## 修复:仅在有 bug fix 时出现。 6. > [!WARNING]:有安装限制、终端命令、破坏性变更、安全提示时必须出现。 7. ## 下载:桌面应用必须出现,列出面向用户的安装包。 8. 自动更新产物说明:有 .tar.gz / .sig / latest.json 时必须说明普通用户无需下载。 9. 完整变更日志:最后一行使用 compare 链接。
新增 / 变更 / 修复 中没有内容的分区直接省略,不保留空标题。
推荐模板
> 一句话概括本版本核心变更(让用户 5 秒内理解为什么要升级)
---
> [!NOTE]
> 升级提示(仅在有需要时出现)
## Highlights
- **核心特性 1**:简短描述,突出用户价值
- **核心特性 2**:简短描述
---
## 新增
- 功能描述 (#PR号)
## 变更
- 行为变更描述 (#PR号)
## 修复
- 修复描述 (#PR号)
---
> [!WARNING]
> 破坏性变更说明(仅在存在时包含此节)
---
## 下载
| 平台 | 架构 | 文件 |
|------|------|------|
| macOS | Apple Silicon | `<项目>_<版本>_aarch64.dmg` |
| macOS | Intel | `<项目>_<版本>_x64.dmg` |
| Windows | x64 | `<项目>_<版本>_x64-setup.exe` |
> `.tar.gz` + `.sig` 为自动更新专用,无需手动下载。
---
**完整变更日志**: https://github.com/<owner>/<repo>/compare/<上个tag>...vX.Y.Z设计决策
| 决策 | 依据 |
|---|---|
| 正文不写版本标题 | GitHub Release 页面已经显示 release title,正文再写 # <项目名> vX.Y.Z 会重复;正文应直接从摘要、提示或 Highlights 开始 |
| 中文撰写 | Clash Verge Rev(Tauri 项目)验证中文 Release Notes 完全可行 |
| 顶部一句话 Highlights | Zettlr / Clash Verge 实践:小项目用户不会逐条读 changelog |
| 固定桌面应用结构 | Clash Verge Rev 重视下载区,Zettlr 重视摘要和 changelog,Folia 需要额外稳定呈现 macOS 终端提示 |
| 表格列下载链接 | 比 ### / #### 分级轻量,比纯链接结构化,适合 Folia 这种多平台桌面应用 |
> [!WARNING] 标注破坏性变更 | GitHub 原生 callout,视觉醒目 |
| 每条附 PR 号 | Zettlr / SiYuan / bat 的共识做法,可追溯 |
| Full Changelog 比较链接 | Zettlr 的做法,一键查看完整 diff |
不同项目类型的适配
桌面应用(Tauri / Electron)
默认使用 desktop-standard。下载表格列出各平台安装包。标注"推荐"和"不常用"。自动更新产物(.tar.gz / .sig / latest.json)单独说明。
CLI 工具 / 库
不需要下载表格。改为安装命令:
## 安装
npm install <package>@X.Y.Z
# 或
cargo install <package>Web 应用
不需要下载表格。改为部署说明或 changelog 链接。
高频发布项目(每天/每周)
参考 Claude Code 的极简格式:所有条目平铺在 ## What's changed 下,不做分类。
不需要的内容
- shields.io badge(适合大项目,小项目过于复杂)
- 安全 checksums(除非面向安全敏感用户或用户明确要求)
- Cargo audit / 构建日志(框架层细节,应用层不需要)
- 赞助提示(可选,非必须)
Tauri 桌面应用发布指南
基于 Tauri v2 的桌面应用发布特有事项。
CI 工作流配置
推荐使用 tauri-apps/tauri-action,分离 build 和 publish 两个 job:
name: release
on:
push:
tags:
- 'v*'
permissions:
contents: write
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: true
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- platform: macos-latest
args: --target aarch64-apple-darwin
- platform: macos-latest
args: --target x86_64-apple-darwin
- platform: windows-latest
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- uses: Swatinem/rust-cache@v2
with:
workdir: src-tauri
- run: pnpm install
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 仅自动更新需要:签名密钥
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: ${{ github.ref_name }}
releaseName: 'App ${{ github.ref_name }}'
releaseDraft: true
prerelease: false
# 不用自动更新时设为 false,用自动更新时设为 true
includeUpdaterJson: false
args: ${{ matrix.args }}
# 仅自动更新需要此 job:生成 latest.json 并发布
publish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate latest.json
run: |
# 从 draft release 下载 .sig 文件,生成 latest.json
- name: Upload and publish
run: |
gh release upload "${{ github.ref_name }}" latest.json --clobber
gh release edit "${{ github.ref_name }}" --draft=false配置要点
包管理器
跨平台构建(macOS + Windows)推荐 pnpm。npm/bun 存在 optional dependencies bug,macOS lock file 不包含 Windows 原生绑定。
仅构建 macOS 时 npm ci 可用,但统一使用 pnpm 可避免后续扩展 Windows 矩阵时踩坑。
includeUpdaterJson
includeUpdaterJson: false + 手动生成 latest.json 可精确控制平台键名和 URL 格式,避免 tauri-action 内置生成器产生冗余键。
分离 build 和 publish
单 job 模式无法在发布前验证所有平台构建成功,也无法在发布前自定义 latest.json。build job 上传到 draft release,publish job 在全部成功后发布。
concurrency
移动 tag 会触发重复构建,添加 concurrency 配置避免。
构建产物
仅安装包(不需要自动更新)
Pake 等项目只发布安装包,不包含更新器产物。适用于用户手动下载更新的场景。
| 平台 | 安装包 |
|---|---|
| macOS ARM | App_X.Y.Z_aarch64.dmg |
| macOS Intel | App_X.Y.Z_x64.dmg |
| Windows | App_X.Y.Z_x64-setup.exe |
带自动更新
lencx/ChatGPT 等项目在安装包之外,还包含更新器所需的产物。更新器通过 latest.json 检查版本、下载对应平台 bundle、用 .sig 验证完整性。
| 平台 | 安装包 | 更新器产物 | 签名 |
|---|---|---|---|
| macOS ARM | App_X.Y.Z_aarch64.dmg | App_aarch64.app.tar.gz | .sig |
| macOS Intel | App_X.Y.Z_x64.dmg | App_x64.app.tar.gz | .sig |
| Windows | App_X.Y.Z_x64-setup.exe | — | .exe.sig |
Windows 只需 .exe(NSIS),不需要额外发布 .msi。签名文件 .sig 和 latest.json 一起放在 release assets 中,更新端点直接用 GitHub 直链:
https://github.com/<owner>/<repo>/releases/latest/download/latest.jsonlatest.json 格式
Tauri updater 需要一个 latest.json,包含版本号、签名和各平台下载 URL。
{
"version": "X.Y.Z",
"notes": "发布说明",
"pub_date": "2026-05-20T00:00:00Z",
"platforms": {
"darwin-aarch64": { "signature": "...", "url": "..." },
"darwin-x86_64": { "signature": "...", "url": "..." },
"windows-x86_64": { "signature": "...", "url": "..." }
}
}平台键名必须使用标准格式(darwin-aarch64 / darwin-x86_64 / windows-x86_64),避免 darwin-aarch64-app 等非标准键名。
国内镜像同步
目标用户包含国内用户时,可在 publish job 中同步到 Gitee:创建 Gitee Release → 上传构建产物 → 生成 Gitee 专属 latest.json → 上传。
需要 GitHub Secrets:GITEE_TOKEN、GITEE_OWNER。
Gitee 没有 releases/latest/download/ 直链,作为 updater endpoint 前需验证可访问性。
跨平台 CI 必踩坑(stable Rust 2026-05 之后 + tauri-plugin-updater)
下面这些坑是真实项目里 4 轮 CI 全 fail 的根因。新项目第一次配 release.yml 前必看,避免重蹈。
1. universal-apple-darwin rust-std 在 stable Rust 2026-05 之后被移除
dtolnay/rust-toolchain@stable 装 rustup 时不再带 universal-apple-darwin rust-std component。cargo tauri build --target universal-apple-darwin 报:
component 'rust-std' for target 'universal-apple-darwin' is unavailable for download修法:matrix.macos-universal.rust_targets 拆成两个 underlying arch target,让 Tauri CLI 用 lipo 合并:
- label: macos-universal
os: macos-latest
target: universal-apple-darwin # Tauri CLI 识别这个 target 名
rust_targets: aarch64-apple-darwin,x86_64-apple-darwin # rustup 装这两个
bundles: app,dmg
artifact_glob: |
src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app
src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz
src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg2. jammy (Ubuntu 22.04) 仓库无 libappindicator3-dev
sudo apt-get install libappindicator3-dev 报:
libappindicator3-dev : Depends: libappindicator3-1 ...
libayatana-appindicator3-dev : Conflicts: libappindicator3-dev修法:apt 列表里只装 libayatana-appindicator3-dev(jammy 唯一可用变体):
- name: Install Linux system dependencies
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
librsvg2-dev \
patchelf \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev # ← 唯一可用变体3. Windows runner 默认 shell 是 PowerShell 7
多行 cargo tauri build \ 反斜杠续行在 PowerShell 7(pwsh.EXE)下被吃掉,下一行 --target 变 PowerShell 表达式:
ParserError: ...ps1:3
Line | 3 | --target x86_64-pc-windows-msvc \
| ~
| Missing expression after unary operator '--'.修法:在 Build Tauri bundle step 显式 shell: bash(GitHub Actions Windows runner 自带 Git Bash),三平台都走 bash 续行一致:
- name: Build Tauri bundle
env: ...
shell: bash # ← 关键
run: |
cargo tauri build \
--target ${{ matrix.target }} \
--bundles ${{ matrix.bundles }}4. concurrency.cancel-in-progress: false 会卡住重试链
cancel-in-progress: false 时,移动 tag 触发的第二次 run 排在前一个之后,前一个 cancelled 但 slot 还没释放,新 run 一直 pending 几分钟。
修法:用 cancel-in-progress: true,新 run 立刻抢占 slot:
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: true5. GitHub release CDN 同步延迟(5-15 分钟)
新 release 的公共 URL https://github.com/<owner>/<repo>/releases/latest/download/latest.json 在 git push tag 后 5-15 分钟内会 404(CDN 同步延迟),但 gh release view / gh release download / GitHub API 已经能正常访问(走另一 endpoint)。
临时验证用 gh release download:
gh release download v0.1.0 --pattern 'latest.json' --dir /tmp/check不要因为 curl 404 就删 release 重发——asset 已经在 release 上了,重发会引入新 asset hash 不一致。
6. 升级 keypair / pubkey 时所有相关文件都要改
Tauri updater 的信任链涉及 3 个地方:
| 位置 | 字段 | 期望格式 |
|---|---|---|
| GitHub Secret | TAURI_SIGNING_PRIVATE_KEY | 文件原文(一层 base64,不要再 base64 -w0) |
src-tauri/tauri.conf.json | plugins.updater.pubkey | base64(2 行 minisign 公钥文件内容)(含 untrusted comment: minisign public key: <KEYNUM> header) |
| GitHub Secret | TAURI_SIGNING_PRIVATE_KEY_PASSWORD | 私钥加密时用的密码明文 |
只改其中一处会断链。验:TAURI_PRIVATE_KEY="$(cat ~/.tauri/<key>)" TAURI_PRIVATE_KEY_PASSWORD="..." cargo tauri signer sign /tmp/test.txt 本地能跑通即 Secret + 密码对。
7. cargo tauri signer --help 会 dump 环境变量明文
clap 把 env var 默认值显示在 --help 输出里。如果 shell session 已经 export 了 TAURI_PRIVATE_KEY_PASSWORD,跑 cargo tauri signer sign --help 会把密码明文 print 到 stderr —— 进 transcript / CI log。
修法:在含密钥的 shell session 里不要跑 cargo tauri signer --help / sign --help 之类。需要查用法时新开一个干净 shell(不 source 含密钥的 env),或查源码(crates/tauri-cli/src/signer.rs)。
完整 SOP(首次配 + 升级)
1. 一次性:cargo tauri signer generate -p "<STRONG_PASSWORD>" -w ~/.tauri/<project>.key 2. 写 tauri.conf.json pubkey:base64 -w0 < (cat ~/.tauri/<project>.key.pub | base64 -d) 3. gh secret set TAURI_SIGNING_PRIVATE_KEY < ~/.tauri/<project>.key(直接灌文件,不要 base64 -w0) 4. gh secret set TAURI_SIGNING_PRIVATE_KEY_PASSWORD "<STRONG_PASSWORD>" 5. 本地试签验证三件套对:env 灌齐 cargo tauri signer sign /tmp/test.txt 6. git tag vX.Y.Z && git push origin vX.Y.Z 触发 release.yml