Skip to content

Commit c7abb5a

Browse files
committed
chore: add GitHub workflows for PR template validation, translation restrictions, and stale PR cleanup
1 parent 9a6c8ad commit c7abb5a

4 files changed

Lines changed: 270 additions & 4 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
## 📋 Description
22

3-
Please include a summary of the changes and the related issue. Explain the problem that you are solving and provide the
4-
necessary context.
3+
<!-- What does this pull request change, and why? Please include the context we need to review it. -->
54

65
## 🚀 Changes made to ...
76

87
- [ ] 🔧 Server
98
- [ ] 🖥️ Client
9+
- [ ] 🌐 Web
1010
- [ ] 📚 Documentation
1111
- [ ] 🔄 Other: ___
1212

1313
## ✅ Checklist
1414

1515
- [ ] My code follows the style guidelines of this project
1616
- [ ] I have performed a self-review of my own code
17+
- [ ] I have tested my changes locally
1718
- [ ] I have looked for similar pull requests in the repository and found none
19+
- [ ] This pull request does not contain translations (they are managed on [Crowdin](https://crowdin.com/project/myspeed), only `en.json` is edited here)
1820

19-
## 🔗 Related Issues <!-- If there are any related issues, please link them here. -->
21+
## 🔗 Related Issues
2022

21-
Fixes #(issue)
23+
<!-- Link any related issues here, for example: Fixes #123 -->
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: PR template guard (stale)
2+
3+
on:
4+
schedule:
5+
- cron: "0 3 * * *"
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
issues: write
12+
13+
env:
14+
LABEL: "incomplete template"
15+
GRACE_DAYS: 14
16+
17+
jobs:
18+
close:
19+
runs-on: ubuntu-latest
20+
if: github.repository == 'gnmyt/MySpeed'
21+
steps:
22+
- name: Close pull requests with an unfinished template
23+
env:
24+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25+
REPO: ${{ github.repository }}
26+
run: |
27+
cutoff=$(( $(date +%s) - GRACE_DAYS * 86400 ))
28+
29+
gh pr list --repo "$REPO" --state open --label "$LABEL" --limit 100 \
30+
--json number,isDraft,author \
31+
--jq '.[] | select(.isDraft | not) | "\(.number) \(.author.login)"' > prs.txt
32+
33+
while read -r number author; do
34+
[ -z "$number" ] && continue
35+
36+
labeled_at=$(gh api --paginate "repos/$REPO/issues/$number/timeline" \
37+
--jq "[.[] | select(.event == \"labeled\" and .label.name == \"$LABEL\") | .created_at] | last")
38+
39+
if [ -z "$labeled_at" ] || [ "$labeled_at" = "null" ]; then
40+
echo "PR #$number: no label event found, skipping."
41+
continue
42+
fi
43+
44+
if [ "$(date -d "$labeled_at" +%s)" -gt "$cutoff" ]; then
45+
echo "PR #$number: still within the grace period."
46+
continue
47+
fi
48+
49+
echo "PR #$number: labeled at $labeled_at, closing."
50+
51+
{
52+
echo "<!-- pr-template-guard-stale -->"
53+
echo "Hey @$author, we haven't heard back on this one, so we're closing it for now."
54+
echo
55+
echo "The pull request template is still incomplete after $GRACE_DAYS days. Feel free to fill it in"
56+
echo "and reopen whenever you get the chance, we're happy to take another look."
57+
} > comment.md
58+
59+
gh pr comment "$number" --repo "$REPO" --body-file comment.md
60+
gh pr close "$number" --repo "$REPO"
61+
done < prs.txt

.github/workflows/pr-template.yml

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: PR template guard
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened, edited, synchronize]
6+
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
issues: write
11+
12+
jobs:
13+
check:
14+
runs-on: ubuntu-latest
15+
if: >
16+
github.repository == 'gnmyt/MySpeed' &&
17+
!endsWith(github.event.pull_request.user.login, '[bot]')
18+
steps:
19+
- name: Check pull request body
20+
id: check
21+
env:
22+
BODY: ${{ github.event.pull_request.body }}
23+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
REPO: ${{ github.repository }}
25+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
26+
run: |
27+
strip() { tr -d '\r' | sed -e 's/<!--.*-->//g' -e '/<!--/,/-->/d'; }
28+
29+
printf '%s' "$BODY" | strip > body.md
30+
31+
gh api "repos/$REPO/contents/.github/PULL_REQUEST_TEMPLATE.md?ref=$BASE_SHA" \
32+
--jq '.content' | base64 -d | strip > template.md
33+
34+
section() {
35+
awk -v want="$2" '
36+
/^#{1,6} / { keep = (index(tolower($0), tolower(want)) > 0); next }
37+
keep { print }
38+
' "$1"
39+
}
40+
41+
items() {
42+
section "$1" "$2" \
43+
| grep -E '^[[:space:]]*- \[[ xX]\]' \
44+
| sed -E -e 's/^[[:space:]]*- \[[ xX]\][[:space:]]*//' -e 's/[[:space:]]+$//' \
45+
| tr -s ' ' | tr '[:upper:]' '[:lower:]' \
46+
| grep -v 'other:' | sort || true
47+
}
48+
49+
headings() {
50+
grep -E '^#{1,6} ' "$1" \
51+
| sed -E -e 's/^#{1,6}[[:space:]]*//' -e 's/[[:space:]]+$//' \
52+
| tr -s ' ' | tr '[:upper:]' '[:lower:]' | sort || true
53+
}
54+
55+
problems=""
56+
add() { problems="${problems}- $1"$'\n'; }
57+
58+
tampered() {
59+
items template.md "$1" > canonical.txt
60+
items body.md "$1" > given.txt
61+
[ -n "$(comm -3 canonical.txt given.txt || true)" ]
62+
}
63+
64+
if ! grep -qi '^#\{1,6\} .*Changes made to' body.md || ! grep -qi '^#\{1,6\} .*Checklist' body.md; then
65+
add "the pull request template was not used"
66+
else
67+
headings template.md > canonical_headings.txt
68+
headings body.md > given_headings.txt
69+
if [ -n "$(comm -3 canonical_headings.txt given_headings.txt || true)" ]; then
70+
add "the section headings were edited, please leave them as they are"
71+
fi
72+
73+
if grep -qi '^#\{1,6\} .*Description' body.md; then
74+
description=$(section body.md "Description" | grep -v '^\s*$' || true)
75+
if [ -z "$description" ]; then
76+
add "the description is empty"
77+
fi
78+
fi
79+
80+
if tampered "Changes made to"; then
81+
add "the options under \"Changes made to ...\" were edited, please leave them as they are"
82+
elif ! section body.md "Changes made to" | grep -qi '^\s*- \[x\]'; then
83+
add "no option is selected under \"Changes made to ...\""
84+
fi
85+
86+
if tampered "Checklist"; then
87+
add "the checklist was edited, please leave the items as they are"
88+
elif section body.md "Checklist" | grep -q '^\s*- \[ \]'; then
89+
add "not every item in the checklist is checked"
90+
fi
91+
fi
92+
93+
{
94+
echo "problems<<EOF"
95+
printf '%s' "$problems"
96+
echo "EOF"
97+
} >> "$GITHUB_OUTPUT"
98+
99+
- name: Update comment
100+
env:
101+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
102+
PR: ${{ github.event.pull_request.number }}
103+
REPO: ${{ github.repository }}
104+
AUTHOR: ${{ github.event.pull_request.user.login }}
105+
PROBLEMS: ${{ steps.check.outputs.problems }}
106+
MARKER: "<!-- pr-template-guard -->"
107+
run: |
108+
comment_id=$(gh api --paginate "repos/$REPO/issues/$PR/comments" \
109+
--jq ".[] | select(.body | contains(\"$MARKER\")) | .id" | head -n1)
110+
111+
if [ -z "$PROBLEMS" ]; then
112+
if [ -n "$comment_id" ]; then
113+
gh api -X DELETE "repos/$REPO/issues/comments/$comment_id"
114+
fi
115+
gh pr edit "$PR" --repo "$REPO" --remove-label "incomplete template" || true
116+
exit 0
117+
fi
118+
119+
{
120+
echo "$MARKER"
121+
echo "Hey @$AUTHOR, thanks for the pull request!"
122+
echo
123+
echo "Before we can review it, could you complete the pull request template? Right now:"
124+
echo
125+
printf '%s' "$PROBLEMS"
126+
echo
127+
echo "You can edit the description at any time, this comment disappears once everything is filled in."
128+
} > comment.md
129+
130+
if [ -n "$comment_id" ]; then
131+
gh api -X PATCH "repos/$REPO/issues/comments/$comment_id" -F body=@comment.md >/dev/null
132+
else
133+
gh pr comment "$PR" --repo "$REPO" --body-file comment.md
134+
fi
135+
136+
gh pr edit "$PR" --repo "$REPO" --add-label "incomplete template" || true
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Translation PR guard
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened, synchronize]
6+
paths:
7+
- "client/public/assets/locales/**"
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
issues: write
13+
14+
jobs:
15+
check:
16+
runs-on: ubuntu-latest
17+
if: >
18+
github.repository == 'gnmyt/MySpeed' &&
19+
!endsWith(github.event.pull_request.user.login, '[bot]') &&
20+
github.event.pull_request.user.login != 'crowdin' &&
21+
!startsWith(github.event.pull_request.head.ref, 'l10n')
22+
steps:
23+
- name: Inspect changed files
24+
id: files
25+
env:
26+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27+
PR: ${{ github.event.pull_request.number }}
28+
REPO: ${{ github.repository }}
29+
run: |
30+
gh api --paginate "repos/$REPO/pulls/$PR/files" --jq '.[].filename' \
31+
| grep -E '^client/public/assets/locales/.+\.json$' \
32+
| grep -v '^client/public/assets/locales/en\.json$' > translations.txt || true
33+
34+
echo "count=$(wc -l < translations.txt)" >> "$GITHUB_OUTPUT"
35+
36+
- name: Comment and close
37+
if: steps.files.outputs.count != '0'
38+
env:
39+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40+
PR: ${{ github.event.pull_request.number }}
41+
REPO: ${{ github.repository }}
42+
AUTHOR: ${{ github.event.pull_request.user.login }}
43+
MARKER: "<!-- translation-pr-guard -->"
44+
run: |
45+
if gh api --paginate "repos/$REPO/issues/$PR/comments" --jq '.[].body' | grep -qF "$MARKER"; then
46+
echo "Already commented, skipping."
47+
exit 0
48+
fi
49+
50+
{
51+
echo "$MARKER"
52+
echo "Hey @$AUTHOR, thanks for helping out with MySpeed's translations!"
53+
echo
54+
echo "All of our translations live on Crowdin: https://crowdin.com/project/myspeed"
55+
echo
56+
echo "Changes to the locale files are overwritten on the next sync, so we can't merge them."
57+
echo "Only \`en.json\` is edited in this repository."
58+
echo
59+
echo "Closing this pull request. Please submit your changes on Crowdin instead, or remove the"
60+
echo "translation files and reopen if this pull request also contains other changes."
61+
echo
62+
echo "Your contribution is appreciated, we'd just like to keep it all in one place."
63+
} > comment.md
64+
65+
gh pr comment "$PR" --repo "$REPO" --body-file comment.md
66+
gh pr edit "$PR" --repo "$REPO" --add-label "translations" || true
67+
gh pr close "$PR" --repo "$REPO"

0 commit comments

Comments
 (0)