Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

17 KiB
Raw Permalink Blame History

GitHub Actions 自动构建

本仓库提供六条流水线:

  • Build:在 main push、Pull Request 和手动触发时运行。执行 XcodeGen、Debug 测试,并在非 PR 场景额外编译 unsigned Release app 做配置校验;不上传不可分发的未签名产物。
  • Prepare Release:在 GitHub Actions 页面手动触发。输入发布类型、目标版本和是否继续发布;它会检查、bump、提交版本变更、创建 tag,并在需要时显式触发 ReleasePlugin Release
  • Release:在推送 v*.*.*v*.*.*-* tag,或手动输入 tag 时运行。构建 Release 版本,使用 Developer ID 签名、公证、打包 DMG,创建或更新 GitHub Release,并提交最新 docs/appcast.xml
  • Homebrew Cask Update:在 Release 成功完成后,或手动输入版本时运行。读取稳定版 Release 里的 MacTools.dmgMacTools.sha256,通过 brew bump-cask-pr 向官方 Homebrew/homebrew-cask 提交 cask bump PR。
  • Plugin Release:在推送 plugins-* tag,或手动输入插件批次 tag 时运行。它按 PluginKit 版本选择 catalogv2 保留使用 docs/plugins/catalog.jsonv3 及以后使用 docs/plugins/vN/catalog.json;首次 ABI 升级默认全量构建、签名并提交新版本 catalog。
  • Deploy Pages:仅在 ReleasePlugin Release 工作流成功完成后,或手动触发时运行。它先构建 site/ 下的 Astro 官网,再合并 docs/ 中的 appcast、插件 catalog、图标库等静态发布资源并发布到 GitHub Pages;普通 push / PR 不会触发这条流水线。

需要配置的 Secrets

进入 GitHub 仓库:SettingsSecrets and variablesActionsRepository secrets,添加以下条目。

Secret 用途
APPLE_DEVELOPMENT_TEAM Apple Developer Team ID,用于生成 LocalConfig.xcconfig
BUNDLE_IDENTIFIER_PREFIX Bundle ID 前缀,例如 com.example,最终 app id 为 <prefix>.mactools
DEVELOPER_ID_CERT_P12 Developer ID Application 证书 .p12 文件的 Base64 内容。
DEVELOPER_ID_CERT_PASSWORD 导出 .p12 时设置的密码。
ASC_API_KEY_P8_BASE64 App Store Connect API Key .p8 文件的 Base64 内容,用于 notarization。
ASC_API_KEY_ID App Store Connect API Key ID。
ASC_API_ISSUER_ID App Store Connect Issuer ID。
SPARKLE_PRIVATE_KEY Sparkle EdDSA 私钥,必须与 project.yml 中的 SPARKLE_PUBLIC_ED_KEY 配对。
PLUGIN_CATALOG_PRIVATE_KEY_BASE64 插件 catalog Ed25519 私钥的 Base64 内容,用于签名当前 PluginKit 版本的 catalog。
HOMEBREW_GITHUB_API_TOKEN 可选。GitHub Personal Access Token,至少需要 public_repo 权限,用于稳定版发布后通过 brew bump-cask-pr 自动向官方 Homebrew/homebrew-cask 提交 cask bump PR;未配置时 Homebrew 同步会失败,可手动运行 workflow 或手动提交 PR。

不要把 LocalConfig.xcconfig.p12.p8、Sparkle 私钥、证书密码或 Apple ID 写入仓库。

准备证书 Secret

  1. 在 Keychain Access 中导出 Developer ID Application 证书和私钥为 .p12
  2. .p12 设置一个强密码,并保存到 DEVELOPER_ID_CERT_PASSWORD
  3. .p12 转为单行 Base64
base64 -i DeveloperIDApplication.p12 | tr -d '\n' | pbcopy
  1. 将剪贴板内容保存到 DEVELOPER_ID_CERT_P12

准备公证 Secret

  1. 在 App Store Connect 创建 API Key,并下载 .p8 文件。
  2. 记录 Key ID 和 Issuer ID。
  3. .p8 转为单行 Base64
base64 -i AuthKey_XXXXXXXXXX.p8 | tr -d '\n' | pbcopy
  1. 将剪贴板内容保存到 ASC_API_KEY_P8_BASE64
  2. 将 Key ID 保存到 ASC_API_KEY_IDIssuer ID 保存到 ASC_API_ISSUER_ID

准备 Sparkle Secret

