Skip to content

Commit 9187ab5

Browse files
phpgaoclaude
andcommitted
fix: correct appcast signature parsing and build number generation
- Parse sign_update output format (sparkle:edSignature="..." length="...") to extract only the base64 signature, avoiding double-encoding in XML - Generate meaningful build numbers from version (1.0.2 -> 10002) instead of hardcoded "1", so Sparkle can properly compare versions - Reset appcast.xml for clean regeneration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 71984e9 commit 9187ab5

2 files changed

Lines changed: 38 additions & 20 deletions

File tree

appcast.xml

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
<?xml version='1.0' encoding='utf-8'?>
2-
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
3-
<channel>
4-
<title>BrowserRouter Updates</title>
5-
<language>en</language>
6-
<item>
7-
<title>Version 1.0.2</title>
8-
<pubDate>Fri, 20 Mar 2026 16:32:06 +0000</pubDate>
9-
<sparkle:version>1</sparkle:version>
10-
<sparkle:shortVersionString>1.0.2</sparkle:shortVersionString>
11-
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
12-
<enclosure url="https://github.com/phpgao/BrowserRouter/releases/download/v1.0.2/BrowserRouter.zip" type="application/octet-stream" sparkle:edSignature="sparkle:edSignature=&quot;NpfJ27DyoIFXwxl6XFQn1STlNDlgzdpiWMaSWaII5emKIzj8ZU52GrLCqmYHvc5bAGf64lRfRempQbsNJA6YBQ==&quot; length=&quot;2091780&quot;" length="2091780" />
13-
</item>
14-
</channel>
15-
</rss>
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
3+
<channel>
4+
<title>BrowserRouter Updates</title>
5+
<language>en</language>
6+
</channel>
7+
</rss>

scripts/update_appcast.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@
22
"""Update appcast.xml with a new release item."""
33
import argparse
44
import os
5+
import re
56
import xml.etree.ElementTree as ET
67
from datetime import datetime, timezone
78

9+
10+
def parse_signature(raw_signature):
11+
"""Parse sign_update output which may be in format:
12+
sparkle:edSignature="BASE64" length="12345"
13+
or just the raw base64 string."""
14+
match = re.search(r'edSignature="([^"]+)"', raw_signature)
15+
if match:
16+
return match.group(1)
17+
# Already a plain base64 string
18+
return raw_signature.strip()
19+
20+
821
def main():
922
parser = argparse.ArgumentParser(description="Update appcast.xml with a new version")
1023
parser.add_argument("--version", required=True, help="Marketing version (e.g. 1.0.2)")
11-
parser.add_argument("--build", default="", help="Build number")
24+
parser.add_argument("--build", default="", help="Build number (if empty, derived from version)")
1225
parser.add_argument("--signature", required=True, help="EdDSA signature from sign_update")
1326
parser.add_argument("--file", required=True, help="Path to the .zip file")
1427
parser.add_argument("--appcast", default="appcast.xml", help="Path to appcast.xml")
@@ -18,21 +31,33 @@ def main():
1831
pub_date = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")
1932
download_url = f"https://github.com/phpgao/BrowserRouter/releases/download/v{args.version}/BrowserRouter.zip"
2033

34+
# Parse the signature (sign_update may output key=value format)
35+
signature = parse_signature(args.signature)
36+
37+
# Build number: use provided value, or convert version like "1.0.2" -> "102"
38+
if args.build:
39+
build_number = args.build
40+
else:
41+
# Convert version string to a comparable integer: 1.0.2 -> 10002
42+
parts = args.version.split(".")
43+
parts = (parts + ["0", "0", "0"])[:3] # pad to 3 parts
44+
build_number = str(int(parts[0]) * 10000 + int(parts[1]) * 100 + int(parts[2]))
45+
2146
# Register namespaces to preserve them
2247
ET.register_namespace("sparkle", "http://www.andymatuschak.org/xml-namespaces/sparkle")
2348
ET.register_namespace("dc", "http://purl.org/dc/elements/1.1/")
2449

2550
tree = ET.parse(args.appcast)
2651
channel = tree.find("channel")
2752

28-
# Build item XML manually for better formatting
53+
# Build item XML
2954
item = ET.SubElement(channel, "item")
3055
ET.SubElement(item, "title").text = f"Version {args.version}"
3156
ET.SubElement(item, "pubDate").text = pub_date
3257

3358
sparkle_ns = "http://www.andymatuschak.org/xml-namespaces/sparkle"
3459
version_el = ET.SubElement(item, f"{{{sparkle_ns}}}version")
35-
version_el.text = args.build if args.build else "1"
60+
version_el.text = build_number
3661

3762
short_ver = ET.SubElement(item, f"{{{sparkle_ns}}}shortVersionString")
3863
short_ver.text = args.version
@@ -43,12 +68,13 @@ def main():
4368
enclosure = ET.SubElement(item, "enclosure")
4469
enclosure.set("url", download_url)
4570
enclosure.set("type", "application/octet-stream")
46-
enclosure.set(f"{{{sparkle_ns}}}edSignature", args.signature)
71+
enclosure.set(f"{{{sparkle_ns}}}edSignature", signature)
4772
enclosure.set("length", str(file_size))
4873

4974
ET.indent(tree, space=" ")
5075
tree.write(args.appcast, encoding="utf-8", xml_declaration=True)
51-
print(f"Updated {args.appcast} with version {args.version}")
76+
print(f"Updated {args.appcast} with version {args.version} (build {build_number})")
77+
5278

5379
if __name__ == "__main__":
5480
main()

0 commit comments

Comments
 (0)