eleven / ocho Public

mac: release script refuses a build number Sparkle would ignore (#1456)

Sparkle orders by <sparkle:version> (the build number), so a mint at or
below the live appcast head gets its DMG silently shunted to old_updates
after ten minutes of archive + notarize. Preflight against the live feed
and fail with the number to use; assert post-generate_appcast too.


Claude-Session: https://claude.ai/code/session_014mt8aQ3bwLs1kcqeP598xr
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by codex agent, operated by paul@keymail.dev 4eebe02bb555665e7a661f0b6cd3e6d539008aa2 parent 6d20f9f
1 file changed, +22 −0
  • ios/release-mac.sh +22 −0
diff --git a/ios/release-mac.sh b/ios/release-mac.sh
index 5650de9..11413bc 100755
--- a/ios/release-mac.sh
+++ b/ios/release-mac.sh
@@ -49,6 +49,19 @@ BUILD="${2:?usage: release-mac.sh <marketing-version> <build-number>}"
RELEASES_REPO="${LCHAT_RELEASES_REPO:-elevenmessenger/releases}"
TEAM_ID="J5NDF5PCD8"
APP_NAME="Eleven"
+
+# Sparkle orders releases by <sparkle:version> — the BUILD number, not the
+# marketing version — so a build at or below the live feed's head is "old":
+# generate_appcast quietly shunts the fresh DMG to old_updates and ships
+# nothing, ten minutes and one notarization too late (the 0.9.1-as-build-1
+# mint, 2026-08-28). Refuse up front instead.
+LIVE_HEAD="$(gh release download -R "$RELEASES_REPO" -p appcast.xml -O - 2>/dev/null \
+ | sed -n 's/.*<sparkle:version>\([0-9][0-9]*\)<\/sparkle:version>.*/\1/p' \
+ | sort -n | tail -1)"
+if [ -n "$LIVE_HEAD" ] && [ "$BUILD" -le "$LIVE_HEAD" ]; then
+ echo "error: build $BUILD is not newer than the live appcast's head (build $LIVE_HEAD) — Sparkle would ignore it. Use $((LIVE_HEAD + 1))." >&2
+ exit 1
+fi
# Keep in sync with the `packages.Sparkle.exactVersion` pin in ios/project.yml
# — generate_appcast's flags are matched against this exact release's --help.
SPARKLE_VERSION="2.9.4"
@@ -165,6 +178,15 @@ chmod +x "$TMP/sparkle-tools/bin/generate_appcast"
--maximum-deltas 0 \
"$APPCAST_DIR"
+# Belt to the preflight's braces: if generate_appcast still didn't write this
+# build (it moves anything it deems old to old_updates and reports success),
+# say so plainly instead of letting the release-create step fail on a missing
+# file two commands later.
+grep -q "<sparkle:version>$BUILD</sparkle:version>" "$APPCAST_DIR/appcast.xml" || {
+ echo "error: generate_appcast did not add build $BUILD to appcast.xml — nothing to publish" >&2
+ exit 1
+}
+
# Embed the "what's new" HTML inline as this item's <description> so it renders
# in the in-app updater (see embed-release-notes.py for why inline beats a link).
if [ -n "$NOTES_HTML" ]; then