将当前发布使用的 Sparkle EdDSA 私钥保存到 SPARKLE_PRIVATE_KEY。它必须与 project.yml 中的 SPARKLE_PUBLIC_ED_KEY 配对,否则旧版本应用无法验证新的更新包。

如果你只在本机钥匙串中保存了 Sparkle 私钥,请先确认能用本机 sign_update 签名当前 DMG;不要为了 CI 随意生成新密钥,除非你计划同时处理已发布版本的更新兼容。

准备插件 Catalog Secret

插件 catalog 使用独立 Ed25519 key。公钥 PLUGIN_CATALOG_PUBLIC_KEY 可以写入 Release.xcconfig 并随 app 发布;私钥必须保存到 GitHub Secret PLUGIN_CATALOG_PRIVATE_KEY_BASE64

生成一对新的 catalog key

python3 - <<'PY'
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
import base64

key = Ed25519PrivateKey.generate()
private = key.private_bytes(
    encoding=serialization.Encoding.Raw,
    format=serialization.PrivateFormat.Raw,
    encryption_algorithm=serialization.NoEncryption(),
)
public = key.public_key().public_bytes(
    encoding=serialization.Encoding.Raw,
    format=serialization.PublicFormat.Raw,
)

print("PLUGIN_CATALOG_PRIVATE_KEY_BASE64=" + base64.b64encode(private).decode())
print("PLUGIN_CATALOG_PUBLIC_KEY=" + base64.b64encode(public).decode())
PY

不要复用 Sparkle 私钥。Sparkle key 只负责 app 更新包,插件 catalog key 只负责插件列表。

App 发布方式

推荐用 GitHub Actions 的 Prepare Release 触发完整发布准备流程:

  1. 打开 ActionsPrepare ReleaseRun workflow
  2. type 选择 app
  3. version 输入目标版本,例如 1.0.7,不要带 v
  4. 勾选 release 时,准备流程会在创建 v1.0.7 后继续触发 Release workflow;不勾选时只 bump、提交并打 tag。

本地也可以用 make release 执行同样的准备流程:

make release

命令会交互选择发布类型、分析当前版本和最新 tag、选择 patch/minor/major,并先展示 bump 预览;确认后才自动 git pull --rebase、运行轻量检查、更新版本文件、提交版本 bump、创建并推送 tag。App 发布会推送 v*.*.* tag,后续构建、签名、公证、上传 GitHub Release、更新 Sparkle appcast 由 Release workflow 完成;官方 Homebrew cask 的版本更新 PR 由 Homebrew Cask Update workflow 跟进。

非交互示例:

make release ARGS="--type app --version 1.0.7 --yes"

Configs/AppVersion.xcconfig 是 App 与内嵌 extension 的共享发布版本源。若需要手动处理,发布前先更新:

MARKETING_VERSION = 0.9.3
CURRENT_PROJECT_VERSION = 15

提交并推送版本号变更:

git add Configs/AppVersion.xcconfig
git commit -m "Bump version to 0.9.3"
git push origin main

然后在同一个提交上打 tag 并推送:

git tag v0.9.3
git push origin v0.9.3

Release 工作流会校验 v0.9.3Configs/AppVersion.xcconfigMARKETING_VERSION = 0.9.3 一致,并使用 CURRENT_PROJECT_VERSION 作为 Sparkle appcast 和 App 包里的 build 号。版本不一致时会直接失败,避免产物、tag 和 appcast 不一致。

也可以在 GitHub Actions 页面手动运行 Release,输入已存在的 tag,例如 v0.9.3;该 tag 指向的提交里仍必须已经更新 Configs/AppVersion.xcconfig

插件发布方式

推荐用 GitHub Actions 的 Prepare Release 发布插件批次:

  1. 打开 ActionsPrepare ReleaseRun workflow
  2. type 选择 plugin
  3. version 输入插件批次版本,例如 1.0.9,不要带 plugins-
  4. plugin_mode 选择 autoselectedallselected 时在 plugins 输入插件 ID 或目录名,多个用逗号分隔。
  5. 勾选 release 时,准备流程会在创建 plugins-1.0.9 后继续触发 Plugin Release workflow;不勾选时只 bump、提交并打 tag。

本地也可以用 make release 发布插件批次:

make release ARGS="--type plugin"

默认 auto 模式会读取当前 PluginKit 版本的 catalog;首次 v3 发布时以旧 v2 catalog 作为比较基线。它会找出新插件、已手动 bump 的插件、pluginKitVersion 已变化的插件,以及包相关文件变化但版本未递增的插件。ABI 变化会自动切换为全量发布;对未递增的插件会按选择的 patch/minor/major 自动更新 plugin.json.version。随后命令会运行 make generate 和发布计划检查,提交版本 bump,推送 plugins-* 批次 tag。

