scripts/gitea-release.py : releases automatisées (corps = section CHANGELOG, APK en pièce jointe)
- 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 »
This commit is contained in:
parent
cb4f5f02c0
commit
65a17ba2b9
@ -611,8 +611,11 @@ adb install -r app/build/outputs/apk/debug/app-debug.apk
|
|||||||
|
|
||||||
### 16.bis Releases Gitea avec APK téléchargeable
|
### 16.bis Releases Gitea avec APK téléchargeable
|
||||||
|
|
||||||
Les releases Gitea embarquent l'APK de chaque version — les utilisateurs n'ont
|
Les releases Gitea embarquent l'APK de chaque version — les utilisateurs n'ont pas
|
||||||
pas besoin de compiler :
|
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
|
```bash
|
||||||
# 1. Construire l'APK au niveau du tag
|
# 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
|
cp app/build/outputs/apk/debug/app-debug.apk /tmp/apks/HormoneTrack-vX.Y.Z.apk
|
||||||
git checkout main
|
git checkout main
|
||||||
|
|
||||||
# 2. Créer la release (API, token trousseau à write:repository)
|
# 2. Créer (ou mettre à jour) la release : corps = CHANGELOG + APK attaché
|
||||||
TOKEN=$(security find-internet-password -s gitea.cloudyfy.fr -w)
|
python3 scripts/gitea-release.py vX.Y.Z /tmp/apks/HormoneTrack-vX.Y.Z.apk
|
||||||
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":"<extrait du CHANGELOG>","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"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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 :
|
Notes :
|
||||||
- L'APK est une **build debug** signée avec la clé debug locale — installable en
|
- L'APK est une **build debug** signée avec la clé debug locale — installable en
|
||||||
sideload, mises à jour entre versions OK (même signature) ;
|
sideload, mises à jour entre versions OK (même signature) ;
|
||||||
|
|||||||
119
scripts/gitea-release.py
Executable file
119
scripts/gitea-release.py
Executable file
@ -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()
|
||||||
Loading…
x
Reference in New Issue
Block a user