Auto-Update & Build Distribution for ShellCraft
Context
ShellCraft is a native macOS SwiftUI app with no existing update mechanism or CI/CD pipeline. The goal is to add Sparkle 2 for auto-updates, distributed via GitHub Releases, with a full GitHub Actions workflow for building, signing, notarizing, and publishing releases on tag push.
Overview
Three components:
- Sparkle 2 integration — SPM dependency, updater controller, "Check for Updates" menu item
- GitHub Actions workflow — Build, sign, notarize, create DMG, generate appcast, publish release
- One-time setup instructions — EdDSA key generation, GitHub secrets, Developer ID certificate export
1. Add Sparkle 2 via SPM in project.yml
File: project.yml
- Add
packages section with Sparkle from https://github.com/sparkle-project/Sparkle, version from: 2.8.1
- Add
- package: Sparkle to the ShellCraft target's dependencies
- Add Info.plist keys via
INFOPLIST_KEY_ build settings:
SUFeedURL → https://github.com/omarshahine/ShellCraft/releases/latest/download/appcast.xml
SUPublicEDKey → placeholder (filled after key generation)
- Set
DEVELOPMENT_TEAM to N9DRSTM2U6 (already in the generated xcodeproj)
- Run
xcodegen generate + the icon sed fix afterward
2. Add Sparkle Updater to the App
File: ShellCraft/App/ShellCraftApp.swift
- Import
Sparkle
- Create
SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) as a property
- Add a
CommandGroup(after: .appInfo) with a "Check for Updates..." button wired to updaterController.updater.checkForUpdates()
- Use a small helper view
CheckForUpdatesView that observes updater.canCheckForUpdates via Combine publisher
New file: ShellCraft/Views/Shared/CheckForUpdatesView.swift
A small SwiftUI view + observable class:
import SwiftUI
import Sparkle
struct CheckForUpdatesView: View {
@ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
init(updater: SPUUpdater) {
self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
}
var body: some View {
Button("Check for Updates...", action: checkForUpdatesViewModel.checkForUpdates)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)
}
}
This follows Sparkle's official SwiftUI integration pattern.
3. Update Entitlements (if needed)
File: ShellCraft/ShellCraft.entitlements
ShellCraft is not sandboxed, so no special entitlements are needed for Sparkle. The entitlements file stays empty (or add com.apple.security.network.client if we want to be explicit, but it's not required for non-sandboxed apps).
4. GitHub Actions Release Workflow
New file: .github/workflows/release.yml
Triggered on: push with tags matching v*
Steps:
- Checkout code
- Install XcodeGen via Homebrew
- Run
xcodegen generate + icon sed fix
- Import Developer ID certificate from
MACOS_CERTIFICATE secret (base64 .p12)
- Build with
xcodebuild in Release configuration, signed with Developer ID
- Create DMG using
hdiutil create (simple, no extra tools needed)
- Notarize the DMG with
xcrun notarytool submit + wait for completion
- Staple notarization ticket with
xcrun stapler staple
- Sign DMG with Sparkle EdDSA using Sparkle's
sign_update tool
- Generate appcast.xml with version info and EdDSA signature
- Create GitHub Release using
softprops/action-gh-release with DMG + appcast.xml
5. Claude GitHub App Workflow
New file: .github/workflows/claude.yml
Standard @claude mention workflow, same as other repos. Copy from ~/GitHub/Claude/.github/workflows/claude.yml.
6. GitHub Secrets Needed
| Secret |
Purpose |
MACOS_CERTIFICATE |
Base64-encoded Developer ID Application .p12 |
MACOS_CERTIFICATE_PWD |
Password for the .p12 |
APPLE_ID |
Apple ID email for notarization |
APPLE_APP_PASSWORD |
App-specific password for notarytool |
APPLE_TEAM_ID |
Team ID (N9DRSTM2U6) |
SPARKLE_PRIVATE_KEY |
EdDSA private key for Sparkle signing |
7. One-Time Setup Steps (Manual)
- Generate Sparkle EdDSA keys — run
generate_keys from Sparkle, copy public key into project.yml, store private key as GitHub secret
- Export Developer ID certificate — from Keychain Access, export as
.p12, base64-encode, store as GitHub secret
- Create app-specific password at appleid.apple.com for notarytool
- Set GitHub secrets via
gh secret set
Files Modified/Created
| File |
Action |
project.yml |
Modify — add Sparkle SPM package, dependency, Info.plist keys, team ID |
ShellCraft/App/ShellCraftApp.swift |
Modify — add Sparkle import, updater controller, menu command |
ShellCraft/Views/Shared/CheckForUpdatesView.swift |
Create — SwiftUI "Check for Updates" button + observable |
.github/workflows/release.yml |
Create — full CI/CD build+sign+notarize+release pipeline |
.github/workflows/claude.yml |
Create — @claude mention support |
Verification
- Build locally:
xcodegen generate, then xcodebuild — verify Sparkle resolves and app compiles
- Launch app: Verify "Check for Updates..." appears in the ShellCraft menu
- Menu state: The button should be enabled after the updater initializes
- CI dry run: Push a
v0.1.0 tag to trigger the workflow (will need secrets configured first)
- End-to-end: After first release is published, build a second release and verify the app detects and offers the update
Auto-Update & Build Distribution for ShellCraft
Context
ShellCraft is a native macOS SwiftUI app with no existing update mechanism or CI/CD pipeline. The goal is to add Sparkle 2 for auto-updates, distributed via GitHub Releases, with a full GitHub Actions workflow for building, signing, notarizing, and publishing releases on tag push.
Overview
Three components:
1. Add Sparkle 2 via SPM in
project.ymlFile:
project.ymlpackagessection with Sparkle fromhttps://github.com/sparkle-project/Sparkle, versionfrom: 2.8.1- package: Sparkleto the ShellCraft target'sdependenciesINFOPLIST_KEY_build settings:SUFeedURL→https://github.com/omarshahine/ShellCraft/releases/latest/download/appcast.xmlSUPublicEDKey→ placeholder (filled after key generation)DEVELOPMENT_TEAMtoN9DRSTM2U6(already in the generated xcodeproj)xcodegen generate+ the icon sed fix afterward2. Add Sparkle Updater to the App
File:
ShellCraft/App/ShellCraftApp.swiftSparkleSPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)as a propertyCommandGroup(after: .appInfo)with a "Check for Updates..." button wired toupdaterController.updater.checkForUpdates()CheckForUpdatesViewthat observesupdater.canCheckForUpdatesvia Combine publisherNew file:
ShellCraft/Views/Shared/CheckForUpdatesView.swiftA small SwiftUI view + observable class:
This follows Sparkle's official SwiftUI integration pattern.
3. Update Entitlements (if needed)
File:
ShellCraft/ShellCraft.entitlementsShellCraft is not sandboxed, so no special entitlements are needed for Sparkle. The entitlements file stays empty (or add
com.apple.security.network.clientif we want to be explicit, but it's not required for non-sandboxed apps).4. GitHub Actions Release Workflow
New file:
.github/workflows/release.ymlTriggered on:
pushwith tags matchingv*Steps:
xcodegen generate+ icon sed fixMACOS_CERTIFICATEsecret (base64.p12)xcodebuildin Release configuration, signed with Developer IDhdiutil create(simple, no extra tools needed)xcrun notarytool submit+ wait for completionxcrun stapler staplesign_updatetoolsoftprops/action-gh-releasewith DMG + appcast.xml5. Claude GitHub App Workflow
New file:
.github/workflows/claude.ymlStandard
@claudemention workflow, same as other repos. Copy from~/GitHub/Claude/.github/workflows/claude.yml.6. GitHub Secrets Needed
MACOS_CERTIFICATE.p12MACOS_CERTIFICATE_PWD.p12APPLE_IDAPPLE_APP_PASSWORDAPPLE_TEAM_IDN9DRSTM2U6)SPARKLE_PRIVATE_KEY7. One-Time Setup Steps (Manual)
generate_keysfrom Sparkle, copy public key intoproject.yml, store private key as GitHub secret.p12, base64-encode, store as GitHub secretgh secret setFiles Modified/Created
project.ymlShellCraft/App/ShellCraftApp.swiftShellCraft/Views/Shared/CheckForUpdatesView.swift.github/workflows/release.yml.github/workflows/claude.ymlVerification
xcodegen generate, thenxcodebuild— verify Sparkle resolves and app compilesv0.1.0tag to trigger the workflow (will need secrets configured first)