#!/usr/bin/env python3 """ fetch_prebuilt_frontend.py Downloads the latest prebuilt frontend artifact from the GitHub releases page and installs it into frontend/prebuilt/ so the backend can serve it directly. No GitHub CLI or authentication required — uses only the public releases API and browser_download_url. Requires only the Python standard library. """ import json import shutil import sys import urllib.request import zipfile from pathlib import Path REPO = "jkingsman/Remote-Terminal-for-MeshCore" API_URL = f"https://api.github.com/repos/{REPO}/releases/latest" PREBUILT_PREFIX = "Remote-Terminal-for-MeshCore/frontend/prebuilt/" SCRIPT_DIR = Path(__file__).resolve().parent PREBUILT_DIR = SCRIPT_DIR.parent / "frontend" / "prebuilt" def fetch_json(url: str) -> dict: req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json"}) with urllib.request.urlopen(req) as resp: return json.loads(resp.read()) def find_prebuilt_asset(release: dict) -> tuple[str, str, str]: """Return (tag_name, asset_name, download_url) for the prebuilt zip.""" tag = release.get("tag_name", "") for asset in release.get("assets", []): name = asset.get("name", "") if name.startswith("remoteterm-prebuilt-frontend-") and name.endswith(".zip"): return tag, name, asset["browser_download_url"] raise SystemExit( f"No prebuilt frontend artifact found in the latest release.\n" f"Check https://github.com/{REPO}/releases for available assets." ) def download(url: str, dest: Path) -> None: with urllib.request.urlopen(url) as resp, open(dest, "wb") as f: shutil.copyfileobj(resp, f) def extract_prebuilt(zip_path: Path, dest: Path) -> int: with zipfile.ZipFile(zip_path) as zf: members = [m for m in zf.namelist() if m.startswith(PREBUILT_PREFIX)] if not members: raise SystemExit(f"'{PREBUILT_PREFIX}' not found inside zip.") if dest.exists(): shutil.rmtree(dest) dest.mkdir(parents=True) for member in members: rel = member[len(PREBUILT_PREFIX):] if not rel: continue target = dest / rel if member.endswith("/"): target.mkdir(parents=True, exist_ok=True) else: target.parent.mkdir(parents=True, exist_ok=True) with zf.open(member) as src, open(target, "wb") as dst: shutil.copyfileobj(src, dst) return len(members) def main() -> None: print("Fetching latest release info...") release = fetch_json(API_URL) tag, asset_name, download_url = find_prebuilt_asset(release) print(f" Release : {tag}") print(f" Asset : {asset_name}") print() zip_path = PREBUILT_DIR.parent / asset_name try: print(f"Downloading {asset_name}...") download(download_url, zip_path) print("Extracting prebuilt frontend...") count = extract_prebuilt(zip_path, PREBUILT_DIR) print(f"Extracted {count} entries.") finally: zip_path.unlink(missing_ok=True) print() print(f"Done! Prebuilt frontend ({tag}) installed to frontend/prebuilt/") print("Start the server with:") print(" uv run uvicorn app.main:app --host 0.0.0.0 --port 8000") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\nAborted.", file=sys.stderr) sys.exit(1)