Skip to content

Commit 6bc8f62

Browse files
committed
ci: migrate CI to GitHub Actions
Replace the Drone pipeline with GitHub Actions workflows for CI, commit checks, release previews, and manual releases. Add git-cliff release version helper coverage and update GoReleaser and lint configuration for the new flow.
1 parent 56457bf commit 6bc8f62

20 files changed

Lines changed: 746 additions & 100 deletions

.commitlintrc.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extends:
2+
- '@commitlint/config-conventional'
3+
4+
rules:
5+
header-max-length: [0, 'always', 100]

.drone.yml

Lines changed: 0 additions & 82 deletions
This file was deleted.
File renamed without changes.
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import argparse
5+
import json
6+
import re
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
from typing import Literal
11+
12+
REPO_ROOT = Path(__file__).resolve().parents[2]
13+
RC_IGNORE_TAGS = r"^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$"
14+
RC_VERSION_RE = re.compile(r"^(\d+\.\d+\.\d+)-rc\.(\d+)$")
15+
ReleaseKind = Literal["stable", "rc"]
16+
17+
18+
def bumped_version(release_kind: ReleaseKind) -> str:
19+
try:
20+
raw_version = run_command(*build_git_cliff_args(release_kind))
21+
except subprocess.CalledProcessError as exc:
22+
stderr = (exc.stderr or "").strip()
23+
if "No releases found" in stderr:
24+
return "0.1.0"
25+
raise RuntimeError(stderr or "git-cliff failed while computing the next version.") from exc
26+
27+
return raw_version
28+
29+
30+
def build_git_cliff_args(release_kind: ReleaseKind) -> list[str]:
31+
args = ["git-cliff"]
32+
if release_kind == "stable":
33+
args.extend(["--ignore-tags", RC_IGNORE_TAGS])
34+
args.append("--bumped-version")
35+
return args
36+
37+
38+
def build_git_cliff_context_args(release_kind: ReleaseKind) -> list[str]:
39+
args = ["git-cliff", "--unreleased", "--bump", "--context"]
40+
if release_kind == "stable":
41+
args.extend(["--ignore-tags", RC_IGNORE_TAGS])
42+
return args
43+
44+
45+
def run_command(*args: str) -> str:
46+
completed = subprocess.run(
47+
args,
48+
cwd=REPO_ROOT,
49+
check=True,
50+
text=True,
51+
capture_output=True,
52+
)
53+
return completed.stdout.strip()
54+
55+
56+
def normalize_version(raw_version: str) -> str:
57+
version = raw_version.strip()
58+
if version.startswith("v"):
59+
version = version[1:]
60+
return version
61+
62+
63+
def compute_next_tag(raw_version: str, release_kind: ReleaseKind) -> str:
64+
version = normalize_version(raw_version)
65+
if not version:
66+
raise RuntimeError("git-cliff did not return a version.")
67+
68+
if release_kind == "stable":
69+
if RC_VERSION_RE.fullmatch(version):
70+
raise RuntimeError("git-cliff returned a prerelease version for a stable release.")
71+
return f"v{version}"
72+
73+
if RC_VERSION_RE.fullmatch(version):
74+
return f"v{version}"
75+
return f"v{version}-rc.1"
76+
77+
78+
def release_commit_count(release_kind: ReleaseKind) -> int:
79+
try:
80+
raw_context = run_command(*build_git_cliff_context_args(release_kind))
81+
except subprocess.CalledProcessError as exc:
82+
stderr = (exc.stderr or "").strip()
83+
raise RuntimeError(stderr or "git-cliff failed while checking for new commits.") from exc
84+
85+
try:
86+
context = json.loads(raw_context)
87+
except json.JSONDecodeError as exc:
88+
raise RuntimeError("git-cliff returned invalid JSON while checking for new commits.") from exc
89+
90+
if not context:
91+
return 0
92+
93+
statistics = context[0].get("statistics", {})
94+
return int(statistics.get("commit_count", 0))
95+
96+
97+
def ensure_new_commits(release_kind: ReleaseKind) -> None:
98+
if release_commit_count(release_kind) == 0:
99+
raise RuntimeError("nothing to release")
100+
101+
102+
def tag_exists(tag: str) -> bool:
103+
completed = subprocess.run(
104+
["git", "ls-remote", "--exit-code", "--tags", "origin", f"refs/tags/{tag}"],
105+
cwd=REPO_ROOT,
106+
text=True,
107+
capture_output=True,
108+
)
109+
if completed.returncode == 0:
110+
return True
111+
if completed.returncode == 2:
112+
return False
113+
114+
stderr = (completed.stderr or completed.stdout or "").strip()
115+
raise RuntimeError(stderr or f"git failed while checking whether {tag} exists in origin.")
116+
117+
118+
def ensure_tag_absent(tag: str) -> None:
119+
if tag_exists(tag):
120+
raise RuntimeError(f"tag {tag} already exists in origin")
121+
122+
123+
def build_parser() -> argparse.ArgumentParser:
124+
parser = argparse.ArgumentParser(
125+
prog="compute_release_version.py",
126+
description="Compute the next release tag for stable or rc workflows.",
127+
)
128+
parser.add_argument(
129+
"--release-kind",
130+
required=True,
131+
choices=("stable", "rc"),
132+
help="Release line to compute the next tag for.",
133+
)
134+
parser.add_argument(
135+
"--require-new-commits",
136+
action="store_true",
137+
help="Fail if there are no unreleased commits for the selected release line.",
138+
)
139+
parser.add_argument(
140+
"--require-absent-tag",
141+
action="store_true",
142+
help="Fail if the computed release tag already exists in origin.",
143+
)
144+
return parser
145+
146+
147+
def main(argv: list[str] | None = None) -> int:
148+
args = build_parser().parse_args(argv)
149+
try:
150+
if args.require_new_commits:
151+
ensure_new_commits(args.release_kind)
152+
next_tag = compute_next_tag(bumped_version(args.release_kind), args.release_kind)
153+
if args.require_absent_tag:
154+
ensure_tag_absent(next_tag)
155+
print(next_tag)
156+
except (RuntimeError, subprocess.CalledProcessError) as exc:
157+
print(str(exc), file=sys.stderr)
158+
return 1
159+
return 0
160+
161+
162+
if __name__ == "__main__":
163+
sys.exit(main())

.github/scripts/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Package marker for unittest discovery.

0 commit comments

Comments
 (0)