chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Warn when a pull request body misses OpenSquilla governance fields."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class RequiredField(NamedTuple):
|
||||
name: str
|
||||
pattern: re.Pattern[str]
|
||||
|
||||
|
||||
REQUIRED_FIELDS = (
|
||||
RequiredField("Scope boundary", re.compile(r"(?im)^\s*Scope boundary\s*:")),
|
||||
RequiredField("Base branch", re.compile(r"(?im)^\s*Base branch\s*:")),
|
||||
RequiredField(
|
||||
"Target exception",
|
||||
re.compile(r"(?im)^\s*(?:Target exception|Main exception)\s*:"),
|
||||
),
|
||||
RequiredField("Linked issue", re.compile(r"(?im)^\s*Linked issue\s*:")),
|
||||
RequiredField("Release note", re.compile(r"(?im)^\s*Release note\s*:")),
|
||||
RequiredField("Tests", re.compile(r"(?im)^\s*(?:#+\s*)?Tests\s*:?\s*$")),
|
||||
RequiredField("Maintainer live check", re.compile(r"(?im)^\s*Maintainer live check\s*:")),
|
||||
RequiredField("Third-party origin", re.compile(r"(?im)^\s*Third-party origin\s*:")),
|
||||
)
|
||||
|
||||
|
||||
def missing_fields(body: str | None) -> list[str]:
|
||||
text = body or ""
|
||||
return [field.name for field in REQUIRED_FIELDS if field.pattern.search(text) is None]
|
||||
|
||||
|
||||
def _annotation_escape(value: str) -> str:
|
||||
return value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
|
||||
|
||||
|
||||
def _warning(title: str, message: str) -> None:
|
||||
print(f"::warning title={_annotation_escape(title)}::{_annotation_escape(message)}")
|
||||
|
||||
|
||||
def _is_strict() -> bool:
|
||||
return os.environ.get("PR_BODY_LINT_STRICT", "0").strip().lower() in {
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
}
|
||||
|
||||
|
||||
def _load_pr_body(event_path: str) -> str | None:
|
||||
with Path(event_path).open(encoding="utf-8") as event_file:
|
||||
event = json.load(event_file)
|
||||
return (event.get("pull_request") or {}).get("body")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
strict = _is_strict()
|
||||
event_path = os.environ.get("GITHUB_EVENT_PATH")
|
||||
if not event_path:
|
||||
_warning("Missing PR event", "GITHUB_EVENT_PATH is not set; skipping PR body lint.")
|
||||
return 1 if strict else 0
|
||||
|
||||
try:
|
||||
body = _load_pr_body(event_path)
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
_warning("Unreadable PR event", f"Unable to read pull request event: {exc}")
|
||||
return 1 if strict else 0
|
||||
|
||||
missing = missing_fields(body)
|
||||
if not missing:
|
||||
print("PR body includes required governance fields.")
|
||||
return 0
|
||||
|
||||
for field in missing:
|
||||
_warning(
|
||||
"Missing PR template field",
|
||||
f"Add the `{field}:` field to make review intent explicit.",
|
||||
)
|
||||
|
||||
print(
|
||||
"PR body lint is warning-only. Maintainers may harden it by setting "
|
||||
"PR_BODY_LINT_STRICT=1 after existing pull requests are migrated."
|
||||
)
|
||||
return 1 if strict else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user