-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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
packagessection with Sparkle fromhttps://github.com/sparkle-project/Sparkle, versionfrom: 2.8.1 - Add
- package: Sparkleto the ShellCraft target'sdependencies - Add Info.plist keys via
INFOPLIST_KEY_build settings:SUFeedURL→https://github.com/omarshahine/ShellCraft/releases/latest/download/appcast.xmlSUPublicEDKey→ placeholder (filled after key generation)
- Set
DEVELOPMENT_TEAMtoN9DRSTM2U6(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 toupdaterController.updater.checkForUpdates() - Use a small helper view
CheckForUpdatesViewthat observesupdater.canCheckForUpdatesvia 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_CERTIFICATEsecret (base64.p12) - Build with
xcodebuildin 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_updatetool - Generate appcast.xml with version info and EdDSA signature
- Create GitHub Release using
softprops/action-gh-releasewith 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_keysfrom Sparkle, copy public key intoproject.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, thenxcodebuild— 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.0tag 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