Add Sparkle-based app updater and release appcast artifacts#4
Conversation
| <item> | ||
| <title>Version ${VERSION}</title> | ||
| <pubDate>${PUB_DATE}</pubDate> | ||
| <sparkle:version>${VERSION}</sparkle:version> |
There was a problem hiding this comment.
🔴 Appcast sparkle:version uses marketing version instead of build number, breaking update detection
The appcast.xml sets sparkle:version to the semantic version string (e.g., 1.2.3), but the app's CFBundleVersion is set to a computed build number (e.g., 10203 for version 1.2.3) at .github/workflows/release.yml:135.
Root Cause and Impact
Sparkle compares sparkle:version from the appcast against the running app's CFBundleVersion to determine if an update is available. The workflow computes CFBundleVersion as MAJOR * 10000 + MINOR * 100 + PATCH (e.g., version 1.2.3 → build 10203) at line 135:
BUILD=$((MAJOR * 10000 + MINOR * 100 + PATCH))But the appcast sets sparkle:version to the raw semantic version string ${VERSION} (e.g., 1.2.3):
<sparkle:version>${VERSION}</sparkle:version>Sparkle's SUStandardVersionComparator splits version strings by . and compares components. When comparing appcast's 1.2.3 (components: [1, 2, 3]) against the installed app's 10203 (single component: [10203]), the first component comparison is 1 vs 10203 — the installed version always appears newer.
Impact: Sparkle will never offer updates to users because it always thinks the installed version is newer than the version advertised in the appcast. The entire auto-update feature is non-functional.
The fix is to set sparkle:version to the same computed build number used for CFBundleVersion, e.g.:
<sparkle:version>${BUILD}</sparkle:version>This requires computing the BUILD variable in the "Generate appcast.xml" step or passing it as an output from the version step.
Prompt for agents
In .github/workflows/release.yml, the 'Generate appcast.xml' step at line 273 sets sparkle:version to the semantic version string (VERSION, e.g. '1.2.3'), but it must match CFBundleVersion which is computed as MAJOR*10000 + MINOR*100 + PATCH (e.g. 10203) at line 135.
To fix this:
1. In the 'Get version from tag' step (around line 127), add an output for the build number by adding this line after line 135:
echo "build=$BUILD" >> $GITHUB_OUTPUT
2. In the 'Generate appcast.xml' step (around line 259), add a BUILD variable:
BUILD="${{ steps.version.outputs.build }}"
3. Change line 273 from:
<sparkle:version>${VERSION}</sparkle:version>
to:
<sparkle:version>${BUILD}</sparkle:version>
This ensures sparkle:version matches CFBundleVersion so Sparkle can correctly detect when updates are available.
Was this helpful? React with 👍 or 👎 to provide feedback.
Integrates Sparkle into the macOS app and adds a new updater view model exposed in the menu bar via a Check for Updates action. Adds SUFeedURL and SUPublicEDKey to the app Info.plist and wires Sparkle as an Xcode package dependency. Updates the release workflow to produce a versioned zip, sign it with Sparkle, generate appcast.xml, and upload zip/appcast alongside the DMG. This enables in-app update checks against GitHub Releases artifacts.