Skip to content

Commit c15925d

Browse files
committed
Add App Store release pipeline
Separate build pipeline for Mac App Store distribution: - archive-appstore: builds with Apple Distribution certificate - upload-appstore: validates package before uploading - validate-appstore: standalone validation check - release-appstore: full pipeline (archive → upload) App Store builds go to build/<version>-appstore/ to avoid conflicts with Developer ID builds used for GitHub releases. Moved detailed release docs to RELEASE.md to keep README focused.
1 parent 0565e5e commit c15925d

5 files changed

Lines changed: 426 additions & 70 deletions

File tree

Justfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,34 @@ log id:
9292
# List available builds
9393
builds:
9494
@ls -d {{build_dir}}/v* {{build_dir}}/dev 2>/dev/null | xargs -I{} basename {} | sort -V || echo "(none)"
95+
96+
# =============================================================================
97+
# App Store (requires Apple Distribution certificate)
98+
# =============================================================================
99+
100+
# Build and sign for App Store (optionally specify a tag)
101+
archive-appstore tag="":
102+
./scripts/archive-appstore.sh {{tag}}
103+
104+
# Upload to App Store Connect
105+
upload-appstore version="":
106+
./scripts/upload-appstore.sh {{version}}
107+
108+
# Validate App Store package (without uploading)
109+
validate-appstore version="":
110+
#!/usr/bin/env bash
111+
set -e
112+
ver="${1:-}"
113+
[[ -z "$ver" && -f "{{build_dir}}/.current_version_appstore" ]] && ver=$(cat "{{build_dir}}/.current_version_appstore")
114+
[[ -z "$ver" ]] && { echo "No version specified"; exit 1; }
115+
pkg=$(find "{{build_dir}}/$ver/export" -name "*.pkg" -type f 2>/dev/null | head -1)
116+
[[ -z "$pkg" ]] && { echo "No .pkg found for $ver"; exit 1; }
117+
echo "Validating: $pkg"
118+
xcrun altool --validate-app -f "$pkg" --type macos --apple-id "$APPLE_ID" --password "$APPLE_APP_PASSWORD"
119+
120+
# Full App Store pipeline: archive, validate, upload
121+
release-appstore tag="":
122+
#!/usr/bin/env bash
123+
set -e
124+
just archive-appstore {{tag}}
125+
just upload-appstore

README.md

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -99,76 +99,9 @@ just xcode # Open project in Xcode
9999

100100
## Release Process
101101

