Skip to content

Commit d3111bf

Browse files
committed
fix: preserve appcast history for cumulative Sparkle update notes
Refactor sign-and-appcast.sh to merge new items into the existing appcast.xml instead of overwriting it. Uses a Python script for proper XML handling instead of shell heredoc.
1 parent 6242ea4 commit d3111bf

3 files changed

Lines changed: 378 additions & 49 deletions

File tree

appcast.xml

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ul>
1414
<li>Table queries incorrectly prefixed with connection username as schema name on non-schema databases (MySQL, MariaDB, ClickHouse, Redis, etc.), causing "Table 'username.table' doesn't exist" errors when opening a second table tab</li>
1515
</ul></body>]]></description>
16-
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.27.1/TablePro-0.27.1-arm64.zip" length="26377697" type="application/octet-stream" sparkle:edSignature="y2dHsqx7Osx7VKogYzQ/DA2dhUnmd+si1UzLLbmyo/V9u1O05AWboycXvJueUoAnDN6LQvZ3UNnVE1kFyR9tDw=="/>
16+
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.27.1/TablePro-0.27.1-arm64.zip" length="26377697" type="application/octet-stream" sparkle:edSignature="y2dHsqx7Osx7VKogYzQ/DA2dhUnmd+si1UzLLbmyo/V9u1O05AWboycXvJueUoAnDN6LQvZ3UNnVE1kFyR9tDw==" />
1717
</item>
1818
<item>
1919
<title>0.27.1</title>
@@ -25,7 +25,138 @@
2525
<ul>
2626
<li>Table queries incorrectly prefixed with connection username as schema name on non-schema databases (MySQL, MariaDB, ClickHouse, Redis, etc.), causing "Table 'username.table' doesn't exist" errors when opening a second table tab</li>
2727
</ul></body>]]></description>
28-
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.27.1/TablePro-0.27.1-x86_64.zip" length="23306298" type="application/octet-stream" sparkle:edSignature="sNCHw0MYMo2ytKD2sujeoK30S1fXvPCLXoqjGqx6HG78x0fo0cB/6eAnirx6yQ0YW/geo3RoJ3+B/gkVqv6FCQ=="/>
28+
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.27.1/TablePro-0.27.1-x86_64.zip" length="23306298" type="application/octet-stream" sparkle:edSignature="sNCHw0MYMo2ytKD2sujeoK30S1fXvPCLXoqjGqx6HG78x0fo0cB/6eAnirx6yQ0YW/geo3RoJ3+B/gkVqv6FCQ==" />
29+
</item>
30+
<item>
31+
<title>0.27.0</title>
32+
<pubDate>Tue, 31 Mar 2026 16:33:58 +0000</pubDate>
33+
<sparkle:version>53</sparkle:version>
34+
<sparkle:shortVersionString>0.27.0</sparkle:shortVersionString>
35+
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
36+
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
37+
<description><![CDATA[<body style="font-family: -apple-system, sans-serif; font-size: 13px; padding: 8px;"><h3>Added</h3>
38+
<ul>
39+
<li>Option to prompt for database password on every connection instead of saving to Keychain</li>
40+
<li>Autocompletion for filter fields: column names and SQL keywords suggested as you type (Raw SQL and Value fields)</li>
41+
<li>Multi-line support for Raw SQL filter field (Option+Enter for newline)</li>
42+
<li>Visual Create Table UI with multi-database support (sidebar → "Create New Table...")</li>
43+
<li>Auto-fit column width: double-click column divider or right-click → "Size to Fit"</li>
44+
<li>Collapsible results panel (`Cmd+Opt+R`), multiple result tabs for multi-statement queries, result pinning</li>
45+
<li>Inline error banner for query errors</li>
46+
<li>JSON syntax highlighting and brace matching in Details sidebar and JSON editor popover</li>
47+
<li>Database-aware SQL functions in field menu (MySQL, PostgreSQL, SQLite, SQL Server, ClickHouse)</li>
48+
</ul>
49+
<h3>Changed</h3>
50+
<ul>
51+
<li>Replace GCD dispatch patterns with Swift structured concurrency</li>
52+
<li>Refactor Details sidebar into modular field editor architecture with extracted editor components</li>
53+
</ul>
54+
<h3>Fixed</h3>
55+
<ul>
56+
<li>PostgreSQL: Schema name lost after app restart, causing "relation does not exist" errors for non-public schemas</li>
57+
<li>Error dialog OK button not dismissing when a SwiftUI sheet is active, making the app unusable</li>
58+
<li>SQL Server: Unicode characters (Thai, CJK, etc.) in nvarchar/nchar/ntext columns displaying as question marks</li>
59+
<li>Globe+F (fn+F) fullscreen shortcut not working in SwiftUI lifecycle app</li>
60+
</ul></body>]]></description>
61+
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.27.0/TablePro-0.27.0-arm64.zip" length="26378023" type="application/octet-stream" sparkle:edSignature="n53O8RIe3dSnxsDNkM8gfwPGKiu/vhp1G75fHA0DKZxA2m+zx4HU7Y7gSnZhMMksmtHf34SQoOOIEzoeIhKfDQ==" />
62+
</item>
63+
<item>
64+
<title>0.27.0</title>
65+
<pubDate>Tue, 31 Mar 2026 16:33:58 +0000</pubDate>
66+
<sparkle:version>53</sparkle:version>
67+
<sparkle:shortVersionString>0.27.0</sparkle:shortVersionString>
68+
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
69+
<description><![CDATA[<body style="font-family: -apple-system, sans-serif; font-size: 13px; padding: 8px;"><h3>Added</h3>
70+
<ul>
71+
<li>Option to prompt for database password on every connection instead of saving to Keychain</li>
72+
<li>Autocompletion for filter fields: column names and SQL keywords suggested as you type (Raw SQL and Value fields)</li>
73+
<li>Multi-line support for Raw SQL filter field (Option+Enter for newline)</li>
74+
<li>Visual Create Table UI with multi-database support (sidebar → "Create New Table...")</li>
75+
<li>Auto-fit column width: double-click column divider or right-click → "Size to Fit"</li>
76+
<li>Collapsible results panel (`Cmd+Opt+R`), multiple result tabs for multi-statement queries, result pinning</li>
77+
<li>Inline error banner for query errors</li>
78+
<li>JSON syntax highlighting and brace matching in Details sidebar and JSON editor popover</li>
79+
<li>Database-aware SQL functions in field menu (MySQL, PostgreSQL, SQLite, SQL Server, ClickHouse)</li>
80+
</ul>
81+
<h3>Changed</h3>
82+
<ul>
83+
<li>Replace GCD dispatch patterns with Swift structured concurrency</li>
84+
<li>Refactor Details sidebar into modular field editor architecture with extracted editor components</li>
85+
</ul>
86+
<h3>Fixed</h3>
87+
<ul>
88+
<li>PostgreSQL: Schema name lost after app restart, causing "relation does not exist" errors for non-public schemas</li>
89+
<li>Error dialog OK button not dismissing when a SwiftUI sheet is active, making the app unusable</li>
90+
<li>SQL Server: Unicode characters (Thai, CJK, etc.) in nvarchar/nchar/ntext columns displaying as question marks</li>
91+
<li>Globe+F (fn+F) fullscreen shortcut not working in SwiftUI lifecycle app</li>
92+
</ul></body>]]></description>
93+
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.27.0/TablePro-0.27.0-x86_64.zip" length="23306571" type="application/octet-stream" sparkle:edSignature="6VU+4T6agdBHgCCNiJIafAQtVlQaOxctK81qfsXOQU7Bhxh5eF3wpHLYKioDlKOgxSJb7R+EjoLenZZWmMNwCg==" />
94+
</item>
95+
<item>
96+
<title>0.26.0</title>
97+
<pubDate>Sun, 29 Mar 2026 15:52:19 +0000</pubDate>
98+
<sparkle:version>52</sparkle:version>
99+
<sparkle:shortVersionString>0.26.0</sparkle:shortVersionString>
100+
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
101+
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
102+
<description><![CDATA[<body style="font-family: -apple-system, sans-serif; font-size: 13px; padding: 8px;"><h3>Added</h3>
103+
<ul>
104+
<li>Global toggle to disable all AI features (Settings > AI)</li>
105+
<li>Drag to reorder columns in the Structure tab (MySQL/MariaDB)</li>
106+
<li>Nested hierarchical groups for connection list (up to 3 levels deep)</li>
107+
<li>Confirmation dialogs for deep link queries, connection imports, and pre-connect scripts</li>
108+
<li>JSON fields in Row Details sidebar now display in a scrollable monospaced text area</li>
109+
<li>Open, save, and save-as for SQL files with native macOS title bar integration (#475)</li>
110+
<li>BigQuery plugin support (Google BigQuery analytics via REST API)</li>
111+
</ul>
112+
<h3>Changed</h3>
113+
<ul>
114+
<li>Removed query history sync from iCloud Sync (connections, groups, settings, and SSH profiles still sync)</li>
115+
</ul>
116+
<h3>Fixed</h3>
117+
<ul>
118+
<li>SQL editor not auto-focused on new tab and cursor missing after tab switch</li>
119+
<li>Long lines not scrollable horizontally in the SQL editor</li>
120+
<li>Home and End keys not moving cursor in the SQL editor (#448)</li>
121+
<li>SSH profile lost after app restart when iCloud Sync enabled</li>
122+
<li>MariaDB JSON columns showing as hex dumps instead of JSON text</li>
123+
<li>MongoDB Atlas TLS certificate verification failure</li>
124+
<li>ENUM/SET dropdown chevron buttons not showing on first table open</li>
125+
</ul></body>]]></description>
126+
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.26.0/TablePro-0.26.0-arm64.zip" length="26177098" type="application/octet-stream" sparkle:edSignature="diNKGmXTDnmind3UW/mRVDYgBIoYUiADz5O3MAHjcb0Y0/Zzo3PQKd65QNoCbX3jC5MbT1ptts8K24JuoPGoBg==" />
127+
</item>
128+
<item>
129+
<title>0.26.0</title>
130+
<pubDate>Sun, 29 Mar 2026 15:52:19 +0000</pubDate>
131+
<sparkle:version>52</sparkle:version>
132+
<sparkle:shortVersionString>0.26.0</sparkle:shortVersionString>
133+
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
134+
<description><![CDATA[<body style="font-family: -apple-system, sans-serif; font-size: 13px; padding: 8px;"><h3>Added</h3>
135+
<ul>
136+
<li>Global toggle to disable all AI features (Settings > AI)</li>
137+
<li>Drag to reorder columns in the Structure tab (MySQL/MariaDB)</li>
138+
<li>Nested hierarchical groups for connection list (up to 3 levels deep)</li>
139+
<li>Confirmation dialogs for deep link queries, connection imports, and pre-connect scripts</li>
140+
<li>JSON fields in Row Details sidebar now display in a scrollable monospaced text area</li>
141+
<li>Open, save, and save-as for SQL files with native macOS title bar integration (#475)</li>
142+
<li>BigQuery plugin support (Google BigQuery analytics via REST API)</li>
143+
</ul>
144+
<h3>Changed</h3>
145+
<ul>
146+
<li>Removed query history sync from iCloud Sync (connections, groups, settings, and SSH profiles still sync)</li>
147+
</ul>
148+
<h3>Fixed</h3>
149+
<ul>
150+
<li>SQL editor not auto-focused on new tab and cursor missing after tab switch</li>
151+
<li>Long lines not scrollable horizontally in the SQL editor</li>
152+
<li>Home and End keys not moving cursor in the SQL editor (#448)</li>
153+
<li>SSH profile lost after app restart when iCloud Sync enabled</li>
154+
<li>MariaDB JSON columns showing as hex dumps instead of JSON text</li>
155+
<li>MongoDB Atlas TLS certificate verification failure</li>
156+
<li>ENUM/SET dropdown chevron buttons not showing on first table open</li>
157+
</ul></body>]]></description>
158+
<enclosure url="https://github.com/TableProApp/TablePro/releases/download/v0.26.0/TablePro-0.26.0-x86_64.zip" length="23103948" type="application/octet-stream" sparkle:edSignature="0SKMK5+lQyDLfMr1zHfzeH+h3QjGtrz5rhRmEFAayER09mNaQhNN9shycatOIOoTVHcroYGW2exw4C27uZpMCg==" />
29159
</item>
30160
</channel>
31161
</rss>
162+

scripts/ci/sign-and-appcast.sh

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,53 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4+
# Signs release archives with Sparkle EdDSA and generates/updates appcast.xml.
5+
#
6+
# Usage: sign-and-appcast.sh <version>
7+
# 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.
11+
412
VERSION="${1:?Usage: sign-and-appcast.sh <version>}"
513

614
if [ -z "${SPARKLE_PRIVATE_KEY:-}" ]; then
715
echo "❌ ERROR: SPARKLE_PRIVATE_KEY environment variable is not set"
816
exit 1
917
fi
1018

11-
# Install Sparkle tools (Cask — binaries in Caskroom, not on PATH)
19+
# ---------------------------------------------------------------------------
20+
# 1. Locate Sparkle tools
21+
# ---------------------------------------------------------------------------
1222
brew list --cask sparkle &>/dev/null || brew install --cask sparkle
1323
SPARKLE_BIN="$(brew --caskroom)/sparkle/$(ls "$(brew --caskroom)/sparkle" | head -1)/bin"
1424

25+
# ---------------------------------------------------------------------------
26+
# 2. Sign archives with EdDSA
27+
# ---------------------------------------------------------------------------
1528
ARM64_ZIP="artifacts/TablePro-${VERSION}-arm64.zip"
1629
X86_64_ZIP="artifacts/TablePro-${VERSION}-x86_64.zip"
1730

18-
# Sign each ZIP with EdDSA using sign_update
1931
KEY_FILE=$(mktemp)
2032
trap 'rm -f "$KEY_FILE"' EXIT
2133
echo "$SPARKLE_PRIVATE_KEY" > "$KEY_FILE"
34+
35+
parse_sig() {
36+
local output="$1" field="$2"
37+
echo "$output" | sed -n "s/.*${field}=\"\\([^\"]*\\)\".*/\\1/p"
38+
}
39+
2240
ARM64_SIG=$("$SPARKLE_BIN/sign_update" "$ARM64_ZIP" -f "$KEY_FILE")
2341
X86_64_SIG=$("$SPARKLE_BIN/sign_update" "$X86_64_ZIP" -f "$KEY_FILE")
2442

25-
# Parse signature and length from sign_update output
26-
# Output format: sparkle:edSignature="..." length="..."
27-
ARM64_ED_SIG=$(echo "$ARM64_SIG" | sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p')
28-
ARM64_LENGTH=$(echo "$ARM64_SIG" | sed -n 's/.*length="\([^"]*\)".*/\1/p')
29-
X86_64_ED_SIG=$(echo "$X86_64_SIG" | sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p')
30-
X86_64_LENGTH=$(echo "$X86_64_SIG" | sed -n 's/.*length="\([^"]*\)".*/\1/p')
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")
3147

32-
# Extract version info from the top-level app's Info.plist inside the ZIP
33-
# Use -maxdepth 3 to avoid nested framework plists (e.g. Sparkle.framework)
48+
# ---------------------------------------------------------------------------
49+
# 3. Extract version metadata from the built app
50+
# ---------------------------------------------------------------------------
3451
TEMP_DIR=$(mktemp -d)
3552
unzip -q "$ARM64_ZIP" -d "$TEMP_DIR"
3653
INFO_PLIST=$(find "$TEMP_DIR" -maxdepth 3 -path "*/Contents/Info.plist" | head -1)
@@ -40,14 +57,16 @@ if [ -n "$INFO_PLIST" ] && [ -f "$INFO_PLIST" ]; then
4057
SHORT_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO_PLIST" 2>/dev/null || echo "$VERSION")
4158
MIN_OS=$(/usr/libexec/PlistBuddy -c "Print :LSMinimumSystemVersion" "$INFO_PLIST" 2>/dev/null || echo "14.0")
4259
else
43-
echo "⚠️ Could not find app Info.plist in ZIP, using defaults from tag"
60+
echo "⚠️ Could not find app Info.plist in ZIP, using defaults"
4461
BUILD_NUMBER="1"
4562
SHORT_VERSION="$VERSION"
4663
MIN_OS="14.0"
4764
fi
4865
rm -rf "$TEMP_DIR"
4966

50-
# Extract release notes for appcast
67+
# ---------------------------------------------------------------------------
68+
# 4. Extract release notes from CHANGELOG.md → HTML
69+
# ---------------------------------------------------------------------------
5170
if [ -f release_notes.md ]; then
5271
NOTES=$(cat release_notes.md)
5372
else
@@ -57,10 +76,6 @@ fi
5776
if [ -z "$NOTES" ]; then
5877
RELEASE_HTML="<li>Bug fixes and improvements</li>"
5978
else
60-
# Convert markdown to simple HTML:
61-
# ### Header -> <h3>Header</h3>
62-
# - item -> <li>item</li>
63-
# Wrap consecutive <li> runs in <ul>...</ul>
6479
RELEASE_HTML=$(echo "$NOTES" | sed -E \
6580
-e 's/^### (.+)$/<h3>\1<\/h3>/' \
6681
-e 's/^- (.+)$/<li>\1<\/li>/' \
@@ -78,41 +93,31 @@ else
7893
')
7994
fi
8095

81-
# Wrap in a styled HTML body
8296
DESCRIPTION_HTML="<body style=\"font-family: -apple-system, sans-serif; font-size: 13px; padding: 8px;\">${RELEASE_HTML}</body>"
8397

84-
# Build appcast.xml with architecture-specific items (Sparkle 2 convention)
98+
# ---------------------------------------------------------------------------
99+
# 5. Build appcast.xml — merge new items into existing feed
100+
# ---------------------------------------------------------------------------
85101
DOWNLOAD_PREFIX="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY:-TableProApp/TablePro}/releases/download/v${VERSION}"
86102
PUB_DATE=$(date -u '+%a, %d %b %Y %H:%M:%S +0000')
103+
EXISTING_APPCAST="appcast.xml"
87104

88105
mkdir -p appcast
89-
cat > appcast/appcast.xml << APPCAST_EOF
90-
<?xml version="1.0" standalone="yes"?>
91-
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
92-
<channel>
93-
<title>TablePro</title>
94-
<item>
95-
<title>${SHORT_VERSION}</title>
96-
<pubDate>${PUB_DATE}</pubDate>
97-
<sparkle:version>${BUILD_NUMBER}</sparkle:version>
98-
<sparkle:shortVersionString>${SHORT_VERSION}</sparkle:shortVersionString>
99-
<sparkle:minimumSystemVersion>${MIN_OS}</sparkle:minimumSystemVersion>
100-
<sparkle:hardwareRequirements>arm64</sparkle:hardwareRequirements>
101-
<description><![CDATA[${DESCRIPTION_HTML}]]></description>
102-
<enclosure url="${DOWNLOAD_PREFIX}/TablePro-${VERSION}-arm64.zip" length="${ARM64_LENGTH}" type="application/octet-stream" sparkle:edSignature="${ARM64_ED_SIG}"/>
103-
</item>
104-
<item>
105-
<title>${SHORT_VERSION}</title>
106-
<pubDate>${PUB_DATE}</pubDate>
107-
<sparkle:version>${BUILD_NUMBER}</sparkle:version>
108-
<sparkle:shortVersionString>${SHORT_VERSION}</sparkle:shortVersionString>
109-
<sparkle:minimumSystemVersion>${MIN_OS}</sparkle:minimumSystemVersion>
110-
<description><![CDATA[${DESCRIPTION_HTML}]]></description>
111-
<enclosure url="${DOWNLOAD_PREFIX}/TablePro-${VERSION}-x86_64.zip" length="${X86_64_LENGTH}" type="application/octet-stream" sparkle:edSignature="${X86_64_ED_SIG}"/>
112-
</item>
113-
</channel>
114-
</rss>
115-
APPCAST_EOF
116-
117-
echo "✅ Appcast generated with architecture-specific items:"
106+
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"
121+
122+
echo "✅ Appcast generated:"
118123
cat appcast/appcast.xml

0 commit comments

Comments
 (0)