DoomCoder ships through two independent pipelines because the Mac app and the iPhone companion live in different Apple distribution channels. This document explains both, including the one-time setup each requires.
Sparkle does not and cannot update an App Store app. If you ever find yourself reaching for
appcast.xmlin the iOS pipeline, stop — that is a Mac-only concept.
| Pipeline | Tag | Workflow | Ships via |
|---|---|---|---|
| macOS | git tag v2.4.0 |
.github/workflows/release.yml |
GitHub Releases + Sparkle appcast |
| iOS | git tag ios-v2.4.0 |
.github/workflows/ios-testflight.yml |
TestFlight → App Store review |
Both are pushed with git push --tags (or as separate single-tag pushes).
The user keeps an "exploratory work is local-only" rule, so push the tag
only when you've explicitly decided to cut the release.
- Bump
CFBundleShortVersionString+CFBundleVersioninDoomCoder/Info.plist.release.ymlalso stamps the tag-derived version into the plist at build time, but committing the bump keeps local dev builds in sync with what shipped. - Update
CHANGELOG.md. git tag v2.4.0 && git push origin v2.4.0.release.yml(runs-on: macos-26) does the rest:- Builds + notarizes the
.appwith the existing Developer ID cert. - Creates a
.dmgand signs the Sparkle update with the EdDSA key. - Publishes the GitHub Release with the DMG attached.
- Updates
appcast.xml. Running Mac clients pick up the update the next time Sparkle polls.
- Builds + notarizes the
Required secrets/vars (already configured):
APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, KEYCHAIN_PASSWORD,
APPLE_TEAM_ID, APPLE_ID, APPLE_APP_PASSWORD, SPARKLE_ED_PRIVATE_KEY.
The iPhone companion is distributed through the App Store. Every TestFlight build automatically becomes available to internal testers; external testers and the public App Store require Apple review (usually < 24 h after the first build of a version is reviewed; subsequent builds with the same marketing version are typically auto-approved).
-
App Store Connect → My Apps → + → New App.
- Platform: iOS
- Name:
DoomCoder Companion - Bundle ID:
com.doomcoder.app.companion - SKU:
doomcoder-companion
-
Users and Access → Integrations → App Store Connect API → Generate API Key.
- Role:
App Manager(Admin works but is broader than needed). - Download the
AuthKey_XXXXXXXXXX.p8file. You cannot re-download it.
- Role:
-
Certificates, Identifiers & Profiles:
- Create an
Apple Distributioncertificate (or reuse an existing one). - Export the cert + private key as a
.p12from Keychain Access.
- Create an
-
GitHub repo → Settings → Secrets and variables → Actions, add:
Name Source APP_STORE_CONNECT_KEY_ID10-char ID from step 2 APP_STORE_CONNECT_ISSUER_IDIssuer UUID from step 2 APP_STORE_CONNECT_PRIVATE_KEYbase64 -i AuthKey_XXX.p8of step 2 fileIOS_DISTRIBUTION_CERTIFICATEbase64 -i Distribution.p12of step 3 fileIOS_DISTRIBUTION_CERT_PASSWORDPassword you used when exporting the .p12 IOS_KEYCHAIN_PASSWORDAny random string — used only on the CI runner -
GitHub repo → Settings → Secrets and variables → Actions → Variables, add:
Name Value APPLE_TEAM_IDYour team ID (e.g. A9P2388PHM)
- Bump
MARKETING_VERSIONinDoomCoderCompanion/project.ymland runcd DoomCoderCompanion && xcodegen generate. - Update
CHANGELOG.md(the same section that documents the matching Mac release). git tag ios-v2.4.0 && git push origin ios-v2.4.0.ios-testflight.ymlruns onmacos-26:- Regenerates the project with XcodeGen.
- Stamps
CFBundleShortVersionStringfrom the tag and a timestamp- basedCFBundleVersionso every build is monotonically newer. - Imports the Apple Distribution cert into a throwaway keychain.
- Archives with
-allowProvisioningUpdates+ the App Store Connect API key so Xcode auto-creates the App Store distribution profile. - Exports with
destination=uploadsoxcodebuilditself uploads to App Store Connect (noaltool, noTransporter.app).
- App Store Connect → TestFlight — the build appears in 5-20 min, pending automated processing.
- Once the build is finished processing:
- Internal testing (up to 100 testers in your team): instant.
- External testing: submit for "Beta App Review" (first build per version; subsequent builds skip review).
- App Store release: submit for review, then
Manually releaseorAutomatic release after approval.
If you ever need to upload without GitHub Actions, run from a Mac:
cd DoomCoderCompanion
xcodegen generate
xcodebuild -project DoomCoderCompanion.xcodeproj \
-scheme DoomCoderCompanion -configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/DoomCoderCompanion.xcarchive \
archive
xcodebuild -exportArchive \
-archivePath build/DoomCoderCompanion.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath build/export \
-allowProvisioningUpdates
xcrun altool --upload-app -f build/export/DoomCoderCompanion.ipa \
--type ios \
--apiKey "$APP_STORE_CONNECT_KEY_ID" \
--apiIssuer "$APP_STORE_CONNECT_ISSUER_ID"(Drop the App Store Connect key file at
~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8 so altool can find
it. The key ID must match.)
- The Mac and iOS apps share a marketing version (e.g.
2.4.0) so a user looking at "About" in either app sees the same number when they were released as a pair. - CFBundleVersion (build number):
- Mac: derived from the marketing version (
major*1000 + minor*100 + patch). - iOS: timestamp-based (
YYYYMMDDHHMM). App Store Connect requires a strictly increasing build number for every upload of the same marketing version; timestamps trivially satisfy this without any repo bookkeeping.
- Mac: derived from the marketing version (
- Pre-releases for the Mac use
vX.Y.Z-betaNtags; the iOS pipeline doesn't need a beta channel — TestFlight is the beta channel.
-
CHANGELOG.mdupdated and dated. -
MARKETING_VERSIONbumped inDoomCoder/Info.plistandDoomCoderCompanion/project.yml. Companion project regenerated withxcodegen generate. -
CloudKit Dashboard → Schema → Deploy to Productionif any new record types or fields landed in this release. (For 2.4.0 this means deploying theinstalledAgents+statusesadditions toAgentConfig.) - App Store Connect screenshots / metadata updated if the iOS UI changed visibly.
- Mac build runs clean locally (
xcodebuild -scheme DoomCoder -configuration Release). - iOS build runs clean locally (
xcodebuild -scheme DoomCoderCompanion -configuration Release -destination 'generic/platform=iOS'). - Both tags pushed. CI green on both workflows.
This is the walkthrough you only do once per app, before the very first TestFlight upload. Allow ~60 minutes the first time.
- Visit appstoreconnect.apple.com → My Apps → + → New App.
- Fill in:
- Platform: iOS
- Name:
DoomCoder Companion - Primary Language: English (U.S.)
- Bundle ID:
com.doomcoder.app.companion(must already exist in the Developer Portal under Identifiers) - SKU:
doomcoder-companion-ios - User Access: Full Access
- Apple assigns a numeric Apple ID (e.g.
6480000000). Note it. UpdatecompanionAppStoreURLinDoomCoder/ConfigureSettingsPane.swiftto replace theid0000000000placeholder.
In the app record → App Store tab:
- Category: Developer Tools / Productivity
- Content rights: No third-party content
- Age rating: questionnaire (defaults yield 4+)
- Privacy policy URL: link to
PRIVACY.mdraw URL on GitHub - Support URL:
https://github.com/katipally/Doom-Coder - Marketing URL: optional
App Store Connect → App Privacy → Get Started.
DoomCoder Companion collects nothing. The correct answers:
- Data collected: No
- Tracking: No
This nutrition label is shown on the App Store product page and is mandatory before submission.
Apple requires screenshots for at least one device class. The 6.7-inch iPhone class (e.g. iPhone 17 Pro Max) is the easiest because its screenshots are reused for every smaller class automatically.
Required: 3-10 PNG/JPEG screenshots, 1290×2796 (portrait) or 2796×1290 (landscape).
Quick way:
xcrun simctl boot "iPhone 17 Pro Max"
open -a Simulator
# Build & run the companion in this simulator
xcrun simctl io booted screenshot ~/Desktop/companion-1.pngSuggested shots: agent list, agent detail view, log empty state, onboarding step, notification arriving (lock screen demo if possible).
In Developer Portal → Certificates, Identifiers & Profiles:
- Identifiers — verify all three App IDs exist:
com.doomcoder.app.companion(App, with iCloud + Push + App Groups)com.doomcoder.app.companion.NotificationService(App Extension, with App Groups)com.doomcoder.app(Mac, with iCloud + App Groups)
- App Groups — verify
group.com.doomcoder.app.companionexists and is enabled on all three identifiers. - Certificates — create an Apple Distribution certificate
(covers both iOS and macOS App Store). Download as
.cer, double-click into Keychain, then export from Keychain Access as a .p12 with a password. Base64-encode it (base64 -i AppleDistribution.p12 -o cert.b64) and store as theIOS_DISTRIBUTION_CERTIFICATEGitHub secret. - Profiles — leave to Xcode automatic signing. The CI workflow
uses
-allowProvisioningUpdatesso profiles are minted on demand.
Used by xcodebuild -exportArchive to authenticate uploads non-interactively.
- App Store Connect → Users and Access → Integrations → App Store Connect API.
- + → name
DoomCoder CI, accessApp Manager. - Download the
.p8(you can only download it once). - Note the Issuer ID (top of the page) and Key ID (next to the key).
- Add as GitHub repo secrets:
APP_STORE_CONNECT_KEY_IDAPP_STORE_CONNECT_ISSUER_IDAPP_STORE_CONNECT_PRIVATE_KEY— paste the raw .p8 contents (including-----BEGIN PRIVATE KEY-----lines).
CloudKit Dashboard →
container iCloud.com.doomcoder.app → Schema → Deploy Schema to
Production. Required before the App Store build can talk to the
production CloudKit environment.
After everything above is done:
git tag ios-v2.4.0
git push origin ios-v2.4.0The ios-testflight.yml workflow runs and uploads to App Store
Connect. The build appears under TestFlight in ~10-20 min.
- App Store Connect → your app → App Store tab → + Version.
- Fill What's New in This Version (paste from CHANGELOG.md).
- Build → pick the TestFlight build you just uploaded.
- Submit for Review.
Review usually takes 24-48 hours. Common rejections to pre-empt:
- Missing privacy policy URL.
- Demo account credentials required if the app has a login. DoomCoder Companion does not — call this out in the Notes for Reviewer field: "App requires iCloud sign-in plus a running Mac with DoomCoder installed. No demo account needed."
- Background modes not justified. Our
Info.plistonly enablesremote-notification, which CloudKit subscriptions legitimately require.
No. App Store apps cannot be updated by Sparkle (Apple prohibits
side-loaded update mechanisms in App Store binaries) and cannot be
distributed as .ipa from GitHub Releases (Apple TOS).
The iOS pipeline ends at App Store Connect. We do still cut a matching
GitHub Release for the iOS tag (ios-v2.4.0) but it carries only the
changelog and a link to the App Store — no binary attachment.
If you want the GitHub Release to be created automatically on tag
push, extend ios-testflight.yml with a final
softprops/action-gh-release@v2 step that publishes notes from
CHANGELOG.md. We're keeping that manual for now so the Release
goes live only after Apple actually approves the build.