@@ -4,9 +4,9 @@ set -euo pipefail
44# Signs release archives and generates appcast.xml using Sparkle's
55# generate_appcast — the official tool for building Sparkle update feeds.
66#
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) .
7+ # Sparkle 2.9+ rejects multiple archives with the same bundle version in
8+ # a single directory, so we run generate_appcast once per architecture
9+ # and merge the resulting appcast entries .
1010#
1111# Usage: sign-and-appcast.sh <version>
1212# Requires: SPARKLE_PRIVATE_KEY env var, artifacts/ directory with ZIPs.
@@ -25,22 +25,7 @@ brew list --cask sparkle &>/dev/null || brew install --cask sparkle
2525SPARKLE_BIN=" $( brew --caskroom) /sparkle/$( ls " $( brew --caskroom) /sparkle" | head -1) /bin"
2626
2727# ---------------------------------------------------------------------------
28- # 2. Prepare staging directory
29- # ---------------------------------------------------------------------------
30- STAGING=$( mktemp -d)
31- trap ' rm -rf "$STAGING"' EXIT
32-
33- # Copy archives
34- cp " artifacts/TablePro-${VERSION} -arm64.zip" " $STAGING /"
35- cp " artifacts/TablePro-${VERSION} -x86_64.zip" " $STAGING /"
36-
37- # Copy existing appcast for history preservation
38- if [ -f appcast.xml ]; then
39- cp appcast.xml " $STAGING /"
40- fi
41-
42- # ---------------------------------------------------------------------------
43- # 3. Extract release notes from CHANGELOG.md → HTML
28+ # 2. Extract release notes from CHANGELOG.md → HTML
4429# ---------------------------------------------------------------------------
4530if [ -f release_notes.md ]; then
4631 NOTES=$( cat release_notes.md)
@@ -68,35 +53,108 @@ else
6853 ' )
6954fi
7055
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} "
56+ DOWNLOAD_PREFIX=" ${GITHUB_SERVER_URL:- https:// github.com} /${GITHUB_REPOSITORY:- TableProApp/ TablePro} /releases/download/v${VERSION} "
57+
58+ KEY_FILE=$( mktemp)
59+ trap ' rm -rf "$KEY_FILE"' EXIT
60+
61+ echo " $SPARKLE_PRIVATE_KEY " > " $KEY_FILE "
62+
63+ # ---------------------------------------------------------------------------
64+ # 3. Generate appcast per architecture
65+ # ---------------------------------------------------------------------------
66+ # Sparkle 2.9+ does not allow two archives with the same bundle version
67+ # in one directory. Process each architecture separately and merge.
68+
69+ ARCHS=(" arm64" " x86_64" )
70+ APPCAST_XMLS=()
71+
72+ for arch in " ${ARCHS[@]} " ; do
73+ ZIP=" artifacts/TablePro-${VERSION} -${arch} .zip"
74+ if [ ! -f " $ZIP " ]; then
75+ echo " ⚠️ Skipping $arch — $ZIP not found"
76+ continue
77+ fi
78+
79+ STAGING=$( mktemp -d)
80+
81+ cp " $ZIP " " $STAGING /"
82+
83+ # Release notes file matching archive name
84+ basename=" ${STAGING} /TablePro-${VERSION} -${arch} "
7585 echo " $RELEASE_HTML " > " ${basename} .html"
86+
87+ # Copy existing appcast for history preservation (only for first arch)
88+ if [ " ${# APPCAST_XMLS[@]} " -eq 0 ] && [ -f appcast.xml ]; then
89+ cp appcast.xml " $STAGING /"
90+ fi
91+
92+ " $SPARKLE_BIN /generate_appcast" \
93+ --ed-key-file " $KEY_FILE " \
94+ --download-url-prefix " $DOWNLOAD_PREFIX " \
95+ --embed-release-notes \
96+ --maximum-versions 0 \
97+ " $STAGING "
98+
99+ APPCAST_XMLS+=(" $STAGING /appcast.xml" )
76100done
77101
78102# ---------------------------------------------------------------------------
79- # 4. Run generate_appcast
103+ # 4. Merge appcast files
80104# ---------------------------------------------------------------------------
81- DOWNLOAD_PREFIX=" ${GITHUB_SERVER_URL:- https:// github.com} /${GITHUB_REPOSITORY:- TableProApp/ TablePro} /releases/download/v${VERSION} "
105+ if [ " ${# APPCAST_XMLS[@]} " -eq 0 ]; then
106+ echo " ❌ ERROR: No archives found to process"
107+ exit 1
108+ fi
82109
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 "
110+ if [ " ${# APPCAST_XMLS[@]} " -eq 1 ]; then
111+ # Single arch — use as-is
112+ FINAL_APPCAST=" ${APPCAST_XMLS[0]} "
113+ else
114+ # Merge: take the first appcast (has history + arm64 entry), then
115+ # extract only the NEW item(s) from the second appcast and insert them.
116+ FINAL_APPCAST=" ${APPCAST_XMLS[0]} "
117+ SECOND_APPCAST=" ${APPCAST_XMLS[1]} "
87118
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 "
119+ # Extract <item>...</item> blocks for the current version from second appcast
120+ NEW_ITEMS=$( awk "
121+ /<item>/ { capture=1; buf=\"\" }
122+ capture { buf = buf \$ 0 \"\\ n\" }
123+ /<\\ /item>/ {
124+ capture=0
125+ if (buf ~ /<sparkle:shortVersionString>${VERSION} </) {
126+ printf \" %s\" , buf
127+ }
128+ }
129+ " " $SECOND_APPCAST " )
130+
131+ if [ -n " $NEW_ITEMS " ]; then
132+ # Insert the new items after the first <item> block for this version
133+ # (i.e., after the arm64 entry's closing </item>)
134+ awk -v new_items=" $NEW_ITEMS " -v version=" $VERSION " '
135+ BEGIN { inserted=0 }
136+ /<\/item>/ {
137+ print
138+ if (!inserted && found_version) {
139+ printf "%s", new_items
140+ inserted=1
141+ }
142+ next
143+ }
144+ /<sparkle:shortVersionString>/ {
145+ if (index($0, version) > 0) found_version=1
146+ }
147+ { print }
148+ ' " $FINAL_APPCAST " > " ${FINAL_APPCAST} .merged"
149+ mv " ${FINAL_APPCAST} .merged" " $FINAL_APPCAST "
150+ fi
151+ fi
94152
95153# ---------------------------------------------------------------------------
96154# 5. Copy result
97155# ---------------------------------------------------------------------------
98156mkdir -p appcast
99- cp " $STAGING /appcast.xml " appcast/appcast.xml
157+ cp " $FINAL_APPCAST " appcast/appcast.xml
100158
101159echo " ✅ Appcast generated by generate_appcast:"
102160cat appcast/appcast.xml
0 commit comments