From 65a17ba2b926c004c176762bff970b4b44a3a678 Mon Sep 17 00:00:00 2001 From: Siphonight Date: Sat, 5 Sep 2026 19:46:10 +0200 Subject: [PATCH] =?UTF-8?q?scripts/gitea-release.py=20:=20releases=20autom?= =?UTF-8?q?atis=C3=A9es=20(corps=20=3D=20section=20CHANGELOG,=20APK=20en?= =?UTF-8?q?=20pi=C3=A8ce=20jointe)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - le corps de chaque release Gitea est extrait de docs/CHANGELOG.md (la section [X.Y.Z] de la version) — les 5 releases existantes ont été mises à jour (le 1er script cherchait [vX.Y.Z] et était tombé sur le fallback) - crée ou met à jour la release (PATCH), remplace l'asset APK du même nom - token Gitea lu dans le trousseau macOS, scope write:repository suffisant - §16.bis documente le script + le piège du préfixe « v » --- docs/DEVELOPPEMENT.md | 32 ++++++----- scripts/gitea-release.py | 119 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 14 deletions(-) create mode 100755 scripts/gitea-release.py diff --git a/docs/DEVELOPPEMENT.md b/docs/DEVELOPPEMENT.md index 1805a84..85594f9 100644 --- a/docs/DEVELOPPEMENT.md +++ b/docs/DEVELOPPEMENT.md @@ -611,8 +611,11 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk ### 16.bis Releases Gitea avec APK téléchargeable -Les releases Gitea embarquent l'APK de chaque version — les utilisateurs n'ont -pas besoin de compiler : +Les releases Gitea embarquent l'APK de chaque version — les utilisateurs n'ont pas +besoin de compiler. **Le corps de chaque release = la section CHANGELOG de la +version** (markdown rendu par Gitea), extrait automatiquement. + +Le tout est automatisé par `scripts/gitea-release.py` : ```bash # 1. Construire l'APK au niveau du tag @@ -620,20 +623,21 @@ git checkout vX.Y.Z && ./gradlew assembleDebug cp app/build/outputs/apk/debug/app-debug.apk /tmp/apks/HormoneTrack-vX.Y.Z.apk git checkout main -# 2. Créer la release (API, token trousseau à write:repository) -TOKEN=$(security find-internet-password -s gitea.cloudyfy.fr -w) -curl -s -X POST "https://gitea.cloudyfy.fr/api/v1/repos/Siphonight/HormoneTrack/releases" \ - -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ - -d '{"tag_name":"vX.Y.Z","name":"vX.Y.Z","body":"","draft":false,"prerelease":false}' - -# 3. Uploader l'APK en pièce jointe -RELEASE_ID=$(curl -s "https://gitea.cloudyfy.fr/api/v1/repos/Siphonight/HormoneTrack/releases/tags/vX.Y.Z" \ - -H "Authorization: token $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])") -curl -s -X POST \ - "https://gitea.cloudyfy.fr/api/v1/repos/Siphonight/HormoneTrack/releases/$RELEASE_ID/assets?name=HormoneTrack-vX.Y.Z.apk" \ - -H "Authorization: token $TOKEN" -F "attachment=@/tmp/apks/HormoneTrack-vX.Y.Z.apk" +# 2. Créer (ou mettre à jour) la release : corps = CHANGELOG + APK attaché +python3 scripts/gitea-release.py vX.Y.Z /tmp/apks/HormoneTrack-vX.Y.Z.apk ``` +Le script : +- extrait la section `## [X.Y.Z]` de `docs/CHANGELOG.md` comme corps ; + ⚠️ piège : le tag git porte le « v » (`v1.2.3`) mais la CHANGELOG non + (`[1.2.3]`) — première version du script cherchait `[vX.Y.Z]` et tombait sur + le fallback « Voir docs/CHANGELOG.md » ; +- crée la release si absente, sinon **met à jour le corps** (PATCH) ; +- attache l'APK (remplace l'asset du même nom si présent) ; +- lit le token Gitea dans le trousseau macOS (`security find-internet-password`) ; + scope requis : `write:repository` (suffit pour releases + assets, pas pour + créer un repo — cf plus haut). + Notes : - L'APK est une **build debug** signée avec la clé debug locale — installable en sideload, mises à jour entre versions OK (même signature) ; diff --git a/scripts/gitea-release.py b/scripts/gitea-release.py new file mode 100755 index 0000000..f5be064 --- /dev/null +++ b/scripts/gitea-release.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""Publie/met à jour une release Gitea : corps = section CHANGELOG de la version, +APK en pièce jointe (optionnel). Réutilisable à chaque release. + +Usage : + python3 scripts/gitea-release.py v1.2.3 [chemin/vers/app.apk] + +- le corps est extrait de docs/CHANGELOG.md (section « ## [X.Y.Z] », SANS « v » + — le tag git porte le « v », la CHANGELOG non : piège corrigé, cf §16.bis) ; +- si la release existe déjà, son corps est mis à jour (PATCH) ; +- si un chemin d'APK est fourni, il est attaché (l'asset existant du même nom + est remplacé). + +Auth : le token Gitea est lu dans le trousseau macOS +(security find-internet-password -s gitea.cloudyfy.fr -w) — scope requis : +write:repository (suffit pour releases + assets, pas pour créer un repo). +""" +import json +import re +import subprocess +import sys +import urllib.error +import urllib.request + +GITEA = "https://gitea.cloudyfy.fr" +OWNER, REPO = "Siphonight", "HormoneTrack" +CHANGELOG = "docs/CHANGELOG.md" + + +def gitea_token() -> str: + out = subprocess.run( + ["security", "find-internet-password", "-s", "gitea.cloudyfy.fr", "-w"], + capture_output=True, text=True + ).stdout.strip() + if not out: + sys.exit("Token Gitea introuvable dans le trousseau macOS") + return out + + +def request(url: str, method: str = "GET", data=None, token: str = "", files=None): + req = urllib.request.Request(url, method=method) + req.add_header("Authorization", f"token {token}") + body = None + if files is not None: + boundary = "----HormoneTrackBoundary" + body = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; " + f"filename=\"{files['name']}\"\r\n" + f"Content-Type: application/vnd.android.package-archive\r\n\r\n").encode() \ + + files["data"] + f"\r\n--{boundary}--\r\n".encode() + req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}") + elif data is not None: + req.add_header("Content-Type", "application/json") + body = json.dumps(data).encode() + if body is not None: + req.data = body + with urllib.request.urlopen(req) as resp: + return json.loads(resp.read().decode()) + + +def changelog_section(version: str) -> str: + """Extrait la section « ## [X.Y.Z] » de la CHANGELOG (version SANS le « v»).""" + num = version.removeprefix("v") + text = open(CHANGELOG, encoding="utf-8").read() + m = re.search(rf"## \[{re.escape(num)}\][^\n]*\n(.*?)(?=\n## \[|\Z)", text, re.S) + if not m: + sys.exit(f"Section [{num}] introuvable dans {CHANGELOG}") + return m.group(1).strip() + + +def main(): + tag = sys.argv[1] if len(sys.argv) > 1 else sys.exit("Usage: gitea-release.py vX.Y.Z [app.apk]") + apk_path = sys.argv[2] if len(sys.argv) > 2 else None + token = gitea_token() + base = f"{GITEA}/api/v1/repos/{OWNER}/{REPO}" + body = changelog_section(tag) + + # Récupère la release du tag (crée-la si absente) + try: + release = request(f"{base}/releases/tags/{tag}", token=token) + release_id = release["id"] + created = False + except urllib.error.HTTPError as e: + if e.code != 404: + raise + release = request(f"{base}/releases", "POST", token=token, data={ + "tag_name": tag, "name": tag, "body": body, + "draft": False, "prerelease": False, + }) + release_id = release["id"] + created = True + + # Corps = section CHANGELOG (PATCH si la release existait déjà) + if not created: + request(f"{base}/releases/{release_id}", "PATCH", token=token, + data={"tag_name": tag, "name": tag, "body": body, "draft": False, "prerelease": False}) + + # APK en pièce jointe (remplace l'asset existant du même nom) + if apk_path: + asset_name = f"HormoneTrack-{tag}.apk" + for asset in release.get("assets", []): + if asset["name"] == asset_name: + request(f"{base}/releases/{release_id}/assets/{asset['id']}", + "DELETE", token=token) + with open(apk_path, "rb") as f: + asset = request( + f"{base}/releases/{release_id}/assets?name={asset_name}", + "POST", token=token, + files={"name": asset_name, "data": f.read()}, + ) + link = asset.get("browser_download_url", "?") + else: + link = "—" + + action = "créée" if created else "mise à jour" + print(f"Release {tag} {action} (id {release_id}) — corps {len(body)} caractères, APK : {link}") + + +if __name__ == "__main__": + main()