macos-release

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Release macOS App

发布macOS应用

This skill covers the full release pipeline for distributing a native macOS app outside the Mac App Store via GitHub Releases with Sparkle auto-update support.
此技能覆盖了完整的发布流程,用于在Mac App Store之外,通过GitHub Releases分发带有Sparkle自动更新支持的原生macOS应用。

Release Pipeline Overview

发布流程概览

Bump version → Archive → Notarize → Export → Create DMG → Sign DMG → Update appcast.xml → Git push → GitHub Release
Bump version → Archive → Notarize → Export → Create DMG → Sign DMG → Update appcast.xml → Git push → GitHub Release

Prerequisites

前置条件

The user needs these tools installed:
ToolInstallPurpose
create-dmg
brew install create-dmg
Creates the DMG installer
gh
brew install gh
Creates GitHub releases
git
Built-inPushes appcast changes
Sparkle
sign_update
Built automatically when the project is built with SparkleEdDSA-signs the DMG
The
sign_update
binary lives in DerivedData after building the project in Xcode:
bash
find ~/Library/Developer/Xcode/DerivedData -name "sign_update" -type f 2>/dev/null | head -1
用户需要安装以下工具:
工具安装方式用途
create-dmg
brew install create-dmg
制作DMG安装包
gh
brew install gh
创建GitHub releases
git
系统自带推送appcast变更
Sparkle
sign_update
项目使用Sparkle构建时自动生成对DMG进行EdDSA签名
sign_update
二进制文件在Xcode构建项目后存放在DerivedData中:
bash
find ~/Library/Developer/Xcode/DerivedData -name "sign_update" -type f 2>/dev/null | head -1

Step-by-Step Release Guide

分步发布指南

1. Bump Version Numbers

1. 提升版本号

In Xcode, update:
  • MARKETING_VERSION
    (e.g.,
    1.2
    ) -- the user-facing version
  • CURRENT_PROJECT_VERSION
    (e.g.,
    5
    ) -- the build number (must be unique per release)
Or via command line:
bash
undefined
在Xcode中,更新以下内容:
  • MARKETING_VERSION
    (例如
    1.2
    )—— 用户可见的版本号
  • CURRENT_PROJECT_VERSION
    (例如
    5
    )—— 构建号(每个版本必须唯一)
也可以通过命令行操作:
bash
undefined

Check current values

Check current values

grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION" YourApp.xcodeproj/project.pbxproj | head -4
undefined
grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION" YourApp.xcodeproj/project.pbxproj | head -4
undefined

2. Archive in Xcode

2. 在Xcode中归档

Product > Archive. This creates a release build with the proper signing identity.
Product > Archive。这会使用正确的签名身份创建发布构建版本。

3. Notarize and Export

3. 公证与导出

In the Archives organizer:
  1. Select the archive > Distribute App
  2. Choose "Direct Distribution" (or "Developer ID" for notarization)
  3. Wait for notarization to complete
  4. Export to
    ~/Downloads/YourApp.app
在归档管理器中:
  1. 选中归档文件 > Distribute App
  2. 选择「Direct Distribution」(或用于公证的「Developer ID」)
  3. 等待公证完成
  4. 导出到
    ~/Downloads/YourApp.app

4. Create DMG

4. 制作DMG

bash
create-dmg \
  --volname "YourApp" \
  --window-pos 200 120 \
  --window-size 660 400 \
  --icon-size 160 \
  --icon "YourApp.app" 180 170 \
  --app-drop-link 480 170 \
  --hide-extension "YourApp.app" \
  ~/Downloads/YourApp.dmg \
  ~/Downloads/YourApp.app
bash
create-dmg \
  --volname "YourApp" \
  --window-pos 200 120 \
  --window-size 660 400 \
  --icon-size 160 \
  --icon "YourApp.app" 180 170 \
  --app-drop-link 480 170 \
  --hide-extension "YourApp.app" \
  ~/Downloads/YourApp.dmg \
  ~/Downloads/YourApp.app

5. Sign DMG with Sparkle

5. 使用Sparkle对DMG签名

bash
/path/to/sign_update ~/Downloads/YourApp.dmg
This outputs the EdDSA signature and file length:
sparkle:edSignature="BASE64..." length="12345"
Save both values for the appcast.
bash
/path/to/sign_update ~/Downloads/YourApp.dmg
这会输出EdDSA签名和文件长度:
sparkle:edSignature="BASE64..." length="12345"
保存这两个值,用于后续更新appcast。