常用非交互示例:

make release ARGS="--type plugin --version 1.0.10 --yes"
make release ARGS="--type plugin --version 1.0.10 --plugin-mode selected --plugin calendar --yes"
make release ARGS="--type plugin --version 1.1.0 --plugin-mode all --yes"

插件按批次单独发布,不和 app DMG 混在同一条 Release。相同 PluginKit 版本内默认是增量发布:只构建和上传本批实际变化的插件包,然后把这些新条目合并进对应的版本化 catalog。首次 ABI 升级则全量构建并生成新 catalog,旧版本 catalog 不会被覆盖。

应用内是否显示“可更新”只比较插件版本,不比较 batch tag 或 asset URL。因此只有实际变化的插件需要递增各自 plugin.json.version;未变化插件不会因为新批次 tag 而显示可更新或无效。

pluginKitVersion 是插件 ABI 边界。升级 PluginKit 时必须全量重建插件包并递增每个插件自己的 plugin.json.version。发布脚本会在 ABI 变化时自动使用 plugin_mode=all,并将完整 catalog 写入 docs/plugins/vN/catalog.json;它禁止把不同 pluginKitVersion 的插件混进同一个 catalog,避免新宿主加载旧 ABI 插件导致启动崩溃。

推送插件批次 tag

git tag plugins-1.0.1
git push origin plugins-1.0.1

Plugin Release 工作流会:

  1. origin/main 读取当前 PluginKit 版本的 catalog 作为基线;首次 v3 发布时回退到旧 docs/plugins/catalog.json,仅用于版本比较。
  2. 生成增量发布计划。auto 模式会选择新插件和 plugin.json.version 高于上一版 catalog 的插件。
  3. 如果插件自身源码、资源或 pluginKitVersion 有包相关变化,但插件版本没有递增,工作流会失败并提示需要 bump 对应 plugin.json.versionMacToolsPluginKit ABI 变化会自动切换到 mode=all 全量重发;展示或宿主侧改动如果也需要重发,可使用 mode=all 或传入特定 --shared-path
  4. 只以 Release 配置构建计划中的插件 target。
  5. 用 Developer ID 重新签名这些插件 bundle,并打包为 *.mactoolsplugin.zip
  6. 创建或更新对应的 plugins-* GitHub Release,并只上传本批变化插件的 zip。catalog-only 变化可以创建没有 zip asset 的插件 Release。
  7. 相同 ABI 内生成本批 delta catalog 并合并进该 ABI 的 catalog;ABI 首次升级则生成包含全部插件的完整 catalog。
  8. 使用 PLUGIN_CATALOG_PRIVATE_KEY_BASE64 签名 catalog,并写入 docs/plugins/catalog.jsonv2)或 docs/plugins/vN/catalog.jsonv3+)。
  9. 将对应版本化 catalog 提交回 main,再由 Deploy Pages 发布到 GitHub Pages。

如果 auto 模式没有发现插件包或 catalog 变化,工作流会成功结束,不创建或更新 GitHub Release。

也可以在 GitHub Actions 页面手动运行 Plugin Release

  • mode=auto:默认推荐,自动检测变化插件。
  • mode=selected:只发布 plugins 输入中列出的插件 ID 或目录名,例如 calendar,display-brightness
  • mode=all:全量重建并上传当前所有插件包,适合证书、打包逻辑或插件运行时发生全局变化后的兜底发布。

插件 release asset 形态:

appearance.mactoolsplugin.zip
calendar.mactoolsplugin.zip
disk-clean.mactoolsplugin.zip

每个 zip 内部保留目录包:

appearance.mactoolsplugin/
  plugin.json
  Appearance.bundle/

Release Notes 规范

