scripts: gitea_issues.py drives the tea CLI; 38 decision issues opened on Gitea
ci / frontend (push) Successful in 43s
ci / server (push) Successful in 11m6s
ci / image (push) Successful in 14s
ci / e2e (push) Failing after 4m3s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-10-18 20:50:34 -06:00
co-authored by Claude Fable 5.1
parent 5ca83f48c3
commit 3623dca77b
+41 -41
View File
@@ -1,4 +1,4 @@
# ruff: noqa: E501 S310
# ruff: noqa: E501
"""Create one Gitea issue per open decision in docs/PLAN.md.
Usage (token from a file, never from the command line or chat):
@@ -12,13 +12,11 @@ from __future__ import annotations
import argparse
import json
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
import tempfile
from pathlib import Path
BASE = "https://gt.pescoinc.biz/api/v1"
REPO = "spencerm/PacketStation"
PLAN = "https://gt.pescoinc.biz/spencerm/PacketStation/src/branch/main/docs/PLAN.md"
@@ -293,22 +291,20 @@ ISSUES: list[tuple[str, str, list[str], str]] = [
]
def api(method: str, path: str, token: str, body: dict | None = None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, method=method)
req.add_header("Authorization", "token " + token)
req.add_header("Content-Type", "application/json")
req.add_header("Accept", "application/json")
try:
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read().decode() or "null")
except urllib.error.HTTPError as e:
raise SystemExit(f"{method} {path}: HTTP {e.code} {e.read().decode()[:300]}") from None
def tea(*args: str, output: str | None = "json") -> list | dict | str:
cmd = ["tea", *args, "--repo", REPO]
if output:
cmd += ["--output", output]
r = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
if r.returncode != 0:
raise SystemExit(f"tea {' '.join(args)} failed: {r.stderr.strip()[:400]}")
if output == "json":
return json.loads(r.stdout or "[]")
return r.stdout
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--token-file", required=True)
ap.add_argument("--dry-run", action="store_true")
a = ap.parse_args()
if a.dry_run:
@@ -317,42 +313,46 @@ def main() -> int:
print(f"{len(ISSUES)} issues")
return 0
token = Path(a.token_file).read_text(encoding="utf-8").strip()
if not token:
raise SystemExit("token file is empty")
existing = {lb["name"]: lb["id"] for lb in api("GET", f"/repos/{REPO}/labels?limit=100", token)}
existing = {lb["name"] for lb in tea("labels", "list")}
for name, color in LABELS.items():
if name not in existing:
lb = api("POST", f"/repos/{REPO}/labels", token, {"name": name, "color": "#" + color})
existing[name] = lb["id"]
tea("labels", "create", "--name", name, "--color", color, output=None)
print("label created:", name)
titles = set()
page = 1
while True:
batch = api("GET", f"/repos/{REPO}/issues?state=all&type=issues&limit=50&page={page}", token)
if not batch:
break
titles.update(i["title"] for i in batch)
page += 1
titles = {i["title"] for i in tea("issues", "list", "--state", "all", "--limit", "500")}
made = 0
for key, title, labels, body in ISSUES:
if title in titles:
print("exists:", title)
continue
full = (
body
+ f"\n\n---\nPlan reference: {key} in [docs/PLAN.md]({PLAN}) (open questions). Answer here; the build takes the default until then."
footer = (
"Plan reference: "
+ key
+ " in [docs/PLAN.md]("
+ PLAN
+ ") (open questions). Answer here; the build takes the default until then."
)
issue = api(
"POST",
f"/repos/{REPO}/issues",
token,
{"title": title, "body": full, "labels": [existing[lb] for lb in labels]},
full = body + "\n\n---\n" + footer + "\n"
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as f:
f.write(full)
path = f.name
try:
out = tea(
"issues",
"create",
"--title",
title,
"--labels",
",".join(labels),
"--description-file",
path,
output=None,
)
print(f"created #{issue['number']}: {title}")
finally:
Path(path).unlink(missing_ok=True)
first = (out or "").strip().splitlines()[0] if out else ""
print(f"created: {title} {first[:60]}")
made += 1
print(f"{made} created, {len(ISSUES) - made} already existed")
return 0