Skip to content

Commit 1c84e8f

Browse files
[tool] chore(workflow): add non-blocking PR title validation GitHub Action
1 parent b361d2d commit 1c84e8f

File tree

2 files changed

+126
-32
lines changed

2 files changed

+126
-32
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: "PR Title check worker"
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, reopened, ready_for_review, synchronize]
6+
7+
jobs:
8+
validate-pr-title:
9+
runs-on: ubuntu-latest
10+
continue-on-error: true
11+
permissions:
12+
contents: read
13+
checks: write
14+
15+
steps:
16+
- name: Generate a token
17+
id: generate-token
18+
uses: actions/create-github-app-token@v2
19+
with:
20+
app-id: ${{ secrets.OPENAEV_PR_CHECKS_APP_ID }}
21+
private-key: ${{ secrets.OPENAEV_PR_CHECKS_PRIVATE_KEY }}
22+
- name: Validate PR title and create check
23+
shell: bash
24+
env:
25+
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
26+
REPO: ${{ github.repository }}
27+
SHA: ${{ github.event.pull_request.head.sha }}
28+
run: |
29+
set -euo pipefail
30+
31+
TITLE="${{ github.event.pull_request.title }}"
32+
echo "PR title: $TITLE"
33+
34+
# Skip validation for renovate
35+
if [[ "$TITLE" == *"chore(deps)"* ]]; then
36+
echo "⚠️ Skipping validation for renovate PRs."
37+
OUTPUT_TITLE="⚠️ Skipping validation for Renovate PRs."
38+
OUTPUT_SUMMARY="⚠️ Skipping validation for Renovate PRs."
39+
CONCLUSION="success"
40+
else
41+
# Full pattern:
42+
# [category/subcategory] type(scope?): description (#123)
43+
FULL_PATTERN='^\[([a-z]+(/[a-z]+)*)\] (feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\([a-z-]+\))?: [a-z].*( \(#[0-9]+\))$'
44+
45+
if [[ "$TITLE" =~ $FULL_PATTERN ]]; then
46+
echo "✅ PR title is valid."
47+
OUTPUT_TITLE="✅ PR title is valid."
48+
OUTPUT_SUMMARY="✅ PR title is valid."
49+
CONCLUSION="success"
50+
else
51+
# Diagnose common failures
52+
53+
# 1) Check category block: [category/category]
54+
CATEGORY_PATTERN='^\[([a-z]+(/[a-z]+)*)\]'
55+
if ! [[ "$TITLE" =~ $CATEGORY_PATTERN ]]; then
56+
REASON="Bad [category] block. Expected: [category] or [category/category]"
57+
fi
58+
59+
# 2) Check type + optional scope
60+
TYPE_PATTERN='^\[([a-z]+(/[a-z]+)*)\] (feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\([a-z-]+\))?: '
61+
if [[ -z "${REASON:-}" ]] && ! [[ "$TITLE" =~ $TYPE_PATTERN ]]; then
62+
REASON="Bad type(scope): block. Expected type: feat, fix, chore, docs, style, refactor, perf, test, build, ci, revert (optionally with scope: type(scope):)"
63+
fi
64+
65+
# 3) Check description starts with lowercase letter
66+
DESC_PATTERN='^\[([a-z]+(/[a-z]+)*)\] (feat|fix|chore|docs|style|refactor|perf|test|build|ci|revert)(\([a-z-]+\))?: [a-z]'
67+
if [[ -z "${REASON:-}" ]] && ! [[ "$TITLE" =~ $DESC_PATTERN ]]; then
68+
REASON="Bad description. Must start with a lowercase letter after ': '"
69+
fi
70+
71+
# 4) Check issue reference at the end: (#XXX)
72+
ISSUE_PATTERN='\(#[0-9]+\)$'
73+
if [[ -z "${REASON:-}" ]] && ! [[ "$TITLE" =~ $ISSUE_PATTERN ]]; then
74+
REASON="Bad (#XXX) ending block. Missing issue reference"
75+
fi
76+
77+
if [[ -z "${REASON:-}" ]]; then
78+
REASON="Bad title. Does not match the required pattern"
79+
fi
80+
81+
echo "❌ Invalid PR title: $REASON"
82+
echo "Required format:"
83+
echo "[category] type(scope?): description (#123)"
84+
85+
OUTPUT_TITLE="$REASON"
86+
OUTPUT_SUMMARY="❌ Invalid PR title: $REASON. \nRequired: [category] type(scope?): description (#XXX)"
87+
CONCLUSION="failure"
88+
fi
89+
fi
90+
91+
# Create custom check run
92+
CHECK_RUN=$(
93+
curl -sS -X POST \
94+
-H "Authorization: Bearer $GITHUB_TOKEN" \
95+
-H "Accept: application/vnd.github+json" \
96+
https://api.github.com/repos/$REPO/check-runs \
97+
-d @- <<EOF
98+
{
99+
"name": "Validate PR Title (optional)",
100+
"head_sha": "$SHA",
101+
"status": "in_progress"
102+
}
103+
EOF
104+
)
105+
106+
CHECK_RUN_ID=$(echo "$CHECK_RUN" | jq -r '.id')
107+
echo "Created check run ID: $CHECK_RUN_ID"
108+
109+
# Complete the check run with conclusion + detailed summary
110+
curl -sS -X PATCH \
111+
-H "Authorization: Bearer $GITHUB_TOKEN" \
112+
-H "Accept: application/vnd.github+json" \
113+
https://api.github.com/repos/$REPO/check-runs/$CHECK_RUN_ID \
114+
-d @- <<EOF
115+
{
116+
"status": "completed",
117+
"conclusion": "$CONCLUSION",
118+
"output": {
119+
"title": "$OUTPUT_TITLE",
120+
"summary": "$OUTPUT_SUMMARY"
121+
}
122+
}
123+
EOF
124+
125+
# Do not fail job (continue-on-error is true)
126+
exit 0

.github/workflows/validate-pr-title.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)