App 和 Plugin Release 共用 CHANGELOG.md 作为唯一更新日志来源,但各自生成不同的 release notes。开发完成用户可见变更时,先添加一个简洁英文 fragment 到 changes/unreleased/*.mdscripts/release.py --type app 只消费 release: app fragments 并写入 ## [vX.Y.Z]scripts/release.py --type plugin 只消费 release: plugin fragments 并写入 ## [plugins-X.Y.Z]。已消费 fragments 会在发布提交中删除。随后对应 workflow 从 tag 对应的 CHANGELOG.md 段落提取 MarkdownApp notes 写入 GitHub ReleasePlugin notes 写入插件批次 GitHub Release。Sparkle appcast 使用单独生成的英文说明,在 App Updates 后附加自上一个 App 版本以来发布的插件条目,并按 changelog 类型合并到 Plugin Updates;没有插件更新时省略该分组,因此不会改变 GitHub Release 的日志内容。

fragment 使用简化的 front matter

---
release: app
type: fixed
area: Finder Integration
---

Finder right-click menu items now stay hidden when the plugin is disabled.

release 必须是 appplugin,决定哪类发布会消费该 fragment。type 会映射到 Keep a Changelog 风格分组:

Type Release 分组 用途
summary Summary 版本顶部的一句话产品摘要,通常发布前再补。
added Added 新功能、用户可感知的新能力。
changed Changed 行为、交互、文案或默认值变化。
fixed Fixed Bug 修复、稳定性修复。
security Security 安全相关修复。
removed Removed 已移除能力或兼容性。
deprecated Deprecated 即将移除或不推荐继续使用。
maintenance Maintenance 维护者需要知道的构建、发布或依赖变化。

更新日志应面向用户或维护者可读,保持英文、短句、少量信息密度。不要把 git log、实现细节、测试重构、重复描述或长篇背景写进 fragment。多个 fragment 内容完全相同时,发布脚本会按文本去重;语义重复仍应由 agent 在提交前合并成一条更好的描述。

如果同一个修改同时影响 App 发布和插件批次发布,应写两份 fragment:release: app 描述宿主或 App 层面的影响,release: plugin 描述插件包层面的影响。不要把同一句复制到两个 release channel;如果用户只需要在其中一个发布说明里看到该变化,就只写对应的 fragment。

发布版本本身的提交,例如 chore: release v1.0.28,不需要 fragment。

如果某个版本需要像产品公告一样在自动列表上方放一段手写摘要,可以在推 tag 前添加:

.github/release-highlights/v0.14.0.md

文件内容会被原样置顶到 CHANGELOG.md 中提取的 release notes 前。没有对应文件时,Release 工作流只使用 CHANGELOG.md 当前版本段落。该文件也会同步进入 Sparkle 更新弹窗。

稳定版发布成功创建 GitHub Release 后,Homebrew Cask Update 工作流会在配置了 HOMEBREW_GITHUB_API_TOKEN 时确认 MacTools.dmg 存在、读取 MacTools.sha256,并通过 brew bump-cask-pr mactools --version ... --sha256 ... 向官方 Homebrew/homebrew-cask 打开版本更新 PR。不要传入 GitHub release asset 的展开下载 URL;官方 cask 应保留 v#{version} URL 模板,否则 Homebrew audit 会把它当成未版本化 URL。预发布版本不会成为默认稳定版;未配置该 secret 时可以手动运行 workflow 或手动提交 Homebrew PR。Homebrew PR 合并后,用户本地运行 brew update 后即可通过 brew upgrade --cask --greedy mactools 检测到新版本。

仓库设置中需要允许 workflow 写入:SettingsActionsGeneralWorkflow permissions 选择 Read and write permissions

GitHub Pages 发布源

为了避免普通 main push 触发 GitHub 内置的 pages-build-deployment,需要在仓库设置里把 Pages 发布源改为 Actions

  1. 进入 SettingsPages
  2. Build and deploymentSource 选择 GitHub Actions
  3. 保存后由本仓库的 Deploy Pages workflow 负责发布 docs/

如果 Pages 仍配置为 Deploy from a branch,GitHub 会继续在所选分支有提交时自动运行内置的 pages-build-deployment。这个内置流水线不是由仓库里的 workflow YAML 控制的,不能只靠修改 .github/workflows/ 禁止。

安全策略

  • PR 构建不读取发布 Secrets,只执行未签名构建和测试。
  • Release 工作流只使用 contents: write 创建或更新 GitHub Release,并把 docs/appcast.xml 提交回 main;普通 Build 工作流只有 contents: read
  • Plugin Release 工作流只使用 contents: write 创建或更新插件批次 Release,并把当前 PluginKit 版本的签名 catalog 提交回 mainv2 仍写入旧 docs/plugins/catalog.jsonv3+ 写入版本化路径。
  • Deploy Pages 工作流只在 Release 或 Plugin Release 成功后发布 docs/,使用 contents: readpages: writeid-token: write
  • 签名证书导入临时 keychain,任务结束后清理。
  • App Store Connect .p8、Sparkle 私钥和插件 catalog 私钥只写入 runner 临时目录或进程环境,使用后删除。
  • 日志不主动输出 Team ID、Bundle 前缀、证书名称、私钥或证书内容。