GRM-54: Integrate tea Gitea CLI for API interactions (#68)

This commit is contained in:
2026-06-22 07:04:58 +00:00
parent b8ab4f854b
commit 28e61aa166
17 changed files with 1319 additions and 265 deletions
+57 -17
View File
@@ -2,7 +2,9 @@
"""Create a Gitea issue when a CI workflow fails.
Used by the release and publish workflows to alert on failures that would
otherwise go unnoticed in the Actions tab.
otherwise go unnoticed in the Actions tab. Uses the ``tea`` Gitea CLI
for issue creation when available, falling back to ``GiteaClient`` (direct
HTTP API) when tea is not installed.
Usage:
REPO_TOKEN=<token> python3 scripts/notify_failure.py \
@@ -14,7 +16,9 @@ Usage:
from __future__ import annotations
import contextlib
import os
import shutil
import click
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
@@ -27,6 +31,48 @@ from gitea_runner_manager.i18n import _
load_dotenv(override=True)
def _create_issue_via_tea(repo: str, title: str, body: str) -> int | None:
"""Try creating issue via tea CLI. Returns issue index or None on failure."""
if shutil.which("tea") is None:
return None
from scripts.gitea_cli import TeaCLI, TeaCLIError
tea = TeaCLI(repo=repo)
try:
# Check if "bug" label exists
labels: list[str] = []
try:
existing_labels = tea.list_labels(repo)
if any(label.get("name") == "bug" for label in existing_labels):
labels = ["bug"]
except TeaCLIError:
pass
issue = tea.create_issue(repo, title=title, body=body, labels=labels if labels else None)
if labels:
with contextlib.suppress(TeaCLIError):
tea.add_label(repo, issue["index"], labels)
return int(issue.get("index", 0))
except TeaCLIError:
return None
def _create_issue_via_client(repo: str, title: str, body: str) -> int:
"""Create issue via GiteaClient (direct HTTP API). Returns issue ID."""
token = os.environ.get("REPO_TOKEN", "")
owner, repo_name = repo.split("/")
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
# Look up label IDs by name (Gitea API expects integer IDs, not strings)
label_ids: list[int] = []
for label in client.list_labels():
if label.get("name") == "bug":
label_ids.append(int(label["id"]))
break
issue = client.create_issue(title=title, body=body, labels=label_ids if label_ids else None)
return int(issue.get("id", 0))
@click.command()
@click.option("--repo", required=True, help="Repository in owner/name format.")
@click.option("--run-id", required=True, help="CI run ID.")
@@ -37,9 +83,6 @@ def main(repo: str, run_id: str, workflow: str, commit: str) -> None:
if not token:
raise click.ClickException(_("ERROR: REPO_TOKEN is not set."))
owner, repo_name = repo.split("/")
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
title = f"[CI] {workflow} workflow failed (run #{run_id})"
body = (
f"The **{workflow}** workflow failed.\n\n"
@@ -50,23 +93,20 @@ def main(repo: str, run_id: str, workflow: str, commit: str) -> None:
f"Please investigate and fix the issue."
)
try:
# Look up label IDs by name (Gitea API expects integer IDs, not strings)
label_ids: list[int] = []
for label in client.list_labels():
if label.get("name") == "bug":
label_ids.append(int(label["id"]))
break
issue = client.create_issue(title=title, body=body, labels=label_ids if label_ids else None)
except APIError as e:
raise click.ClickException(
_("Failed to create issue: HTTP {status}{message}", status=e.status, message=e.message)
) from None
# Try tea CLI first, fall back to GiteaClient
issue_id = _create_issue_via_tea(repo, title, body)
if issue_id is None:
try:
issue_id = _create_issue_via_client(repo, title, body)
except APIError as e:
raise click.ClickException(
_("Failed to create issue: HTTP {status}{message}", status=e.status, message=e.message)
) from None
click.echo(
_(
"Created issue #{issue_id}: {title}",
issue_id=issue.get("id", "?"),
issue_id=issue_id or "?",
title=title,
)
)
+6 -18
View File
@@ -2,6 +2,7 @@
"""Build package, optionally publish to PyPI, and create Gitea release.
Uses git-cliff to generate the release notes from conventional commits.
Uses the ``tea`` Gitea CLI for release creation.
Usage:
REPO_TOKEN=<token> [PYPI_TOKEN=<token>] python3 scripts/publish.py <tag> <repo>
@@ -15,10 +16,8 @@ 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 _
from scripts.gitea_cli import TeaCLI, TeaCLIError
load_dotenv(override=True)
@@ -109,24 +108,13 @@ def main(tag: str, repo: str) -> None:
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)
tea = TeaCLI(repo=repo)
release_body = generate_release_notes(tag)
try:
client.create_release_idempotent(
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
tea.create_release(repo, tag=tag, title=tag, body=release_body)
except TeaCLIError as e:
raise click.ClickException(_("Release creation failed: {error}", error=str(e))) from None
click.echo(
_(
+9 -20
View File
@@ -3,9 +3,9 @@
Used by the GRM workflow to post structured PR reviews. The review body
is provided via --body and inline comments via a JSON file
(--comments-json) or stdin (--comments-stdin). This script is a thin
CLI wrapper around ``GiteaClient.create_review`` — the actual review
analysis is performed by the agent before invoking this tool.
(--comments-json) or stdin (--comments-stdin). This script uses the
``tea`` Gitea CLI for posting the review — the actual review analysis is
performed by the agent before invoking this tool.
Usage:
REPO_TOKEN=<token> python3 scripts/review_pr.py <pr_number> <repo> \
@@ -36,10 +36,8 @@ from typing import Any
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 _
from scripts.gitea_cli import TeaCLI, TeaCLIError
load_dotenv(override=True)
@@ -107,8 +105,7 @@ def main(
if not token:
raise click.ClickException(_("ERROR: REPO_TOKEN is not set."))
owner, repo_name = repo.split("/")
client = GiteaClient(GITEA_API_URL, token, owner, repo_name)
tea = TeaCLI(repo=repo)
comments = parse_comments(comments_json, comments_stdin)
@@ -132,21 +129,13 @@ def main(
)
try:
review = client.create_review(pr_number, event=event, body=body, comments=comments)
except APIError as e:
raise click.ClickException(
_(
"Failed to post review: HTTP {status}{message}",
status=e.status,
message=e.message,
)
) from None
tea.review_pr(repo, int(pr_number), event=event, body=body)
except TeaCLIError as e:
raise click.ClickException(_("Failed to post review: {error}", error=str(e))) from None
review_id = review.get("id", "?")
click.echo(
_(
"Review #{review_id} posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).",
review_id=review_id,
"Review posted on PR #{pr_number} with event '{event}' ({num_comments} inline comments).",
pr_number=pr_number,
event=event,
num_comments=len(comments),