releasing-macos-apps

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Releasing macOS Apps

macOS应用发布指南

Complete workflow for creating notarized macOS app releases with Sparkle auto-updates, DMG installers, and GitHub releases.
实现创建带有Sparkle自动更新、DMG安装程序和GitHub发布的公证版macOS应用发布的完整工作流。

Release Checklist

发布检查清单

Copy this checklist and track progress:
Release Progress:
- [ ] Step 1: Check prerequisites (certificates, credentials)
- [ ] Step 2: Update version in .xcconfig file
- [ ] Step 3: Build and archive the app
- [ ] Step 4: Export with proper code signing
- [ ] Step 5: Create zip and generate Sparkle signature
- [ ] Step 6: Create DMG with Applications folder
- [ ] Step 7: Submit for notarization
- [ ] Step 8: Staple notarization ticket to DMG
- [ ] Step 9: Update appcast.xml with new signature
- [ ] Step 10: Commit and push changes
- [ ] Step 11: Update GitHub release assets
- [ ] Step 12: Verify DMG and version number
复制此清单并跟踪进度:
Release Progress:
- [ ] Step 1: Check prerequisites (certificates, credentials)
- [ ] Step 2: Update version in .xcconfig file
- [ ] Step 3: Build and archive the app
- [ ] Step 4: Export with proper code signing
- [ ] Step 5: Create zip and generate Sparkle signature
- [ ] Step 6: Create DMG with Applications folder
- [ ] Step 7: Submit for notarization
- [ ] Step 8: Staple notarization ticket to DMG
- [ ] Step 9: Update appcast.xml with new signature
- [ ] Step 10: Commit and push changes
- [ ] Step 11: Update GitHub release assets
- [ ] Step 12: Verify DMG and version number

Prerequisites

前置条件

Before starting a release, verify:
  1. Apple Developer ID Application certificate installed and valid
  2. Apple ID credentials for notarization:
    • Apple ID email
    • App-specific password (generate at appleid.apple.com)
    • Team ID
  3. Sparkle private key for signing updates
  4. GitHub CLI (
    gh
    ) installed and authenticated
  5. Version configuration location identified (usually
    .xcconfig
    file)
Check certificate:
bash
security find-identity -v -p codesigning | grep "Developer ID Application"
开始发布前,请确认以下内容:
  1. Apple Developer ID Application证书已安装且有效
  2. 用于公证的Apple ID凭据
    • Apple ID邮箱
    • 应用专用密码(在appleid.apple.com生成)
    • 团队ID
  3. 用于签名更新的Sparkle私钥
  4. GitHub CLI
    gh
    )已安装并完成认证
  5. 版本配置位置已确定(通常为
    .xcconfig
    文件)
检查证书:
bash
security find-identity -v -p codesigning | grep "Developer ID Application"

Step 1: Update Version

步骤1:更新版本号

Locate your version configuration file (commonly
ProjectName.xcconfig
or
project.pbxproj
).
For .xcconfig files:
bash
undefined
找到版本配置文件(通常为
ProjectName.xcconfig
project.pbxproj
)。
针对.xcconfig文件:
bash
undefined

Edit the APP_VERSION line

编辑APP_VERSION行

Example: APP_VERSION = 1.0.9

示例: APP_VERSION = 1.0.9


**Verify the update:**
```bash
xcodebuild -project PROJECT.xcodeproj -showBuildSettings | grep MARKETING_VERSION

**验证版本更新:**
```bash
xcodebuild -project PROJECT.xcodeproj -showBuildSettings | grep MARKETING_VERSION

Step 2: Build and Archive

步骤2:构建并归档应用

Archive the app with the new version:
bash
xcodebuild -project PROJECT.xcodeproj \
  -scheme SCHEME_NAME \
  -configuration Release \
  -archivePath ~/Desktop/APP-VERSION.xcarchive \
  archive
