Sparkle fixes
This commit is contained in:
14
Sparkle/appcast-local.xml
vendored
Normal file
14
Sparkle/appcast-local.xml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<rss xmlns:ns0="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
|
||||
<channel>
|
||||
<title>iKeyMon</title>
|
||||
<item>
|
||||
<title>26.0.37 (reinstall)</title>
|
||||
<pubDate>Mon, 08 Dec 2025 18:17:55 +0000</pubDate>
|
||||
<ns0:shortVersionString>26.0.37</ns0:shortVersionString>
|
||||
<ns0:version>1077</ns0:version>
|
||||
<ns0:minimumSystemVersion>15.2</ns0:minimumSystemVersion>
|
||||
<enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/v26.0.37/iKeyMon-26.0.37.zip" length="4813994" type="application/octet-stream" ns0:edSignature="C65NrwvB2vwPx9qh/Tu1Xqbfljk1zKaKHVRedOX0nINbhKMbOHerY28HxX0veybUWSFEOu6RGi2yDXumQQVMBA==" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
75
scripts/make_local_appcast.sh
Executable file
75
scripts/make_local_appcast.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/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
|
||||
25
scripts/serve_local_appcast.sh
Executable file
25
scripts/serve_local_appcast.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Generate a local appcast and serve it over HTTP for Sparkle testing without
|
||||
# publishing a new release. Sparkle disallows file:// feeds, so we use
|
||||
# http://localhost:${PORT}/appcast-local.xml.
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PORT="${PORT:-8000}"
|
||||
WORKDIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORKDIR"' EXIT
|
||||
|
||||
"$ROOT_DIR/scripts/make_local_appcast.sh" "${1:-$ROOT_DIR/Sparkle/appcast.xml}" "$WORKDIR/appcast-local.xml"
|
||||
|
||||
echo "🍏 Serving local appcast on http://127.0.0.1:${PORT}/appcast-local.xml"
|
||||
echo "Set SUFeedURL to that URL, then launch iKeyMon and check for updates."
|
||||
echo
|
||||
echo "To set it:"
|
||||
echo " defaults write net.24unix.iKeyMon SUFeedURL \"http://127.0.0.1:${PORT}/appcast-local.xml\""
|
||||
echo " killall iKeyMon 2>/dev/null; open /Applications/iKeyMon.app"
|
||||
echo
|
||||
echo "Press Ctrl+C to stop."
|
||||
|
||||
cd "$WORKDIR"
|
||||
python3 -m http.server "$PORT"
|
||||
Reference in New Issue
Block a user