Skip to content

Commit b4ddcb0

Browse files
committed
docs: add comprehensive release guide
1 parent 3f62008 commit b4ddcb0

File tree

1 file changed

+349
-0
lines changed

1 file changed

+349
-0
lines changed

RELEASE.md

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
# MyMacCleaner Release Guide
2+
3+
This guide explains how to create new releases for MyMacCleaner with proper versioning, code signing, notarization, and auto-update support via Sparkle.
4+
5+
## Prerequisites
6+
7+
### 1. Environment Setup
8+
9+
Create a `.env` file in the project root (copy from `.env.example`):
10+
11+
```bash
12+
cp .env.example .env
13+
```
14+
15+
Required variables in `.env`:
16+
17+
```bash
18+
# Apple Developer credentials
19+
APPLE_TEAM_ID=YOUR_TEAM_ID # e.g., 7K4SKUHU47
20+
MACOS_CERTIFICATE_SHA1=YOUR_CERT_SHA1 # Run: security find-identity -v -p codesigning
21+
22+
# Sparkle EdDSA key for signing updates (base64 encoded)
23+
SPARKLE_PRIVATE_KEY=YOUR_PRIVATE_KEY # Generate with: ./bin/generate_keys
24+
```
25+
26+
### 2. Keychain Profile for Notarization
27+
28+
Set up a notarization profile (one-time setup):
29+
30+
```bash
31+
xcrun notarytool store-credentials "notary-profile" \
32+
--apple-id "your@email.com" \
33+
--team-id "YOUR_TEAM_ID" \
34+
--password "app-specific-password"
35+
```
36+
37+
Create an app-specific password at: https://appleid.apple.com/account/manage
38+
39+
### 3. GitHub CLI Authentication
40+
41+
```bash
42+
gh auth login
43+
```
44+
45+
### 4. Sparkle EdDSA Keys
46+
47+
If you don't have Sparkle keys yet:
48+
49+
```bash
50+
# Download Sparkle tools
51+
curl -L -o /tmp/sparkle.tar.xz https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz
52+
mkdir -p ./bin && tar -xf /tmp/sparkle.tar.xz -C ./bin
53+
54+
# Generate new keys
55+
./bin/generate_keys
56+
```
57+
58+
Save the private key to your `.env` file and add the public key to your app's `Info.plist` as `SUPublicEDKey`.
59+
60+
---
61+
62+
## Release Process
63+
64+
### Quick Release (Recommended)
65+
66+
For a standard release, just run:
67+
68+
```bash
69+
./scripts/release.sh <version> "<changelog>"
70+
```
71+
72+
**Examples:**
73+
74+
```bash
75+
# Patch release (bug fixes)
76+
./scripts/release.sh 0.1.2 "Fixed crash on startup"
77+
78+
# Minor release (new features)
79+
./scripts/release.sh 0.2.0 "Added new disk analysis feature"
80+
81+
# Major release
82+
./scripts/release.sh 1.0.0 "First stable release"
83+
```
84+
85+
The script automatically:
86+
1. Increments the build number
87+
2. Updates version in Xcode project
88+
3. Builds and archives the app
89+
4. Signs with Developer ID certificate
90+
5. Notarizes with Apple
91+
6. Creates DMG and ZIP packages
92+
7. Signs ZIP for Sparkle auto-updates
93+
8. Updates `appcast.xml` for Sparkle
94+
9. Updates `website/public/data/releases.json`
95+
10. Creates GitHub release with assets
96+
11. Commits and pushes all changes
97+
98+
---
99+
100+
## Manual Release Steps (If Needed)
101+
102+
If you need to do a manual release or the script fails partway through:
103+
104+
### Step 1: Update Version Numbers
105+
106+
Edit `MyMacCleaner.xcodeproj/project.pbxproj`:
107+
- `MARKETING_VERSION` = display version (e.g., "0.1.2")
108+
- `CURRENT_PROJECT_VERSION` = build number (integer, e.g., 3)
109+
110+
Or use sed:
111+
```bash
112+
sed -i '' 's/MARKETING_VERSION = [^;]*;/MARKETING_VERSION = 0.1.2;/g' MyMacCleaner.xcodeproj/project.pbxproj
113+
sed -i '' 's/CURRENT_PROJECT_VERSION = [^;]*;/CURRENT_PROJECT_VERSION = 3;/g' MyMacCleaner.xcodeproj/project.pbxproj
114+
```
115+
116+
### Step 2: Build and Archive
117+
118+
```bash
119+
xcodebuild archive \
120+
-project MyMacCleaner.xcodeproj \
121+
-scheme MyMacCleaner \
122+
-archivePath build/MyMacCleaner.xcarchive \
123+
-configuration Release \
124+
CODE_SIGN_IDENTITY="Developer ID Application" \
125+
DEVELOPMENT_TEAM="YOUR_TEAM_ID"
126+
```
127+
128+
### Step 3: Export Signed App
129+
130+
```bash
131+
xcodebuild -exportArchive \
132+
-archivePath build/MyMacCleaner.xcarchive \
133+
-exportPath build/export \
134+
-exportOptionsPlist ExportOptions.plist
135+
```
136+
137+
### Step 4: Notarize
138+
139+
```bash
140+
# Create ZIP for notarization
141+
ditto -c -k --keepParent build/export/MyMacCleaner.app build/notarization.zip
142+
143+
# Submit for notarization
144+
xcrun notarytool submit build/notarization.zip \
145+
--keychain-profile "notary-profile" \
146+
--wait
147+
148+
# Staple the ticket
149+
xcrun stapler staple build/export/MyMacCleaner.app
150+
```
151+
152+
### Step 5: Create DMG
153+
154+
```bash
155+
create-dmg \
156+
--volname "MyMacCleaner" \
157+
--window-size 600 400 \
158+
--icon-size 100 \
159+
--icon "MyMacCleaner.app" 150 200 \
160+
--app-drop-link 450 200 \
161+
build/MyMacCleaner-v0.1.2.dmg \
162+
build/export/MyMacCleaner.app
163+
164+
# Sign and notarize DMG
165+
codesign --force --sign "Developer ID Application: Your Name (TEAM_ID)" build/MyMacCleaner-v0.1.2.dmg
166+
xcrun notarytool submit build/MyMacCleaner-v0.1.2.dmg --keychain-profile "notary-profile" --wait
167+
xcrun stapler staple build/MyMacCleaner-v0.1.2.dmg
168+
```
169+
170+
### Step 6: Create Sparkle ZIP
171+
172+
```bash
173+
cd build/export
174+
zip -r ../MyMacCleaner-v0.1.2.zip MyMacCleaner.app
175+
cd ../..
176+
177+
# Sign with Sparkle EdDSA key
178+
./bin/sign_update build/MyMacCleaner-v0.1.2.zip
179+
```
180+
181+
### Step 7: Update appcast.xml
182+
183+
Add new item at the TOP of the channel (most recent first):
184+
185+
```xml
186+
<item>
187+
<title>Version 0.1.2</title>
188+
<pubDate>Fri, 24 Jan 2026 10:00:00 +0100</pubDate>
189+
<sparkle:version>3</sparkle:version>
190+
<sparkle:shortVersionString>0.1.2</sparkle:shortVersionString>
191+
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
192+
<description><![CDATA[
193+
<h2>What's New in Version 0.1.2</h2>
194+
<ul>
195+
<li>Your changelog here</li>
196+
</ul>
197+
]]></description>
198+
<enclosure
199+
url="https://github.com/Prot10/MyMacCleaner/releases/download/v0.1.2/MyMacCleaner-v0.1.2.zip"
200+
sparkle:edSignature="YOUR_SIGNATURE_HERE"
201+
length="FILE_SIZE_IN_BYTES"
202+
type="application/octet-stream"/>
203+
</item>
204+
```
205+
206+
### Step 8: Create GitHub Release
207+
208+
```bash
209+
gh release create v0.1.2 \
210+
--title "MyMacCleaner v0.1.2" \
211+
--notes "Your changelog here" \
212+
build/MyMacCleaner-v0.1.2.dmg \
213+
build/MyMacCleaner-v0.1.2.zip
214+
```
215+
216+
### Step 9: Commit and Push
217+
218+
```bash
219+
git add -A
220+
git commit -m "release: v0.1.2"
221+
git push
222+
```
223+
224+
---
225+
226+
## Version Numbering
227+
228+
Follow semantic versioning (MAJOR.MINOR.PATCH):
229+
230+
| Type | When to Use | Example |
231+
|------|-------------|---------|
232+
| PATCH | Bug fixes, minor improvements | 0.1.1 → 0.1.2 |
233+
| MINOR | New features, backward compatible | 0.1.2 → 0.2.0 |
234+
| MAJOR | Breaking changes, major rewrites | 0.2.0 → 1.0.0 |
235+
236+
**Build numbers** are always incremented (never reset) and are used internally for update comparison.
237+
238+
---
239+
240+
## How Auto-Updates Work
241+
242+
1. **On app launch**: `UpdateManager` fetches `appcast.xml` from GitHub
243+
2. **Version comparison**: Compares `sparkle:version` (build number) with current app's `CFBundleVersion`
244+
3. **If update available**: Sets `updateAvailable = true`, button appears in toolbar
245+
4. **User clicks button**: Shows update sheet with version details
246+
5. **User clicks "Download & Install"**: Sparkle handles download, verification, and installation
247+
248+
### Key Files for Auto-Updates
249+
250+
| File | Purpose |
251+
|------|---------|
252+
| `appcast.xml` | Sparkle feed with version info and signatures |
253+
| `Info.plist``SUFeedURL` | URL to appcast.xml |
254+
| `Info.plist``SUPublicEDKey` | Public key for signature verification |
255+
| `UpdateManager.swift` | Fetches appcast, compares versions |
256+
| `UpdateAvailableButton.swift` | UI for update notification |
257+
258+
---
259+
260+
## Troubleshooting
261+
262+
### Update button doesn't appear
263+
264+
1. **Check build numbers**: The appcast `sparkle:version` must be greater than the app's `CFBundleVersion`
265+
2. **Check appcast URL**: Verify `SUFeedURL` in Info.plist points to raw GitHub URL
266+
3. **Check Console logs**: Filter for `[UpdateManager]` to see fetch results
267+
4. **Clear cache**: The app uses cache-busting, but try quitting and restarting
268+
269+
### Notarization fails
270+
271+
1. **Check credentials**: Run `xcrun notarytool history --keychain-profile "notary-profile"`
272+
2. **Check entitlements**: Ensure hardened runtime is enabled
273+
3. **Check signing**: Run `codesign -dvvv build/export/MyMacCleaner.app`
274+
275+
### Sparkle signature error
276+
277+
1. **Check key match**: Public key in Info.plist must match private key used for signing
278+
2. **Re-sign the ZIP**: `./bin/sign_update build/MyMacCleaner-vX.X.X.zip`
279+
3. **Update appcast**: Ensure `sparkle:edSignature` matches the new signature
280+
281+
### GitHub release issues
282+
283+
1. **Check authentication**: Run `gh auth status`
284+
2. **Check repository**: Ensure you're in the correct repo
285+
3. **Delete and retry**: `gh release delete vX.X.X --yes` then re-run script
286+
287+
---
288+
289+
## Clean Slate Release
290+
291+
If you need to start fresh (delete all releases and re-release):
292+
293+
```bash
294+
# Delete all GitHub releases
295+
gh release list | awk '{print $3}' | xargs -I {} gh release delete {} --yes
296+
297+
# Delete all tags
298+
git tag -l | xargs -I {} git tag -d {}
299+
git tag -l | xargs -I {} git push origin --delete {}
300+
301+
# Clear appcast.xml (keep only channel header)
302+
cat > appcast.xml << 'EOF'
303+
<?xml version="1.0" encoding="utf-8"?>
304+
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
305+
<channel>
306+
<title>MyMacCleaner Updates</title>
307+
<link>https://github.com/Prot10/MyMacCleaner</link>
308+
<description>Most recent changes with links to updates.</description>
309+
<language>en</language>
310+
311+
</channel>
312+
</rss>
313+
EOF
314+
315+
# Set version to X.Y.Z with build 0 (script will increment to 1)
316+
sed -i '' 's/MARKETING_VERSION = [^;]*;/MARKETING_VERSION = 0.1.0;/g' MyMacCleaner.xcodeproj/project.pbxproj
317+
sed -i '' 's/CURRENT_PROJECT_VERSION = [^;]*;/CURRENT_PROJECT_VERSION = 0;/g' MyMacCleaner.xcodeproj/project.pbxproj
318+
319+
# Commit and push
320+
git add -A && git commit -m "chore: prepare for fresh release" && git push
321+
322+
# Release first version
323+
./scripts/release.sh 0.1.0 "Initial release"
324+
325+
# Release second version (for testing updates)
326+
./scripts/release.sh 0.1.1 "Update notification improvements"
327+
```
328+
329+
---
330+
331+
## Quick Reference
332+
333+
```bash
334+
# Standard release
335+
./scripts/release.sh 0.1.2 "Bug fixes and improvements"
336+
337+
# Check current version
338+
grep -m1 "MARKETING_VERSION" MyMacCleaner.xcodeproj/project.pbxproj
339+
grep -m1 "CURRENT_PROJECT_VERSION" MyMacCleaner.xcodeproj/project.pbxproj
340+
341+
# List GitHub releases
342+
gh release list
343+
344+
# View appcast
345+
cat appcast.xml
346+
347+
# Check notarization history
348+
xcrun notarytool history --keychain-profile "notary-profile"
349+
```

0 commit comments

Comments
 (0)