11#! /usr/bin/env bash
22set -euo pipefail
33
4- # Signs release archives with Sparkle EdDSA and generates/updates appcast.xml.
4+ # Signs release archives and generates appcast.xml using Sparkle's
5+ # generate_appcast — the official tool for building Sparkle update feeds.
6+ #
7+ # generate_appcast handles: EdDSA signing, architecture detection,
8+ # version extraction from Info.plist, CDATA release notes, and merging
9+ # new items into an existing appcast (preserving history).
510#
611# Usage: sign-and-appcast.sh <version>
712# Requires: SPARKLE_PRIVATE_KEY env var, artifacts/ directory with ZIPs.
8- #
9- # The appcast.xml preserves history — new items are prepended to the existing
10- # feed so Sparkle shows cumulative release notes for users skipping versions.
1113
1214VERSION=" ${1:? Usage: sign-and-appcast.sh <version>} "
1315
@@ -23,58 +25,31 @@ brew list --cask sparkle &>/dev/null || brew install --cask sparkle
2325SPARKLE_BIN=" $( brew --caskroom) /sparkle/$( ls " $( brew --caskroom) /sparkle" | head -1) /bin"
2426
2527# ---------------------------------------------------------------------------
26- # 2. Sign archives with EdDSA
28+ # 2. Prepare staging directory
2729# ---------------------------------------------------------------------------
28- ARM64_ZIP=" artifacts/TablePro-${VERSION} -arm64.zip"
29- X86_64_ZIP=" artifacts/TablePro-${VERSION} -x86_64.zip"
30-
31- KEY_FILE=$( mktemp)
32- trap ' rm -f "$KEY_FILE"' EXIT
33- echo " $SPARKLE_PRIVATE_KEY " > " $KEY_FILE "
30+ STAGING=$( mktemp -d)
31+ trap ' rm -rf "$STAGING"' EXIT
3432
35- parse_sig () {
36- local output=" $1 " field=" $2 "
37- echo " $output " | sed -n " s/.*${field} =\"\\ ([^\" ]*\\ )\" .*/\\ 1/p"
38- }
39-
40- ARM64_SIG=$( " $SPARKLE_BIN /sign_update" " $ARM64_ZIP " -f " $KEY_FILE " )
41- X86_64_SIG=$( " $SPARKLE_BIN /sign_update" " $X86_64_ZIP " -f " $KEY_FILE " )
42-
43- ARM64_ED_SIG=$( parse_sig " $ARM64_SIG " " sparkle:edSignature" )
44- ARM64_LENGTH=$( parse_sig " $ARM64_SIG " " length" )
45- X86_64_ED_SIG=$( parse_sig " $X86_64_SIG " " sparkle:edSignature" )
46- X86_64_LENGTH=$( parse_sig " $X86_64_SIG " " length" )
47-
48- # ---------------------------------------------------------------------------
49- # 3. Extract version metadata from the built app
50- # ---------------------------------------------------------------------------
51- TEMP_DIR=$( mktemp -d)
52- unzip -q " $ARM64_ZIP " -d " $TEMP_DIR "
53- INFO_PLIST=$( find " $TEMP_DIR " -maxdepth 3 -path " */Contents/Info.plist" | head -1)
33+ # Copy archives
34+ cp " artifacts/TablePro-${VERSION} -arm64.zip" " $STAGING /"
35+ cp " artifacts/TablePro-${VERSION} -x86_64.zip" " $STAGING /"
5436
55- if [ -n " $INFO_PLIST " ] && [ -f " $INFO_PLIST " ]; then
56- BUILD_NUMBER=$( /usr/libexec/PlistBuddy -c " Print :CFBundleVersion" " $INFO_PLIST " 2> /dev/null || echo " 1" )
57- SHORT_VERSION=$( /usr/libexec/PlistBuddy -c " Print :CFBundleShortVersionString" " $INFO_PLIST " 2> /dev/null || echo " $VERSION " )
58- MIN_OS=$( /usr/libexec/PlistBuddy -c " Print :LSMinimumSystemVersion" " $INFO_PLIST " 2> /dev/null || echo " 14.0" )
59- else
60- echo " ⚠️ Could not find app Info.plist in ZIP, using defaults"
61- BUILD_NUMBER=" 1"
62- SHORT_VERSION=" $VERSION "
63- MIN_OS=" 14.0"
37+ # Copy existing appcast for history preservation
38+ if [ -f appcast.xml ]; then
39+ cp appcast.xml " $STAGING /"
6440fi
65- rm -rf " $TEMP_DIR "
6641
6742# ---------------------------------------------------------------------------
68- # 4 . Extract release notes from CHANGELOG.md → HTML
43+ # 3 . Extract release notes from CHANGELOG.md → HTML
6944# ---------------------------------------------------------------------------
7045if [ -f release_notes.md ]; then
71- NOTES=$( cat release_notes.md)
46+ NOTES=$( cat release_notes.md)
7247else
73- NOTES=$( awk " /^## \\ [${VERSION} \\ ]/{flag=1; next} /^## \\ [/{flag=0} flag" CHANGELOG.md)
48+ NOTES=$( awk " /^## \\ [${VERSION} \\ ]/{flag=1; next} /^## \\ [/{flag=0} flag" CHANGELOG.md)
7449fi
7550
7651if [ -z " $NOTES " ]; then
77- RELEASE_HTML=" <li>Bug fixes and improvements</li>"
52+ RELEASE_HTML=" <ul>< li>Bug fixes and improvements</li></ul >"
7853else
7954 RELEASE_HTML=$( echo " $NOTES " | sed -E \
8055 -e ' s/^### (.+)$/<h3>\1<\/h3>/' \
9368 ' )
9469fi
9570
96- DESCRIPTION_HTML=" <body style=\" font-family: -apple-system, sans-serif; font-size: 13px; padding: 8px;\" >${RELEASE_HTML} </body>"
71+ # Create HTML release notes files matching each archive name.
72+ # generate_appcast picks up <archive-name>.html automatically.
73+ for zip in " $STAGING " /TablePro-* .zip; do
74+ basename=" ${zip% .zip} "
75+ echo " $RELEASE_HTML " > " ${basename} .html"
76+ done
9777
9878# ---------------------------------------------------------------------------
99- # 5. Build appcast.xml — merge new items into existing feed
79+ # 4. Run generate_appcast
10080# ---------------------------------------------------------------------------
10181DOWNLOAD_PREFIX=" ${GITHUB_SERVER_URL:- https:// github.com} /${GITHUB_REPOSITORY:- TableProApp/ TablePro} /releases/download/v${VERSION} "
102- PUB_DATE=$( date -u ' +%a, %d %b %Y %H:%M:%S +0000' )
103- EXISTING_APPCAST=" appcast.xml"
10482
105- mkdir -p appcast
83+ KEY_FILE=$( mktemp)
84+ # Override trap to clean up both
85+ trap ' rm -rf "$STAGING" "$KEY_FILE"' EXIT
86+ echo " $SPARKLE_PRIVATE_KEY " > " $KEY_FILE "
10687
107- python3 scripts/ci/update-appcast.py \
108- --output appcast/appcast.xml \
109- --existing " $EXISTING_APPCAST " \
110- --version " $SHORT_VERSION " \
111- --build " $BUILD_NUMBER " \
112- --min-os " $MIN_OS " \
113- --pub-date " $PUB_DATE " \
114- --description " $DESCRIPTION_HTML " \
115- --arm64-url " ${DOWNLOAD_PREFIX} /TablePro-${VERSION} -arm64.zip" \
116- --arm64-length " $ARM64_LENGTH " \
117- --arm64-sig " $ARM64_ED_SIG " \
118- --x86-url " ${DOWNLOAD_PREFIX} /TablePro-${VERSION} -x86_64.zip" \
119- --x86-length " $X86_64_LENGTH " \
120- --x86-sig " $X86_64_ED_SIG "
88+ " $SPARKLE_BIN /generate_appcast" \
89+ --ed-key-file " $KEY_FILE " \
90+ --download-url-prefix " $DOWNLOAD_PREFIX " \
91+ --embed-release-notes \
92+ --maximum-versions 0 \
93+ " $STAGING "
94+
95+ # ---------------------------------------------------------------------------
96+ # 5. Copy result
97+ # ---------------------------------------------------------------------------
98+ mkdir -p appcast
99+ cp " $STAGING /appcast.xml" appcast/appcast.xml
121100
122- echo " ✅ Appcast generated:"
101+ echo " ✅ Appcast generated by generate_appcast :"
123102cat appcast/appcast.xml
0 commit comments