- Replace argparse/print/sys.exit with click commands and ClickException - Translate all user-facing messages via _() - Add friendly Oops! / Nice! prompts - Wrap HTTP errors in all scripts with user-friendly translated messages - Update all unit tests to use CliRunner and expect ClickException - Add 100% branch coverage for new HTTP error handling branches - Add missing translation keys to i18n.py - Fix pre-commit hook to use venv Python for validate_commit_msg.py
128 lines
3.2 KiB
Python
128 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Build package, optionally publish to PyPI, and create Gitea release.
|
|
|
|
Usage:
|
|
GITEA_TOKEN=<token> [PYPI_TOKEN=<token>] python3 scripts/publish.py <tag> <repo>
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
import click
|
|
import requests
|
|
|
|
from gitea_runner_manager.i18n import _
|
|
|
|
GITEA_API = "https://git.oblachno.oblachno.fyi/api/v1"
|
|
|
|
|
|
def build_package() -> None:
|
|
"""Build the Python package using python -m build."""
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "build"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode != 0:
|
|
raise click.ClickException(
|
|
_(
|
|
"Oops! Package build failed:\n{stderr}",
|
|
stderr=result.stderr.strip(),
|
|
)
|
|
)
|
|
|
|
|
|
def publish_to_pypi(token: str) -> None:
|
|
"""Publish built packages to PyPI using twine."""
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"twine",
|
|
"upload",
|
|
"dist/*",
|
|
"-u",
|
|
"__token__",
|
|
"-p",
|
|
token,
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode != 0:
|
|
raise click.ClickException(
|
|
_(
|
|
"Oops! PyPI publish failed:\n{stderr}",
|
|
stderr=result.stderr.strip(),
|
|
)
|
|
)
|
|
click.echo(_("Published to PyPI."))
|
|
|
|
|
|
def create_gitea_release(token: str, repo: str, tag: str) -> None:
|
|
"""Create a Gitea release for the given tag."""
|
|
url = f"{GITEA_API}/repos/{repo}/releases"
|
|
headers = {
|
|
"Authorization": f"token {token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload = {
|
|
"tag_name": tag,
|
|
"name": tag,
|
|
"body": f"Release {tag}\n\nSee CHANGELOG.md for details.",
|
|
"draft": False,
|
|
"prerelease": False,
|
|
}
|
|
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
|
response.raise_for_status()
|
|
|
|
|
|
@click.command()
|
|
@click.argument("tag")
|
|
@click.argument("repo")
|
|
def main(tag: str, repo: str) -> None:
|
|
gitea_token = os.environ.get("GITEA_TOKEN", "")
|
|
if not gitea_token:
|
|
raise click.ClickException(_("ERROR: GITEA_TOKEN is not set."))
|
|
|
|
pypi_token = os.environ.get("PYPI_TOKEN", "")
|
|
|
|
build_package()
|
|
|
|
if pypi_token:
|
|
publish_to_pypi(pypi_token)
|
|
else:
|
|
click.echo(_("PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release."))
|
|
|
|
try:
|
|
create_gitea_release(gitea_token, repo, tag)
|
|
except requests.HTTPError as e:
|
|
response = e.response
|
|
status = response.status_code if response else 0
|
|
try:
|
|
body = response.json() if response else {}
|
|
message = body.get("message", str(e))
|
|
except Exception:
|
|
message = str(e)
|
|
raise click.ClickException(
|
|
_(
|
|
"Release creation failed with HTTP {status}: {message}",
|
|
status=status,
|
|
message=message,
|
|
)
|
|
) from None
|
|
|
|
click.echo(
|
|
_(
|
|
"Nice! Gitea release {tag} created.",
|
|
tag=tag,
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main()
|