102-
Creating a signed and notarized release requires Apple Developer credentials.
103-
104-
### Prerequisites
105-
106-
1. **Apple Developer Account** with a Developer ID certificate
107-
108-
2. **Create `.env`** in the project root:
109-
110-
```bash
111-
cp .env.example .env
112-
```
113-
114-
Then edit `.env` with your credentials:
115-
116-
| Variable | Description |
117-
| -------------------- | --------------------------------------------------------------------------- |
118-
| `APPLE_ID` | Your Apple ID email |
119-
| `APPLE_TEAM_ID` | 10-character Team ID from [developer.apple.com/account](https://developer.apple.com/account) → Membership |
120-
| `APPLE_APP_PASSWORD` | App-specific password from [appleid.apple.com](https://appleid.apple.com/account/manage) → Sign-In and Security → App-Specific Passwords |
121-
122-
3. **Configure Xcode signing** with your Developer ID certificate
123-
124-
### Release Commands
125-
126-
**Full automated release** (archive → submit → wait → staple):
127-
128-
```bash
129-
just release v0.0.5
130-
```
131-
132-
**Step-by-step release** (useful when notarization takes time):
133-
134-
```bash
135-
# 1. Build and sign
136-
just archive v0.0.5
137-
138-
# 2. Submit for notarization
139-
just submit
140-
141-
# 3. Check status (optional)
142-
just status
143-
144-
# 4. Wait for completion (can take minutes to hours)
145-
just wait
146-
147-
# 5. Staple ticket and create final zip
148-
just staple
149-
```
150-
151-
**Other commands:**
152-
153-
```bash
154-
just history # Show notarization history
155-
just log <id> # Get notarization log for a submission
156-
just builds # List available builds
157-
```
158-
159-
### Build Output
160-
161-
Builds are organized by version in `build/<version>/`:
162-
163-
```
164-
build/
165-
├── v0.0.4/
166-
│ ├── archive/CF Cache Status.xcarchive
167-
│ ├── export/CF Cache Status.app
168-
│ ├── CacheStatus-v0.0.4.zip # Final notarized release
169-
│ └── .submission_id # Notarization tracking
170-
└── .current_version # Tracks active build
171-
```
102+
See [RELEASE.md](RELEASE.md) for the full release guide covering:
103+
- Direct distribution (GitHub) with notarization
104+
- Mac App Store submission
172105

173106
## Project Structure
174107

RELEASE.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Release Guide
2+
3+
This document covers building and releasing CF Cache Status for distribution.
4+
5+
## Prerequisites
6+
7+
1. **Apple Developer Account** ($99/year) with:
8+
- Developer ID Application certificate (for direct distribution)
9+
- Apple Distribution certificate (for App Store)
10+
11+
2. **Environment variables** in `.env`:
12+
13+
```bash
14+
cp .env.example .env
15+
```
16+
17+
| Variable | Description |
18+
| -------------------- | --------------------------------------------------------------------------- |
19+
| `APPLE_ID` | Your Apple ID email |
20+
| `APPLE_TEAM_ID` | 10-character Team ID from [developer.apple.com](https://developer.apple.com/account) |
21+
| `APPLE_APP_PASSWORD` | App-specific password from [appleid.apple.com](https://appleid.apple.com) |
22+
23+
3. **Xcode** configured with your signing certificates
24+
25+
## Distribution Options
26+
27+
There are two separate release pipelines:
28+
29+
| Channel | Certificate | Output | Use Case |
30+
| -------------- | ------------------------ | ----------------- | --------------------------- |
31+
| **Direct** | Developer ID Application | Notarized `.zip` | GitHub releases, website |
32+
| **App Store** | Apple Distribution | `.pkg` upload | Mac App Store |
33+
34+
Both can be built for the same version — they're stored in separate directories.
35+
36+
---
37+
38+
## Direct Distribution (GitHub)
39+
40+
For distributing outside the App Store. Requires notarization for Gatekeeper.
41+
42+
### Full Pipeline
43+
44+
```bash
45+
just release v0.0.6
46+
```
47+
48+
This runs: archive → submit → wait → staple
49+
50+
### Step-by-Step
51+
52+
```bash
53+
# 1. Build and sign with Developer ID
54+
just archive v0.0.6
55+
56+
# 2. Submit for notarization
57+
just submit
58+
59+
# 3. Check status (optional, non-blocking)
60+
just status
61+
62+
# 4. Wait for Apple to process (can take minutes to hours)
63+
just wait
64+
65+
# 5. Staple notarization ticket and create final zip
66+
just staple
67+
```
68+
69+
### Output
70+
71+
```
72+
build/
73+
└── v0.0.6/
74+
├── CacheStatus.xcarchive # Xcode archive
75+
├── CF Cache Status.app # Signed app
76+
├── CF.Cache.Status.v0.0.6.zip # Final notarized release
77+
└── .submission_id # Notarization tracking
78+
```
79+
80+
### Utility Commands
81+
82+
```bash
83+
just history # Show notarization history
84+
just log <id> # Get notarization log for a submission
85+
just builds # List available builds
86+
```
87+
88+
---
89+
90+
## App Store Distribution
91+
92+
For publishing on the Mac App Store.
93+
94+
### Full Pipeline
95+
96+
```bash
97+
just release-appstore v0.0.6
98+
```
99+
100+
This runs: archive-appstore → upload-appstore (with validation)
101+
102+
### Step-by-Step
103+
104+
```bash
105+
# 1. Build and sign with Apple Distribution
106+
just archive-appstore v0.0.6
107+
108+
# 2. Validate package (optional, upload does this automatically)
109+
just validate-appstore
110+
111+
# 3. Upload to App Store Connect
112+
just upload-appstore
113+
```
114+
115+
### Output
116+
117+
```
118+
build/
119+
└── v0.0.6-appstore/
120+
├── CacheStatus.xcarchive # Xcode archive
121+
├── ExportOptions.plist # Export configuration
122+
└── export/
123+
└── CF Cache Status.pkg # App Store package
124+
```
125+
126+
### App Store Connect API (Optional)
127+
128+
For automated uploads, you can use API keys instead of Apple ID:
129+
130+
| Variable | Description |
131+
| ------------------------- | ------------------------------------ |
132+
| `APPSTORE_API_KEY_ID` | Key ID from App Store Connect |
133+
| `APPSTORE_API_ISSUER_ID` | Issuer ID from App Store Connect |
134+
| `APPSTORE_API_KEY_PATH` | Path to `.p8` private key file |
135+
136+
Generate keys at: App Store Connect → Users and Access → Keys
137+
138+
---
139+
140+
## Building Both
141+
142+
You can build both versions for the same release:
143+
144+
```bash
145+
# Direct distribution (for GitHub)
146+
just release v0.0.6
147+
148+
# App Store (for Mac App Store)
149+
just release-appstore v0.0.6
150+
```
151+
152+
They don't conflict — outputs go to separate directories:
153+
- `build/v0.0.6/` — Developer ID build
154+
- `build/v0.0.6-appstore/` — App Store build
155+
156+
---
157+
158+
## Troubleshooting
159+
160+
### Notarization Stuck "In Progress"
161+
162+
Apple's notarization service can sometimes take hours. Check status with:
163+
164+
```bash
165+
just status
166+
just history
167+
```
168+
169+
### Notarization Failed
170+
171+
Get the detailed log:
172+
173+
```bash
174+
just log <submission-id>
175+
```
176+
177+
Common issues:
178+
- Missing entitlements
179+
- Hardened runtime not enabled
180+
- Unsigned frameworks/binaries
181+
182+
### App Store Validation Failed
183+
184+
The upload script validates before uploading. Common issues:
185+
- Missing provisioning profile
186+
- Bundle ID mismatch
187+
- Version/build number conflicts
188+
189+
### Certificate Issues
190+
191+
Verify your certificates in Keychain Access:
192+
- **Developer ID Application** — for direct distribution
193+
- **Apple Distribution** — for App Store
194+
195+
Both require the private key to be present.
196+
197+
---
198+
199+
## Version Checklist
200+
201+
Before releasing a new version:
202+
203+
1. [ ] Update version in Xcode (both targets)
204+
2. [ ] Update `CHANGELOG.md`
205+
3. [ ] Create git tag: `git tag v0.0.X`
206+
4. [ ] Push tag: `git push origin v0.0.X`
207+
5. [ ] Build releases:
208+
- [ ] `just release v0.0.X` (direct)
209+
- [ ] `just release-appstore v0.0.X` (App Store)
210+
6. [ ] Create GitHub release with the `.zip` file
211+
7. [ ] Submit App Store version in App Store Connect

0 commit comments

Comments
 (0)