141 lines
3.7 KiB
Python
141 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Build package, optionally publish to PyPI, and create Gitea release.
|
|
|
|
Uses git-cliff to generate the release notes from conventional commits.
|
|
|
|
Usage:
|
|
REPO_TOKEN=<token> [PYPI_TOKEN=<token>] python3 scripts/publish.py <tag> <repo>
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess # nosec B404
|
|
import sys
|
|
|
|
import click
|
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
|
|
|
from gitea_runner_manager.api_clients import GiteaClient
|
|
from gitea_runner_manager.config import GITEA_API_URL
|
|
from gitea_runner_manager.exceptions import APIError
|
|
from gitea_runner_manager.i18n import _
|
|
|
|
load_dotenv(override=True)
|
|
|
|
CLIFF_CONFIG = "cliff.toml"
|
|
|
|
|
|
def generate_release_notes(tag: str) -> str:
|
|
"""Generate release notes for the given tag using git-cliff.
|
|
|
|
Falls back to a generic message if git-cliff is not available.
|
|
"""
|
|
cliff_bin = shutil.which("git-cliff")
|
|
if not cliff_bin:
|
|
return f"Release {tag}\n\nSee CHANGELOG.md for details."
|
|
try:
|
|
result = subprocess.run( # nosec B603
|
|
[cliff_bin, "--config", CLIFF_CONFIG, "--latest", "--strip", "header"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
return result.stdout.strip()
|
|
except FileNotFoundError:
|
|
pass
|
|
return f"Release {tag}\n\nSee CHANGELOG.md for details."
|
|
|
|
|
|
def build_package() -> None:
|
|
"""Build the Python package using python -m build."""
|
|
result = subprocess.run( # nosec B603
|
|
[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( # nosec B603
|
|
[
|
|
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."))
|
|
|
|
|
|
@click.command()
|
|
@click.argument("tag")
|
|
@click.argument("repo")
|
|
def main(tag: str, repo: str) -> None:
|
|
gitea_token = os.environ.get("REPO_TOKEN", "")
|
|
if not gitea_token:
|
|
raise click.ClickException(_("ERROR: REPO_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."))
|
|
|
|
owner, repo_name = repo.split("/")
|
|
client = GiteaClient(GITEA_API_URL, gitea_token, owner, repo_name)
|
|
|
|
release_body = generate_release_notes(tag)
|
|
|
|
try:
|
|
client.create_release(
|
|
tag=tag,
|
|
body=release_body,
|
|
)
|
|
except APIError as e:
|
|
raise click.ClickException(
|
|
_(
|
|
"Release creation failed with HTTP {status}: {message}",
|
|
status=e.status,
|
|
message=e.message,
|
|
)
|
|
) from None
|
|
|
|
click.echo(
|
|
_(
|
|
"Nice! Gitea release {tag} created.",
|
|
tag=tag,
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main()
|