-
Notifications
You must be signed in to change notification settings - Fork 0
125 lines (107 loc) · 4.67 KB
/
Copy pathrelease-audit.yml
File metadata and controls
125 lines (107 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
name: release-audit
on:
pull_request:
branches: [main, master]
push:
branches: [main, master]
workflow_dispatch:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Run release audit
run: |
python3 - <<'PY'
import json, pathlib, sys
repo = pathlib.Path.cwd()
scorecard = {"overall_grade": "A", "angles_passing": 0, "angles_total": 8, "blockers": 0, "angles": []}
def check(name, grade, detail):
scorecard["angles"].append({"angle": name, "grade": grade, "detail": detail})
if grade == "A":
scorecard["angles_passing"] += 1
elif grade == "F":
scorecard["blockers"] += 1
# 1. README
readme = repo / "README.md"
if readme.exists() and len(readme.read_text()) > 200:
check("README", "A", "README.md exists and has content")
else:
check("README", "F", "README.md missing or too short")
# 2. License
lic = repo / "LICENSE"
if lic.exists() and len(lic.read_text()) > 100:
check("License", "A", "LICENSE file present")
else:
check("License", "F", "LICENSE file missing or too short")
# 3. CI/CD
wf_dir = repo / ".github" / "workflows"
wf_files = list(wf_dir.glob("*.yml")) + list(wf_dir.glob("*.yaml"))
if len(wf_files) >= 1:
check("CI/CD", "A", f"{len(wf_files)} workflow(s) found")
else:
check("CI/CD", "F", "No CI/CD workflows found")
# 4. Dependencies
pyproj = repo / "pyproject.toml"
if pyproj.exists():
check("Dependencies", "A", "pyproject.toml found")
else:
check("Dependencies", "F", "pyproject.toml missing")
# 5. Tests
tests = repo / "tests"
test_files = list(tests.glob("test_*.py")) + list(tests.glob("*_test.py"))
if len(test_files) >= 1:
check("Tests", "A", f"{len(test_files)} test file(s) found")
else:
check("Tests", "F", "No test files found")
# 6. Versioning
if pyproj.exists():
import tomllib
data = tomllib.loads(pyproj.read_text())
ver = data.get("project", {}).get("version", "")
if ver:
check("Versioning", "A", f"Version {ver} set in pyproject.toml")
else:
check("Versioning", "F", "No version in pyproject.toml")
else:
check("Versioning", "F", "Cannot check version — pyproject.toml missing")
# 7. Changelog
changelog = repo / "CHANGELOG.md"
if changelog.exists() and len(changelog.read_text()) > 50:
check("Changelog", "A", "CHANGELOG.md present")
else:
check("Changelog", "C", "CHANGELOG.md missing or too short")
# 8. Security
sec = repo / "SECURITY.md"
if sec.exists() and len(sec.read_text()) > 50:
check("Security", "A", "SECURITY.md present")
else:
check("Security", "C", "SECURITY.md missing or too short")
# Compute overall grade
if scorecard["blockers"] > 0:
scorecard["overall_grade"] = "F"
elif scorecard["angles_passing"] == scorecard["angles_total"]:
scorecard["overall_grade"] = "A"
elif scorecard["angles_passing"] >= scorecard["angles_total"] - 2:
scorecard["overall_grade"] = "B"
else:
scorecard["overall_grade"] = "C"
out_dir = repo / "scorecard"
out_dir.mkdir(exist_ok=True)
(out_dir / f"{repo.name}.json").write_text(json.dumps(scorecard, indent=2))
print("## Release Audit (8 angles)")
print()
print(f"**Overall grade: {scorecard['overall_grade']}** ({scorecard['angles_passing']}/{scorecard['angles_total']} angles passing, {scorecard['blockers']} blocker(s))")
print()
print("| Angle | Grade | Detail |")
print("|-------|-------|--------|")
for a in scorecard["angles"]:
print(f"| {a['angle']} | {a['grade']} | {a['detail']} |")
if scorecard["blockers"] > 0:
print(f"\n::error::{scorecard['blockers']} release-blocker angle(s) — see audit output above")
sys.exit(1)
PY