Sparkle (macOS)
For native macOS apps using Sparkle 2. Create the app with type Sparkle; the appcast returns 400 for other app types.
Feed URL
Set SUFeedURL in Info.plist to the app’s appcast, and SUPublicEDKey to your EdDSA public key:
<key>SUFeedURL</key>
<string>https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml</string>
<key>SUPublicEDKey</key>
<string>YOUR_BASE64_PUBLIC_KEY</string>The appcast at https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml is RSS 2.0 with Sparkle extensions. It lists every enabled, non-draft release in the requested channel that has a macOS asset, newest version first. Each item carries the release notes, sparkle:minimumSystemVersion when you set one on the app, and an enclosure pointing at https://releaseflow.net/{username}/{slug}/updates/download/{version}/{fileName}, which streams the asset from GitHub (private repositories included).
Query parameters:
?channel=betaor?channel=alpha: include pre-releases, see Channels.?sync=1: start a GitHub sync now instead of waiting for the hourly refresh.
Release assets
Attach a .zip of the app bundle to the GitHub release. When a release has both a .zip and a .dmg, the appcast uses the .zip; the .dmg is still offered on the download page. A .zip without mac/darwin in its name is treated as macOS when a matching .zip.sig sits next to it.
Signing
ReleaseFlow never holds your private key and does not sign builds. Sign the archive with Sparkle’s sign_update and upload the signature as <asset name>.sig; on sync, ReleaseFlow reads that file and writes it into the enclosure’s sparkle:edSignature.
# Once: create the key pair (private key goes to the login Keychain)
./bin/generate_keys
# Every release: sign the update archive and upload the .sig next to it
./bin/sign_update -p MyApp.zip > MyApp.zip.sig
gh release upload v1.2.0 MyApp.zip MyApp.zip.sig-pprints only the signature; the.sigfile must contain just that base64 string.- Sparkle checks the signature against
SUPublicEDKeyin the installed app. An item withoutsparkle:edSignatureis rejected, so upload the.sigbefore (or together with) publishing the release, or press Sync Releases after adding it. - Code-sign and notarize the app before zipping; EdDSA signs the archive, not the bundle.
Check the result:
curl -s "https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml?sync=1" | head -40Versions
Sparkle compares sparkle:version with the installed app’s CFBundleVersion. ReleaseFlow derives it from the tag, and writes the tag version to sparkle:shortVersionString. The app’s version strategy decides the format:
| Tag | SEMVER (default) | NUMERIC_BUILD |
|---|---|---|
v1.2.3 | 1.2.3 | 10203 |
v1.3.0-beta.2 | 1.3.0b2 | 10300b2 |
v1.3.0-rc.1 | 1.3.0fc1 | 10300fc1 |
Set CFBundleVersion in the build to the same value. Use NUMERIC_BUILD if your build numbers are integers; it is set with sparkleVersionStrategy on PATCH https://releaseflow.net/api/rest/apps/{id} (see the API reference).
Beta builds
Enable the beta channel on the app, then let users opt in by returning the beta feed from your updater delegate:
import Sparkle
final class UpdaterDelegate: NSObject, SPUUpdaterDelegate {
func feedURLString(for updater: SPUUpdater) -> String? {
UserDefaults.standard.bool(forKey: "betaUpdates")
? "https://releaseflow.net/{username}/{slug}/sparkle/appcast.xml?channel=beta"
: nil // nil = SUFeedURL from Info.plist
}
}Licensing
To gate the app behind a license key, add the ReleaseFlowLicensing Swift package; see License keys.