ci: add automated CI/release workflows
This commit is contained in:
parent
11156be20e
commit
9558e9ad64
29
.gitea/workflows/ci.yml
Normal file
29
.gitea/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with: { python-version: "3.11" }
|
||||||
|
- run: pip install -r requirements.txt
|
||||||
|
- run: python3 -m pytest tests/ -q
|
||||||
|
|
||||||
|
build-frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Pack frontend
|
||||||
|
run: tar -czf frontend.tar.gz frontend
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with: { name: frontend, path: frontend.tar.gz }
|
||||||
35
.gitea/workflows/release.yml
Normal file
35
.gitea/workflows/release.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tag-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with: { fetch-depth: 0 }
|
||||||
|
- name: Auto-tag
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
TAG="0.0.${{ github.run_number }}"
|
||||||
|
git tag "$TAG" || true
|
||||||
|
git push origin "$TAG" || true
|
||||||
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
draft-rc:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: tag-release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with: { fetch-depth: 0 }
|
||||||
|
- name: Draft release
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
TAG="${{ needs.tag-release.outputs.tag }}"
|
||||||
|
gh release create "$TAG" --draft --title "RC $TAG" --notes "Automated release candidate for $TAG." || true
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
104
scripts/gitea_sync.py
Executable file
104
scripts/gitea_sync.py
Executable file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Sync Hermes kanban board state to Gitea issue labels."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
GITEA_API = os.getenv("GITEA_API", "http://127.0.0.1:3000/api/v1")
|
||||||
|
GITEA_TOKEN = os.getenv("GITEA_TOKEN", "")
|
||||||
|
REPO = os.getenv("GITEA_REPO", "stackchain/stackchain-hackathon")
|
||||||
|
|
||||||
|
LABEL_MAP = {
|
||||||
|
"todo": "P1",
|
||||||
|
"ready": "ready",
|
||||||
|
"in_progress": "in_progress",
|
||||||
|
"review": "review",
|
||||||
|
"done": "done",
|
||||||
|
"blocked": "blocked",
|
||||||
|
"triage": "triage",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _gitea(path, body=None, method="GET"):
|
||||||
|
url = f"{GITEA_API}/{path}"
|
||||||
|
data = json.dumps(body).encode() if body else None
|
||||||
|
req = urllib.request.Request(url, data=data, method=method)
|
||||||
|
req.add_header("Accept", "application/json")
|
||||||
|
req.add_header("Authorization", f"token {GITEA_TOKEN}")
|
||||||
|
if data:
|
||||||
|
req.add_header("Content-Type", "application/json")
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read().decode())
|
||||||
|
|
||||||
|
|
||||||
|
def list_issues():
|
||||||
|
return _gitea(f"repos/{REPO}/issues?state=all&limit=100")
|
||||||
|
|
||||||
|
|
||||||
|
def list_labels():
|
||||||
|
return _gitea(f"repos/{REPO}/labels")
|
||||||
|
|
||||||
|
|
||||||
|
def find_label(name, labels):
|
||||||
|
matches = [l for l in labels if l["name"] == name]
|
||||||
|
return matches[0]["id"] if matches else None
|
||||||
|
|
||||||
|
|
||||||
|
def create_label(name):
|
||||||
|
colors = {
|
||||||
|
"P1": "00ccff",
|
||||||
|
"P2": "cccccc",
|
||||||
|
"ready": "0e8a16",
|
||||||
|
"in_progress": "fbca04",
|
||||||
|
"review": "d93f0b",
|
||||||
|
"done": "00cc00",
|
||||||
|
"blocked": "ff4444",
|
||||||
|
"triage": "cccccc",
|
||||||
|
}
|
||||||
|
return _gitea(
|
||||||
|
f"repos/{REPO}/labels",
|
||||||
|
body={"name": name, "color": colors.get(name, "cccccc")},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_issue(number, labels=None):
|
||||||
|
if labels is None:
|
||||||
|
return
|
||||||
|
_gitea(
|
||||||
|
f"repos/{REPO}/issues/{number}",
|
||||||
|
body={"labels": labels},
|
||||||
|
method="PATCH",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
kanban_json = subprocess.check_output(
|
||||||
|
["hermes", "kanban", "list", "--json"], text=True
|
||||||
|
)
|
||||||
|
tasks = json.loads(kanban_json)
|
||||||
|
labels = list_labels()
|
||||||
|
label_ids = {l["name"]: l["id"] for l in labels}
|
||||||
|
issues = {i["title"].split(" ", 1)[1].strip(): i for i in list_issues() if i.get("pull_request") is None}
|
||||||
|
|
||||||
|
for t in tasks:
|
||||||
|
title = t.get("title", "")
|
||||||
|
issue_key = title.split(" ", 1)[1].strip() if title.startswith("#") else title
|
||||||
|
prefix = LABEL_MAP.get(t.get("status", ""))
|
||||||
|
if not prefix:
|
||||||
|
continue
|
||||||
|
gitea_label = label_ids.get(prefix)
|
||||||
|
if gitea_label is None:
|
||||||
|
create_label(prefix)
|
||||||
|
label_ids[prefix] = find_label(prefix, list_labels()) or len(label_ids) + 1
|
||||||
|
if issue_key in issues:
|
||||||
|
issue = issues[issue_key]
|
||||||
|
current = [l["id"] for l in issue.get("labels", [])]
|
||||||
|
if gitea_label and gitea_label not in current:
|
||||||
|
current.append(gitea_label)
|
||||||
|
edit_issue(issue["number"], labels=list(set(current)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user