Skip to content

chore(main): release 0.1.0 #25

chore(main): release 0.1.0

chore(main): release 0.1.0 #25

name: Pull Request Metadata
on:
pull_request:
types: [opened, edited, synchronize, reopened]
workflow_dispatch:
inputs:
pr_number:
description: Pull request number to validate
required: true
type: number
permissions:
contents: read
pull-requests: read
jobs:
validate:
name: Pull Request Metadata
runs-on: ubuntu-latest
steps:
- name: Resolve pull request metadata
env:
EVENT_NAME: ${{ github.event_name }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pr_number }}
run: |
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
cp "${GITHUB_EVENT_PATH}" "${RUNNER_TEMP}/pull-request.json"
else
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
> "${RUNNER_TEMP}/pull-request.json"
fi
- name: Validate branch, title, and description
env:
PR_METADATA_PATH: ${{ runner.temp }}/pull-request.json
run: |
python - <<'PY'
import json
import os
import re
import sys
with open(os.environ["PR_METADATA_PATH"], encoding="utf-8") as metadata_file:
payload = json.load(metadata_file)
pull_request = payload.get("pull_request", payload)
branch = pull_request["head"]["ref"]
title = pull_request["title"]
body = pull_request.get("body") or ""
branch_pattern = re.compile(
r"^(feat|fix|perf|refactor|test|docs|build|ci|chore|revert)/"
r"[a-z0-9]+(?:-[a-z0-9]+)*$"
)
automation_pattern = re.compile(
r"^(release-please--|dependabot/|renovate/|github-actions/)"
)
title_pattern = re.compile(
r"^(feat|fix|perf|refactor|test|docs|build|ci|chore|revert)"
r"(?:\([a-z0-9][a-z0-9-]*\))?!?: [^ ].+$"
)
errors: list[str] = []
automated = automation_pattern.match(branch) is not None
if not automated and branch_pattern.fullmatch(branch) is None:
errors.append("Branch must use <type>/<kebab-case>.")
if title_pattern.fullmatch(title) is None:
errors.append("PR title must follow Conventional Commits.")
if not automated:
if len(body) > 2_000:
errors.append("PR description must not exceed 2,000 characters.")
sections = list(
re.finditer(r"^## (Why|Changes|Verification)\s*$", body, re.MULTILINE)
)
found = [match.group(1) for match in sections]
expected = ["Why", "Changes", "Verification"]
if found != expected:
errors.append(
"PR description must contain Why, Changes, and Verification "
"sections in that order."
)
else:
for index, match in enumerate(sections):
end = sections[index + 1].start() if index + 1 < len(sections) else len(body)
content = re.sub(r"<!--.*?-->", "", body[match.end():end], flags=re.DOTALL)
if not content.strip():
errors.append(f"PR description section {match.group(1)} is empty.")
if errors:
print(f"Invalid pull request metadata for {branch}:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
sys.exit(1)
print(f"Pull request metadata is valid for {branch}.")
PY