Add --publish flag to release.py for automatic Gitea releases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chrome-storm-c442
2026-02-23 14:47:53 -05:00
parent e148e8e2cd
commit 38d2387cd5

View File

@@ -11,10 +11,13 @@ Usage:
""" """
import argparse import argparse
import json
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import urllib.request
import urllib.error
from datetime import date from datetime import date
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -253,8 +256,120 @@ def main() -> None:
print("Next steps:") print("Next steps:")
print(" 1. Review changes: git diff") print(" 1. Review changes: git diff")
print(" 2. Build: python build.py --clean") print(" 2. Build: python build.py --clean")
print(" 3. Commit & push") print(" 3. Commit, tag & push")
print(f" 4. python release.py --publish {new_version}")
# ---------------------------------------------------------------------------
# Gitea release publishing
# ---------------------------------------------------------------------------
def _gitea_api(endpoint: str, method: str = "GET", data: dict | None = None,
binary_path: str | None = None) -> dict | None:
"""Call Gitea API. Reads credentials from git remote 'sensey'."""
import base64
try:
result = subprocess.run(
["git", "remote", "get-url", "sensey"],
capture_output=True, text=True, cwd=PROJECT_DIR,
)
remote_url = result.stdout.strip()
except Exception:
print("ERROR: cannot read 'sensey' remote")
return None
m = re.match(r"https://([^:]+):([^@]+)@([^/]+)/(.+?)(?:\.git)?$", remote_url)
if not m:
print(f"ERROR: cannot parse remote URL: {remote_url}")
return None
user, password, host, repo_path = m.groups()
base = f"https://{host}/api/v1/repos/{repo_path}"
url = f"{base}/{endpoint}" if endpoint else base
headers = {
"Authorization": "Basic " + base64.b64encode(
f"{user}:{password}".encode()
).decode(),
}
if binary_path:
headers["Content-Type"] = "application/octet-stream"
with open(binary_path, "rb") as f:
body = f.read()
elif data is not None:
headers["Content-Type"] = "application/json"
body = json.dumps(data).encode()
else:
body = None
req = urllib.request.Request(url, data=body, headers=headers, method=method)
try:
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
print(f"Gitea API error {e.code}: {e.read().decode()}")
return None
def publish(version: str):
"""Create a Gitea release and upload binaries from releases/ folder."""
tag = f"v{version}" if not version.startswith("v") else version
ver = tag.lstrip("v")
print(f"\nPublishing release {tag} to Gitea...")
# Read changelog section for this version
changelog_path = os.path.join(PROJECT_DIR, "CHANGELOG.md")
body = ""
if os.path.exists(changelog_path):
text = read_file(changelog_path)
pattern = rf"(## \[{re.escape(ver)}\].*?)(?=\n## \[|\Z)"
m = re.search(pattern, text, re.DOTALL)
if m:
body = m.group(1).strip()
# Create release
release = _gitea_api("releases", "POST", {
"tag_name": tag,
"name": tag,
"body": body or f"Release {tag}",
"draft": False,
"prerelease": False,
})
if not release:
print("ERROR: failed to create release")
sys.exit(1)
release_id = release["id"]
print(f" Release created: {release['html_url']}")
# Upload matching binaries
releases_dir = os.path.join(PROJECT_DIR, "releases")
if os.path.isdir(releases_dir):
for fname in sorted(os.listdir(releases_dir)):
if f"-v{ver}-" in fname:
fpath = os.path.join(releases_dir, fname)
print(f" Uploading {fname}...", end=" ")
asset = _gitea_api(
f"releases/{release_id}/assets?name={fname}",
"POST", binary_path=fpath,
)
if asset:
size_mb = asset.get("size", 0) / (1024 * 1024)
print(f"OK ({size_mb:.1f} MB)")
else:
print("FAILED")
print(f"\nRelease {tag} published!")
if __name__ == "__main__": if __name__ == "__main__":
if "--publish" in sys.argv:
idx = sys.argv.index("--publish")
if idx + 1 < len(sys.argv):
publish(sys.argv[idx + 1])
else:
publish(get_current_version())
sys.exit(0)
main() main()