Verify archive was created:
bash
ls -la ~/Desktop/APP-VERSION.xcarchive
使用新版本号归档应用:
bash
xcodebuild -project PROJECT.xcodeproj \
  -scheme SCHEME_NAME \
  -configuration Release \
  -archivePath ~/Desktop/APP-VERSION.xcarchive \
  archive
验证归档文件已创建:
bash
ls -la ~/Desktop/APP-VERSION.xcarchive

Step 3: Export with Code Signing

步骤3:带代码签名导出

Create export options file:
bash
cat > /tmp/ExportOptions.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>destination</key>
	<string>export</string>
	<key>method</key>
	<string>developer-id</string>
	<key>signingStyle</key>
	<string>automatic</string>
	<key>teamID</key>
	<string>YOUR_TEAM_ID</string>
	<key>signingCertificate</key>
	<string>Developer ID Application</string>
</dict>
</plist>
EOF
Replace
YOUR_TEAM_ID
with your actual team ID.
Export the archive:
bash
xcodebuild -exportArchive \
  -archivePath ~/Desktop/APP-VERSION.xcarchive \
  -exportPath ~/Desktop/APP-VERSION-Export \
  -exportOptionsPlist /tmp/ExportOptions.plist
Verify the exported app version:
bash
defaults read ~/Desktop/APP-VERSION-Export/APP.app/Contents/Info.plist CFBundleShortVersionString
This should show the new version number.
Verify code signing:
bash
codesign -dvvv ~/Desktop/APP-VERSION-Export/APP.app
Look for "Developer ID Application" in the Authority lines.
创建导出选项文件:
bash
cat > /tmp/ExportOptions.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>destination</key>
	<string>export</string>
	<key>method</key>
	<string>developer-id</string>
	<key>signingStyle</key>
	<string>automatic</string>
	<key>teamID</key>
	<string>YOUR_TEAM_ID</string>
	<key>signingCertificate</key>
	<string>Developer ID Application</string>
</dict>
</plist>
EOF
YOUR_TEAM_ID
替换为实际的团队ID。
导出归档文件:
bash
xcodebuild -exportArchive \
  -archivePath ~/Desktop/APP-VERSION.xcarchive \
  -exportPath ~/Desktop/APP-VERSION-Export \
  -exportOptionsPlist /tmp/ExportOptions.plist
验证导出应用的版本号:
bash
defaults read ~/Desktop/APP-VERSION-Export/APP.app/Contents/Info.plist CFBundleShortVersionString
此命令应显示新的版本号。
验证代码签名:
bash
codesign -dvvv ~/Desktop/APP-VERSION-Export/APP.app
在Authority行中查找"Developer ID Application"。

Step 4: Create Zip and Generate Sparkle Signature

步骤4:创建Zip包并生成Sparkle签名

