#!/usr/bin/env python3 """Monta o latest.json que o atualizador do app lê (Tauri updater), a partir dos pacotes e das assinaturas `.sig` já enviados à release no Gitea. Roda no job de publicação, depois que os jobs de empacotamento terminaram. Montar o arquivo aqui, em um passo só, evita a corrida de quem tenta atualizá-lo em paralelo por plataforma. Uso: make-latest-json.py Precisa de API (URL do repositório na API do Gitea) e TOKEN no ambiente. """ import json import os import sys import urllib.parse import urllib.request from datetime import datetime, timezone # (chaves de plataforma, padrão do nome do pacote). {v} = versão sem o "v". # Sem macOS: não há runner para compilar essa plataforma. PLATFORMS = [ (["linux-x86_64", "linux-x86_64-appimage"], ("Guará_{v}_amd64.AppImage", "Guara_{v}_amd64.AppImage")), (["linux-aarch64", "linux-aarch64-appimage"], ("Guará_{v}_aarch64.AppImage", "Guara_{v}_aarch64.AppImage")), (["windows-x86_64", "windows-x86_64-nsis"], ("Guará_{v}_x64-setup.exe", "Guara_{v}_x64-setup.exe")), ] def api_get(path): req = urllib.request.Request(f"{os.environ['API']}{path}", headers={"Authorization": f"token {os.environ['TOKEN']}"}) return json.load(urllib.request.urlopen(req)) def api_get_text(url): safe_url = urllib.parse.quote(url, safe=":/?&=%") req = urllib.request.Request(safe_url, headers={"Authorization": f"token {os.environ['TOKEN']}"}) return urllib.request.urlopen(req).read().decode() def build(version, notes, assets, pub_date): """`assets` = {nome_do_pacote: (url, assinatura)}. Devolve o dicionário do latest.json. Plataforma sem pacote ou sem assinatura sai da lista; o chamador decide se isso é erro.""" platforms = {} for keys, patterns in PLATFORMS: name = next((n for p in patterns if (n := p.format(v=version)) in assets), None) if name is None: continue url, sig = assets[name] for k in keys: platforms.setdefault(k, {"signature": sig.strip(), "url": url}) return {"version": version, "notes": notes.strip(), "pub_date": pub_date, "platforms": platforms} def main(): tag, notes_path, out = sys.argv[1:4] version = tag.lstrip("v") rel = api_get(f"/releases/tags/{tag}") by_name = {a["name"]: a for a in rel["assets"]} assets = {} for _, patterns in PLATFORMS: name = next((n for p in patterns if (n := p.format(v=version)) in by_name), None) if name is None: continue sig_name = name + ".sig" if sig_name in by_name: sig = api_get_text(by_name[sig_name]["browser_download_url"]) assets[name] = (by_name[name]["browser_download_url"], sig) data = build(version, open(notes_path, encoding="utf-8").read(), assets, datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")) # Exige os pacotes assinados de Linux (x86_64 e aarch64) e Windows. platforms = data["platforms"] for must in ("linux-x86_64", "linux-aarch64", "windows-x86_64"): if must not in platforms: sys.exit(f"faltam pacotes ou assinaturas de {must} (achei {sorted(platforms)}); " "confira se TAURI_SIGNING_PRIVATE_KEY está nos segredos") with open(out, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write("\n") print(f"{out}: {sorted(data['platforms'])}") if __name__ == "__main__": main()