-
Notifications
You must be signed in to change notification settings - Fork 1
233 lines (201 loc) · 9.47 KB
/
Copy pathvalidate-pr.yml
File metadata and controls
233 lines (201 loc) · 9.47 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
---
name: PR Governance & Workflow
on:
pull_request:
types: [opened, edited, reopened, synchronize, ready_for_review, converted_to_draft, assigned, unassigned, closed]
permissions:
contents: read
pull-requests: write # Required for commenting
repository-projects: write
issues: read
jobs:
validate-pr:
name: Validate PR Standards
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
TITLE: "${{ github.event.pull_request.title }}"
REPO: "${{ github.repository }}"
steps:
- name: Checkout Code
uses: actions/checkout@v7
# --- STEP 1: Check Assignee ---
- name: Check Assignee
id: check_assignee
run: |
AUTHOR=$(gh pr view ${{ github.event.pull_request.number }} --json author --jq '.author.login')
if [ "$AUTHOR" == "app/renovate" ]; then
echo "✅ This PR is authored by Renovate, no assignee required"
exit 0
fi
ASSIGNEES=$(gh pr view ${{ github.event.pull_request.number }} --json assignees --jq '.assignees | length')
if [ "$ASSIGNEES" -eq 0 ]; then
echo "::error::⛔ Missing Assignee: You must assign this PR to a user."
exit 1
else
echo "✅ Assignee check passed."
fi
# --- STEP 2: Check Conventional Commits ---
- name: Check Title Format (Conventional Commits)
id: check_title
run: |
if [[ ! "$TITLE" =~ ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-]+\))?:[[:space:]].+ ]]; then
echo "::error::⛔ Invalid Title: Must follow Conventional Commits (e.g., 'feat: Add new button')."
exit 1
else
echo "✅ Title format passed."
fi
# --- STEP 3: Validate Linked Issue in Title ---
- name: Check Linked Issue & Title Match
id: check_issue
run: |
WARNING_IDENTIFIER="PR Governance Notice"
WARNING_BODY="### ⚠️ $WARNING_IDENTIFIER<br/>No linked issue found. This PR will not be moved on the project board automatically."
# 1. Check if a warning comment already exists
# We inject the variable directly into the jq string using escaped quotes (\")
EXISTING_COMMENT_ID=$(gh api repos/puzzle/pcts/issues/$PR_NUMBER/comments \
--jq ".[] | select(.body | contains(\"$WARNING_IDENTIFIER\")) | .id")
# 2. Get Linked Issues
ISSUE_JSON=$(gh pr view $PR_NUMBER --json closingIssuesReferences --jq '.closingIssuesReferences')
ISSUE_COUNT=$(echo "$ISSUE_JSON" | jq 'length')
if [ "$ISSUE_COUNT" -gt 0 ]; then
# --- SCENARIO: Issue is Linked ---
ISSUE_NUM=$(echo "$ISSUE_JSON" | jq -r '.[0].number')
echo "🔗 Found Linked Issue: #$ISSUE_NUM"
# Check if Title contains the number
if [[ "$TITLE" != *"$ISSUE_NUM"* ]]; then
echo "::error::⛔ Title Mismatch: This PR is linked to Issue #$ISSUE_NUM, but the number is missing from the title."
exit 1
else
echo "✅ Title contains the linked issue number."
# CLEANUP: If the warning comment exists, delete it now because the PR is fixed.
if [ ! -z "$EXISTING_COMMENT_ID" ]; then
echo "🧹 Removing stale warning comment (ID: $EXISTING_COMMENT_ID)..."
gh api -X DELETE "repos/puzzle/pcts/issues/comments/$EXISTING_COMMENT_ID"
echo "✅ Comment deleted."
fi
fi
else
# --- SCENARIO: No Linked Issue ---
echo "⚠️ No linked issue found."
# Only post if the comment doesn't already exist to avoid spam
if [ -z "$EXISTING_COMMENT_ID" ]; then
gh pr comment $PR_NUMBER --body "$WARNING_BODY"
echo "✅ Warning comment posted."
else
echo "ℹ️ Warning comment already exists. No action taken."
fi
fi
- name: Checkout Code
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Check Ancestry
run: |
# Define branch names for clarity
BASE_BRANCH="origin/${{ github.base_ref }}"
HEAD_BRANCH="origin/${{ github.head_ref }}"
echo "Checking if $HEAD_BRANCH is rebased on $BASE_BRANCH..."
# Fetch the latest refs to ensure accurate comparison
git fetch origin "${{ github.base_ref }}"
git fetch origin "${{ github.head_ref }}"
# Check if the base branch is an ancestor of the head branch
if git merge-base --is-ancestor "$BASE_BRANCH" "$HEAD_BRANCH"; then
echo "✅ Success: The PR branch is fully up-to-date with the base branch."
exit 0
else
echo "❌ Error: The PR branch is behind the base branch."
echo "Action Required: Please rebase your branch on ${{ github.base_ref }}."
echo "Command suggestion: git fetch origin && git rebase origin/${{ github.base_ref }}"
exit 1
fi
update-project-status:
name: Update Project Status
runs-on: ubuntu-latest
needs: validate-pr
if: success()
env:
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_GH_TOKEN }}
OWNER: "puzzle"
PROJECT_NUMBER: 17
FIELD_NAME: "Status"
steps:
- name: Determine Target Status
id: set_status
run: |
if [ "${{ github.event.pull_request.merged }}" == "true" ]; then
echo "TARGET_STATUS=Ready for Sprint Review" >> $GITHUB_ENV
elif [ "${{ github.event.action }}" == "closed" ]; then
echo "PR closed without merge. Skipping update."
echo "TARGET_STATUS=STOP" >> $GITHUB_ENV
elif [ "${{ github.event.pull_request.draft }}" == "true" ]; then
echo "TARGET_STATUS=In progress" >> $GITHUB_ENV
else
echo "TARGET_STATUS=In review" >> $GITHUB_ENV
fi
- name: Get Id of desired status label
run: |
LABEL_ID=$(gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json | jq -r --arg field_name "Status" --arg search_string "In review" '
.fields[]
| select(.name == $field_name)
| .options[]
| select(.name | endswith($search_string))
| .id
')
echo "$LABEL_ID"
echo "DESIRED_STATUS_LABEL_ID=$LABEL_ID" >> $GITHUB_ENV
- name: Move Linked Issues to In Implementation
if: env.TARGET_STATUS != 'STOP'
shell: bash
env:
# GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # or your specific PAT
OWNER: puzzle
PROJECT_NUMBER: 17
FIELD_NAME: Status
# ADDED: These are required for the script to work
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
echo "Desired status: $TARGET_STATUS, desired status label id: $DESIRED_STATUS_LABEL_ID"
# 1. Fetch Closing Issues (Renamed variable to match the loop below)
LINKED_ISSUES=$(gh pr view "$PR_NUMBER" -R "$REPO" --json closingIssuesReferences -q '.closingIssuesReferences[].number')
echo "Linked issue numbers: $LINKED_ISSUES"
if [[ -z "$LINKED_ISSUES" ]]; then
echo "No linked issues found for PR #$PR_NUMBER."
exit 0
fi
# 3. Get Project ID
echo "Project number: $PROJECT_NUMBER, owner $OWNER"
PROJECT_ID=$(gh project view "$PROJECT_NUMBER" --owner "$OWNER" --format json -q .id)
echo "Project ID: $PROJECT_ID"
# 4. Loop through issues
for ISSUE_NUM in $LINKED_ISSUES; do
echo "Processing Issue #$ISSUE_NUM..."
# Get Item ID within the project
ITEM_ID=$(gh project item-list "$PROJECT_NUMBER" --owner "$OWNER" --limit 100 --format json | jq -r --argjson n "$ISSUE_NUM" '
.items[] | select(.content.number == $n) | .id
')
echo "Found Item ID: $ITEM_ID"
if [[ -n "$ITEM_ID" ]]; then
FIELD_ID=$(gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json | jq -r --arg name "$FIELD_NAME" '.fields[] | select(.name==$name) | .id')
OPTION_ID=$(gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json | jq -r --arg name "$FIELD_NAME" --arg status "$TARGET_STATUS" '
.fields[] | select(.name==$name) | .options[] | select(.name | endswith($status)) | .id
')
if [[ -n "$FIELD_ID" && -n "$OPTION_ID" ]]; then
gh project item-edit --id "$ITEM_ID" --project-id "$PROJECT_ID" --field-id "$FIELD_ID" --single-select-option-id "$OPTION_ID"
echo "✅ Issue #$ISSUE_NUM moved to '$TARGET_STATUS'"
else
echo "❌ Could not find Field ID or Option ID for status '$TARGET_STATUS'"
fi
else
echo "⚠️ Skipping Issue #$ISSUE_NUM: Item not found in project."
fi
done
- name: Warn if Update Skipped
if: env.STATUS != 'STOP' && env.TARGET_URL == ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Switch back to standard token for commenting
run: |
echo "⚠️ Skipping Project Update because no linked issue was found."
# We don't exit 1 here, we just finish successfully without moving anything.