6. Update appcast.xml

6. 更新appcast.xml

Add a new
<item>
at the top of the
<channel>
in your
appcast.xml
:
xml
<item>
  <title>Version 1.2 (Build 5)</title>
  <pubDate>Mon, 26 May 2026 12:00:00 +0000</pubDate>
  <sparkle:version>5</sparkle:version>
  <sparkle:shortVersionString>1.2</sparkle:shortVersionString>
  <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
  <description><![CDATA[<ul><li>Feature one</li><li>Bug fix two</li></ul>]]></description>
  <enclosure url="https://github.com/OWNER/REPO/releases/download/v1.2/YourApp.dmg"
             type="application/octet-stream"
             sparkle:edSignature="THE_SIGNATURE_FROM_STEP_5"
             length="THE_LENGTH_FROM_STEP_5" />
</item>
The
pubDate
should be RFC 2822 format. Generate it:
bash
date -R
在你的
appcast.xml
<channel>
顶部添加一个新的
<item>
xml
<item>
  <title>Version 1.2 (Build 5)</title>
  <pubDate>Mon, 26 May 2026 12:00:00 +0000</pubDate>
  <sparkle:version>5</sparkle:version>
  <sparkle:shortVersionString>1.2</sparkle:shortVersionString>
  <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
  <description><![CDATA[<ul><li>Feature one</li><li>Bug fix two</li></ul>]]></description>
  <enclosure url="https://github.com/OWNER/REPO/releases/download/v1.2/YourApp.dmg"
             type="application/octet-stream"
             sparkle:edSignature="THE_SIGNATURE_FROM_STEP_5"
             length="THE_LENGTH_FROM_STEP_5" />
</item>
pubDate
需为RFC 2822格式。可通过以下命令生成:
bash
date -R

7. Commit and Push Appcast

7. 提交并推送appcast

bash
git add appcast.xml
git commit -m "Release v1.2 appcast"
git push origin main
bash
git add appcast.xml
git commit -m "Release v1.2 appcast"
git push origin main

8. Create GitHub Release

8. 创建GitHub Release

bash
gh release create v1.2 \
  ~/Downloads/YourApp.dmg \
  --title "v1.2" \
  --notes "- Feature one
- Bug fix two"
bash
gh release create v1.2 \
  ~/Downloads/YourApp.dmg \
  --title "v1.2" \
  --notes "- Feature one
- Bug fix two"

Automating the Pipeline

流程自动化

For frequent releases, build a CLI tool that automates steps 4-8. See
references/release-pipeline.md
for a template Go CLI that handles DMG creation, signing, appcast updates, and GitHub release creation in one command.
The CLI should:
  • Find
    sign_update
    in DerivedData automatically
  • Read version/build from the exported app's Info.plist via
    plutil
  • Parse and update appcast.xml (preserving existing entries)
  • Interactively collect release notes
  • Show a summary and ask for confirmation before proceeding
  • Create the GitHub release with the DMG attached
对于频繁发布的场景,可以构建一个CLI工具来自动化第4-8步。参考
references/release-pipeline.md
中的Go CLI模板,它可以在一条命令中完成DMG制作、签名、appcast更新和GitHub Release创建。
该CLI应具备以下功能:
  • 自动在DerivedData中查找
    sign_update
  • 通过
    plutil
    从导出应用的Info.plist中读取版本/构建号
  • 解析并更新appcast.xml(保留现有条目)
  • 交互式收集发布说明
  • 显示摘要并在执行前请求确认
  • 创建附带DMG的GitHub Release

Release CLI Tool

发布CLI工具

This repo includes a Go CLI that automates steps 4-8 (DMG creation through GitHub release) in a single command. It reads configuration from a
release.json
file in your project root.
此仓库包含一个Go CLI,可在单条命令中自动化第4-8步(从DMG制作到GitHub Release)。它从项目根目录的
release.json
文件中读取配置。

Setup

配置步骤

  1. Create a
    release.json
    in your project root (see
    cli/release.example.json
    ):
json
{
  "app_name": "MyApp",
  "github_repo": "owner/myapp"
}
Only
app_name
and
github_repo
are required. Everything else has sensible defaults:
FieldDefaultDescription
bundle_name
{app_name}.app
The .app bundle filename
dmg_name
{app_name}.dmg
Output DMG filename
git_branch
main
Branch to push appcast to
min_system_version
14.0
Sparkle minimum macOS version
appcast_file
appcast.xml
Appcast filename in repo root
derived_data_prefixes
["{app_name}-"]
DerivedData prefixes to search for
sign_update
  1. Run the CLI from your project directory:
