Phase 0 scaffold plus everything since: quality negotiation, host-relay, and active drift correction were each built and reverted after real bugs in live use (see PLAN.md for the full account of each). What remains is read-only SyncPlay observability (live drift/playback stats page) and a one-shot manual "sync me to group" action, plus Phase 5 packaging/release tooling (build.yaml, manifest.json generator, Gitea Actions release workflow) to distribute it as a proper plugin repository. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
2.9 KiB
Python
Executable File
86 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Appends (or replaces) this build's version entry in manifest.json, using build.yaml
|
|
for the plugin-level metadata (guid, name, description, etc.) and changelog. Run by
|
|
dev/package-release.sh; not meant to be invoked directly outside that flow."""
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
import re
|
|
|
|
|
|
def parse_build_yaml(path):
|
|
# build.yaml is simple enough to not need a real YAML parser dependency.
|
|
text = open(path, encoding="utf-8").read()
|
|
fields = {}
|
|
for key in ("name", "guid", "targetAbi", "framework", "owner", "overview", "category"):
|
|
m = re.search(rf'^{key}:\s*"([^"]*)"', text, re.MULTILINE)
|
|
if m:
|
|
fields[key] = m.group(1)
|
|
|
|
def block(key):
|
|
m = re.search(rf'^{key}:\s*>\s*\n((?: .*\n?)+)', text, re.MULTILINE)
|
|
if not m:
|
|
return ""
|
|
lines = [line[2:].rstrip() for line in m.group(1).splitlines()]
|
|
return " ".join(line for line in lines if line).strip()
|
|
|
|
fields["description"] = block("description")
|
|
fields["changelog"] = block("changelog")
|
|
return fields
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--build-yaml", required=True)
|
|
parser.add_argument("--manifest", required=True)
|
|
parser.add_argument("--version", required=True)
|
|
parser.add_argument("--checksum", required=True)
|
|
parser.add_argument("--source-url", required=True)
|
|
args = parser.parse_args()
|
|
|
|
build = parse_build_yaml(args.build_yaml)
|
|
|
|
try:
|
|
with open(args.manifest, encoding="utf-8") as f:
|
|
manifest = json.load(f)
|
|
except FileNotFoundError:
|
|
manifest = []
|
|
|
|
entry = next((p for p in manifest if p.get("guid") == build["guid"]), None)
|
|
if entry is None:
|
|
entry = {
|
|
"guid": build["guid"],
|
|
"name": build["name"],
|
|
"description": build["description"],
|
|
"overview": build["overview"],
|
|
"owner": build["owner"],
|
|
"category": build["category"],
|
|
"versions": [],
|
|
}
|
|
manifest.append(entry)
|
|
else:
|
|
entry["description"] = build["description"]
|
|
entry["overview"] = build["overview"]
|
|
entry["owner"] = build["owner"]
|
|
entry["category"] = build["category"]
|
|
|
|
entry["versions"] = [v for v in entry["versions"] if v["version"] != args.version]
|
|
entry["versions"].insert(0, {
|
|
"version": args.version,
|
|
"changelog": build["changelog"],
|
|
"targetAbi": build["targetAbi"],
|
|
"sourceUrl": args.source_url,
|
|
"checksum": args.checksum,
|
|
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
})
|
|
|
|
with open(args.manifest, "w", encoding="utf-8") as f:
|
|
json.dump(manifest, f, indent=4)
|
|
f.write("\n")
|
|
|
|
print(f"Updated {args.manifest} with version {args.version}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|