Skip to content

Commit 5785547

Browse files
committed
fix: handle Sparkle 2.9 duplicate version error in appcast generation
1 parent 63054a3 commit 5785547

File tree

1 file changed

+94
-36
lines changed

1 file changed

+94
-36
lines changed

scripts/ci/sign-and-appcast.sh

Lines changed: 94 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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
2525
SPARKLE_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
# ---------------------------------------------------------------------------
4530
if [ -f release_notes.md ]; then
4631
NOTES=$(cat release_notes.md)
@@ -68,35 +53,108 @@ else
6853
')
6954
fi
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")
76100
done
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
# ---------------------------------------------------------------------------
98156
mkdir -p appcast
99-
cp "$STAGING/appcast.xml" appcast/appcast.xml
157+
cp "$FINAL_APPCAST" appcast/appcast.xml
100158

101159
echo "✅ Appcast generated by generate_appcast:"
102160
cat appcast/appcast.xml

0 commit comments

Comments
 (0)