bash
go run github.com/fayazara/macos-app-skills/release/cli@latest
Or clone this repo and run locally:
bash
go run ./release/cli
The CLI is interactive -- it prompts for release notes and asks for confirmation before proceeding. It must be run in a terminal the user can interact with.
  1. 在项目根目录创建
    release.json
    (参考
    cli/release.example.json
    ):
json
{
  "app_name": "MyApp",
  "github_repo": "owner/myapp"
}
仅需填写
app_name
github_repo
,其余配置均有合理默认值:
字段默认值描述
bundle_name
{app_name}.app
.app包的文件名
dmg_name
{app_name}.dmg
输出的DMG文件名
git_branch
main
推送appcast的分支
min_system_version
14.0
Sparkle要求的最低macOS版本
appcast_file
appcast.xml
仓库根目录下的appcast文件名
derived_data_prefixes
["{app_name}-"]
用于搜索
sign_update
的DerivedData前缀
  1. 在项目目录中运行CLI:
bash
go run github.com/fayazara/macos-app-skills/release/cli@latest
或者克隆此仓库后本地运行:
bash
go run ./release/cli
该CLI是交互式的——它会提示输入发布说明,并在执行前请求确认。必须在用户可交互的终端中运行。

What the CLI Does

CLI的功能

  1. Finds
    release.json
    by walking up from the current directory
  2. Checks that
    create-dmg
    ,
    gh
    ,
    git
    , and Sparkle's
    sign_update
    are available
  3. Validates the exported app in
    ~/Downloads/
    (reads version, build, Sparkle keys from Info.plist)
  4. Warns if the build number already exists in the appcast
  5. Collects release notes interactively (one bullet per line, empty line to finish)
  6. Shows a release summary and asks for confirmation
  7. Creates the DMG via
    create-dmg
  8. Signs the DMG with Sparkle's
    sign_update
    (EdDSA)
  9. Updates
    appcast.xml
    with the new release entry
  10. Commits and pushes the appcast
  11. Creates a GitHub release with the DMG attached
  1. 从当前目录向上查找
    release.json
  2. 检查
    create-dmg
    gh
    git
    和Sparkle的
    sign_update
    是否可用
  3. 验证
    ~/Downloads/
    中导出的应用(从Info.plist读取版本、构建号、Sparkle密钥)
  4. 如果构建号已存在于appcast中则发出警告
  5. 交互式收集发布说明(每行一个要点,空行结束输入)
  6. 显示发布摘要并请求确认
  7. 通过
    create-dmg
    制作DMG
  8. 使用Sparkle的
    sign_update
    (EdDSA)对DMG签名
  9. 用新的发布条目更新
    appcast.xml
  10. 提交并推送appcast
  11. 创建附带DMG的GitHub Release

Common Issues

常见问题

ProblemSolution
sign_update
not found
Build the project in Xcode first so DerivedData has the Sparkle artifacts
gh
auth failure
Run
gh auth login
Duplicate build number in appcastBump
CURRENT_PROJECT_VERSION
before archiving
Notarization failsCheck signing identity, entitlements, and hardened runtime settings
DMG is too largeCheck for debug symbols or unnecessary frameworks in the export
App won't updateVerify
SUFeedURL
points to the raw appcast URL, not the GitHub page URL
问题解决方案
找不到
sign_update
先在Xcode中构建项目,确保DerivedData中有Sparkle构建产物
gh
认证失败
运行
gh auth login
appcast中存在重复构建号在归档前提升
CURRENT_PROJECT_VERSION
公证失败检查签名身份、entitlements和hardened runtime设置
DMG体积过大检查导出的内容中是否包含调试符号或不必要的框架
应用无法更新确认
SUFeedURL
指向appcast的原始文件URL,而非GitHub页面URL

Appcast Hosting

Appcast托管

The simplest hosting: commit
appcast.xml
to your GitHub repo and use the raw URL:
https://raw.githubusercontent.com/OWNER/REPO/main/appcast.xml
This must match
SUFeedURL
in your app's Info.plist.
最简单的托管方式:将
appcast.xml
提交到GitHub仓库,使用原始文件URL:
https://raw.githubusercontent.com/OWNER/REPO/main/appcast.xml
该URL必须与应用Info.plist中的
SUFeedURL
一致。