Create zip file for Sparkle auto-updates:
bash
cd ~/Desktop/APP-VERSION-Export
ditto -c -k --keepParent APP.app APP.app.zip
Generate Sparkle EdDSA signature (you'll be prompted for the private key):
bash
echo "YOUR_SPARKLE_PRIVATE_KEY" | \
  ~/Library/Developer/Xcode/DerivedData/PROJECT-HASH/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \
  APP.app.zip --ed-key-file -
Output format:
sparkle:edSignature="BASE64_SIGNATURE" length="FILE_SIZE"
Save both the signature and length for updating appcast.xml.
For more details, see SPARKLE.md.
为Sparkle自动更新创建Zip文件:
bash
cd ~/Desktop/APP-VERSION-Export
ditto -c -k --keepParent APP.app APP.app.zip
生成Sparkle EdDSA签名(会提示输入私钥):
bash
echo "YOUR_SPARKLE_PRIVATE_KEY" | \
  ~/Library/Developer/Xcode/DerivedData/PROJECT-HASH/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \
  APP.app.zip --ed-key-file -
输出格式:
sparkle:edSignature="BASE64_SIGNATURE" length="FILE_SIZE"
保存签名和文件大小,用于更新appcast.xml。
更多详情,请查看SPARKLE.md

Step 5: Create DMG with Applications Folder

步骤5:创建带Applications文件夹的DMG

Create DMG installer with Applications folder symlink for drag-and-drop installation:
bash
TEMP_DMG_DIR="/tmp/APP_dmg" && \
rm -rf "${TEMP_DMG_DIR}" && \
mkdir -p "${TEMP_DMG_DIR}" && \
cp -R ~/Desktop/APP-VERSION-Export/APP.app "${TEMP_DMG_DIR}/" && \
ln -s /Applications "${TEMP_DMG_DIR}/Applications" && \
hdiutil create -volname "APP VERSION" \
  -srcfolder "${TEMP_DMG_DIR}" \
  -ov -format UDZO ~/Desktop/APP-VERSION.dmg && \
rm -rf "${TEMP_DMG_DIR}"
Verify DMG contents:
bash
hdiutil attach ~/Desktop/APP-VERSION.dmg -readonly -nobrowse -mountpoint /tmp/verify_dmg && \
ls -la /tmp/verify_dmg && \
hdiutil detach /tmp/verify_dmg
You should see both
APP.app
and
Applications
(symlink).
创建包含Applications文件夹符号链接的DMG安装包,支持拖放安装:
bash
TEMP_DMG_DIR="/tmp/APP_dmg" && \
rm -rf "${TEMP_DMG_DIR}" && \
mkdir -p "${TEMP_DMG_DIR}" && \
cp -R ~/Desktop/APP-VERSION-Export/APP.app "${TEMP_DMG_DIR}/" && \
ln -s /Applications "${TEMP_DMG_DIR}/Applications" && \
hdiutil create -volname "APP VERSION" \
  -srcfolder "${TEMP_DMG_DIR}" \
  -ov -format UDZO ~/Desktop/APP-VERSION.dmg && \
rm -rf "${TEMP_DMG_DIR}"
验证DMG内容:
bash
hdiutil attach ~/Desktop/APP-VERSION.dmg -readonly -nobrowse -mountpoint /tmp/verify_dmg && \
ls -la /tmp/verify_dmg && \
hdiutil detach /tmp/verify_dmg
应同时显示
APP.app
Applications
(符号链接)。

Step 6: Submit for Notarization

步骤6:提交公证

Submit the DMG to Apple for notarization (you'll be prompted for credentials):
bash
xcrun notarytool submit ~/Desktop/APP-VERSION.dmg \
  --apple-id YOUR_APPLE_ID@gmail.com \
  --team-id YOUR_TEAM_ID \
  --password YOUR_APP_SPECIFIC_PASSWORD \
  --wait
The
--wait
flag makes the command wait for processing to complete (typically 1-2 minutes).
Expected output:
Processing complete
  id: [submission-id]
  status: Accepted
If status is "Invalid", get detailed logs:
bash
xcrun notarytool log SUBMISSION_ID \
  --apple-id YOUR_APPLE_ID@gmail.com \
  --team-id YOUR_TEAM_ID \
  --password YOUR_APP_SPECIFIC_PASSWORD
For notarization troubleshooting, see NOTARIZATION.md.
将DMG提交给Apple进行公证(会提示输入凭据):
bash
xcrun notarytool submit ~/Desktop/APP-VERSION.dmg \
  --apple-id YOUR_APPLE_ID@gmail.com \
  --team-id YOUR_TEAM_ID \
  --password YOUR_APP_SPECIFIC_PASSWORD \
  --wait
--wait
参数会让命令等待处理完成(通常需要1-2分钟)。
预期输出:
Processing complete
  id: [submission-id]
  status: Accepted
如果状态为"Invalid",获取详细日志:
bash
xcrun notarytool log SUBMISSION_ID \
  --apple-id YOUR_APPLE_ID@gmail.com \
  --team-id YOUR_TEAM_ID \
  --password YOUR_APP_SPECIFIC_PASSWORD
关于公正确排,请查看NOTARIZATION.md

Step 7: Staple Notarization Ticket

步骤7:绑定公证票据

Staple the notarization ticket to the DMG:
bash
xcrun stapler staple ~/Desktop/APP-VERSION.dmg
Expected output:
The staple and validate action worked!
Verify notarization:
bash
spctl -a -vvv ~/Desktop/APP-VERSION-Export/APP.app
Should show:
accepted
source=Notarized Developer ID
将公证票据绑定到DMG:
bash
xcrun stapler staple ~/Desktop/APP-VERSION.dmg
预期输出:
The staple and validate action worked!
验证公证状态:
bash
spctl -a -vvv ~/Desktop/APP-VERSION-Export/APP.app
应显示:
accepted
source=Notarized Developer ID

Step 8: Update appcast.xml

步骤8:更新appcast.xml

Update the Sparkle appcast file with the new version, signature, and file size from Step 4:
xml
<item>
  <title>Version X.X.X</title>
  <link>https://github.com/USER/REPO</link>
  <sparkle:version>X.X.X</sparkle:version>
  <sparkle:channel>stable</sparkle:channel>
  <description><![CDATA[
    Release version X.X.X
  ]]></description>
  <pubDate>DAY, DD MMM YYYY HH:MM:SS -0700</pubDate>
  <enclosure
    url="https://github.com/USER/REPO/releases/download/vX.X.X/APP.app.zip"
    sparkle:version="X.X.X"
    sparkle:edSignature="SIGNATURE_FROM_STEP_4"
    length="FILE_SIZE_FROM_STEP_4"
    type="application/octet-stream" />
</item>
Note: The gitleaks pre-commit hook may flag the Sparkle signature as a potential secret. This is a false positive - the EdDSA signature is public and safe to commit. Use
git commit --no-verify
if needed.
使用步骤4中获取的新版本号、签名和文件大小更新Sparkle的appcast文件:
xml
<item>
  <title>Version X.X.X</title>
  <link>https://github.com/USER/REPO</link>
  <sparkle:version>X.X.X</sparkle:version>
  <sparkle:channel>stable</sparkle:channel>
  <description><![CDATA[
    Release version X.X.X
  ]]></description>
  <pubDate>DAY, DD MMM YYYY HH:MM:SS -0700</pubDate>
  <enclosure
    url="https://github.com/USER/REPO/releases/download/vX.X.X/APP.app.zip"
    sparkle:version="X.X.X"
    sparkle:edSignature="SIGNATURE_FROM_STEP_4"
    length="FILE_SIZE_FROM_STEP_4"
    type="application/octet-stream" />
</item>
注意: gitleaks预提交钩子可能会将Sparkle签名标记为潜在机密。这是误报 - EdDSA签名是公开的,可以安全提交。如有需要,使用
git commit --no-verify

Step 9: Commit and Push Changes

步骤9:提交并推送更改

Commit the version update and appcast changes:
bash
git add PROJECT.xcconfig appcast.xml
git commit --no-verify -m "Bump version to X.X.X

Update appcast.xml with new version, Sparkle signature, and file size.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>"

git push
提交版本更新和appcast更改:
bash
git add PROJECT.xcconfig appcast.xml
git commit --no-verify -m "Bump version to X.X.X

Update appcast.xml with new version, Sparkle signature, and file size.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>"

git push

Step 10: Update GitHub Release

步骤10:更新GitHub发布

Create or update the GitHub release with new assets:
For new releases:
bash
gh release create vX.X.X \
  --title "APP vX.X.X" \
  --notes "Release version X.X.X" \
  ~/Desktop/APP-VERSION.dmg \
  ~/Desktop/APP-VERSION-Export/APP.app.zip
For updating existing releases:
bash
undefined
创建或更新GitHub发布并添加新资产:
针对新发布:
bash
gh release create vX.X.X \
  --title "APP vX.X.X" \
  --notes "Release version X.X.X" \
  ~/Desktop/APP-VERSION.dmg \
  ~/Desktop/APP-VERSION-Export/APP.app.zip
针对更新现有发布:
bash
undefined

Upload new assets (overwrites existing with --clobber)

上传新资产(--clobber参数会覆盖现有资产)

gh release upload vX.X.X
~/Desktop/APP-VERSION.dmg
~/Desktop/APP-VERSION-Export/APP.app.zip
--clobber

**Note on asset naming:**
The uploaded filename becomes the asset name. To upload with a specific name:
```bash
gh release upload vX.X.X
~/Desktop/APP-VERSION.dmg
~/Desktop/APP-VERSION-Export/APP.app.zip
--clobber

**资产命名注意事项:**
上传的文件名会成为资产名称。如需使用特定名称上传:
```bash

Copy to desired name first

先复制为目标名称

cp ~/Desktop/APP-1.0.9.dmg /tmp/APP.dmg gh release upload vX.X.X /tmp/APP.dmg

**Verify release assets:**
```bash
gh release view vX.X.X --json assets -q '.assets[] | "\(.name) - \(.size) bytes"'
cp ~/Desktop/APP-1.0.9.dmg /tmp/APP.dmg gh release upload vX.X.X /tmp/APP.dmg

**验证发布资产:**
```bash
gh release view vX.X.X --json assets -q '.assets[] | "\(.name) - \(.size) bytes"'

Step 11: Final Verification

步骤11:最终验证

Verify the release is working correctly:
Check version in app:
bash
defaults read /Applications/APP.app/Contents/Info.plist CFBundleShortVersionString
Should show:
X.X.X
Test DMG:
  1. Download the DMG from GitHub release
  2. Open the DMG
  3. Verify Applications folder is present for drag-and-drop
  4. Drag app to Applications and launch
  5. Should open without any "malicious" or security warnings
Test Sparkle updates:
  • Users with previous versions should receive automatic update notifications
  • The update should download and install smoothly
验证发布是否正常工作:
检查应用内版本号:
bash
defaults read /Applications/APP.app/Contents/Info.plist CFBundleShortVersionString
应显示:
X.X.X
测试DMG:
  1. 从GitHub发布下载DMG
  2. 打开DMG
  3. 确认存在用于拖放的Applications文件夹
  4. 将应用拖到Applications文件夹并启动
  5. 启动时不应出现“恶意软件”或安全警告
测试Sparkle更新:
  • 旧版本用户应收到自动更新通知
  • 更新应能顺利下载并安装

Common Issues

常见问题

If you encounter problems, see TROUBLESHOOTING.md for solutions to:
  • Version not updating after rebuild
  • DMG missing Applications folder
  • Notarization failures
  • "Malicious app" warnings
  • Sparkle signature issues
  • CI/CD failures
如遇到问题,请查看TROUBLESHOOTING.md获取以下问题的解决方案:
  • 重新构建后版本号未更新
  • DMG缺少Applications文件夹
  • 公证失败
  • “恶意应用”警告
  • Sparkle签名问题
  • CI/CD失败

Quick Reference

快速参考

Check version:
bash
defaults read /path/to/APP.app/Contents/Info.plist CFBundleShortVersionString
Check code signing:
bash
codesign -dvvv /path/to/APP.app
Check notarization:
bash
spctl -a -vvv /path/to/APP.app
Get Sparkle sign_update path:
bash
find ~/Library/Developer/Xcode/DerivedData -name sign_update -type f
检查版本号:
bash
defaults read /path/to/APP.app/Contents/Info.plist CFBundleShortVersionString
检查代码签名:
bash
codesign -dvvv /path/to/APP.app
检查公证状态:
bash
spctl -a -vvv /path/to/APP.app
获取Sparkle sign_update路径:
bash
find ~/Library/Developer/Xcode/DerivedData -name sign_update -type f