#!/usr/bin/env bash set -euo pipefail # Generate a local appcast that reuses the latest Sparkle entry but bumps the # sparkle:version so Sparkle will reinstall the same build. Useful for testing # updates without cutting a new release. ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" INPUT="${1:-$ROOT_DIR/Sparkle/appcast.xml}" OUTPUT="${2:-$ROOT_DIR/Sparkle/appcast-local.xml}" python3 - "$INPUT" "$OUTPUT" <<'PY' import sys, xml.etree.ElementTree as ET, datetime, email.utils if len(sys.argv) != 3: sys.exit("Usage: make_local_appcast.sh [input] [output]") src, dst = sys.argv[1], sys.argv[2] tree = ET.parse(src) root = tree.getroot() channel = root.find("channel") items = channel.findall("item") if channel is not None else [] if not items: sys.exit("No items found in appcast") latest = items[0] enc = latest.find("enclosure") if enc is None: sys.exit("Latest item missing enclosure") sparkle_ns = "{http://www.andymatuschak.org/xml-namespaces/sparkle}" def get_text(tag): el = latest.find(tag) return el.text if el is not None else "" short_version = get_text(f"{sparkle_ns}shortVersionString") version_el = latest.find(f"{sparkle_ns}version") try: next_build = str(int(version_el.text) + 1000 if version_el is not None else 9999) except ValueError: next_build = "9999" now_rfc822 = email.utils.format_datetime(datetime.datetime.now(datetime.timezone.utc)) new_item = ET.Element("item") ET.SubElement(new_item, "title").text = f"{short_version} (reinstall)" ET.SubElement(new_item, "pubDate").text = now_rfc822 sv = ET.SubElement(new_item, f"{sparkle_ns}shortVersionString") sv.text = short_version bv = ET.SubElement(new_item, f"{sparkle_ns}version") bv.text = next_build min_os = latest.find(f"{sparkle_ns}minimumSystemVersion") if min_os is not None and min_os.text: ET.SubElement(new_item, f"{sparkle_ns}minimumSystemVersion").text = min_os.text new_enc = ET.SubElement(new_item, "enclosure") for attr in ("url", "length", "type", f"{sparkle_ns}edSignature"): if attr in enc.attrib: new_enc.set(attr, enc.attrib[attr]) new_channel = ET.Element("channel") title = channel.find("title") ET.SubElement(new_channel, "title").text = title.text if title is not None else "Local" new_channel.append(new_item) new_root = ET.Element("rss", { "version": "2.0", "xmlns:sparkle": "http://www.andymatuschak.org/xml-namespaces/sparkle" }) new_root.append(new_channel) ET.indent(new_root, space=" ") ET.ElementTree(new_root).write(dst, encoding="utf-8", xml_declaration=True) print(f"✅ wrote {dst}") PY