22"""Update appcast.xml with a new release item."""
33import argparse
44import os
5+ import re
56import xml .etree .ElementTree as ET
67from 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+
821def 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
5379if __name__ == "__main__" :
5480 main ()
0 commit comments