Skip to content

Commit 28e9726

Browse files
committed
ci: require accepted feature requests for external feature PRs
1 parent beee75c commit 28e9726

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Summary
2+
3+
<!-- Briefly describe what changed and why. -->
4+
5+
## Related issues
6+
7+
<!--
8+
External feature PRs must link an accepted feature request issue.
9+
Use a closing keyword such as `Closes #123`.
10+
Maintainers accept feature requests by commenting `/accept-feature-request` on the linked issue.
11+
-->
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
name: External Feature PR Gate
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
- edited
8+
- synchronize
9+
- reopened
10+
- ready_for_review
11+
12+
permissions:
13+
issues: read
14+
pull-requests: read
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
main:
22+
if: |
23+
!startsWith(github.event.pull_request.head.ref, 'release-notes/') &&
24+
github.event.pull_request.draft == false
25+
name: Check accepted feature request
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Require accepted feature request for external feature PRs
29+
env:
30+
GH_TOKEN: ${{ github.token }}
31+
REPOSITORY: ${{ github.repository }}
32+
PR_NUMBER: ${{ github.event.pull_request.number }}
33+
run: |
34+
set -euo pipefail
35+
36+
owner="${REPOSITORY%/*}"
37+
repo="${REPOSITORY#*/}"
38+
39+
pr_json="$(gh api graphql \
40+
-F owner="$owner" \
41+
-F repo="$repo" \
42+
-F number="$PR_NUMBER" \
43+
-f query='
44+
query($owner: String!, $repo: String!, $number: Int!) {
45+
repository(owner: $owner, name: $repo) {
46+
pullRequest(number: $number) {
47+
title
48+
authorAssociation
49+
closingIssuesReferences(first: 20) {
50+
nodes {
51+
number
52+
url
53+
labels(first: 50) {
54+
nodes {
55+
name
56+
}
57+
}
58+
comments(last: 100) {
59+
nodes {
60+
body
61+
authorAssociation
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
')"
70+
71+
author_association="$(jq -r '.data.repository.pullRequest.authorAssociation' <<<"$pr_json")"
72+
pr_title="$(jq -r '.data.repository.pullRequest.title' <<<"$pr_json")"
73+
74+
case "$author_association" in
75+
OWNER|MEMBER|COLLABORATOR)
76+
echo "Maintainer or collaborator PR; skipping external feature request gate."
77+
exit 0
78+
;;
79+
esac
80+
81+
if [[ ! "$pr_title" =~ ^feat(\(.+\))?: ]]; then
82+
echo "External PR is not titled as a feature PR; skipping feature request gate."
83+
exit 0
84+
fi
85+
86+
accepted_issue_urls="$(
87+
jq -r '
88+
.data.repository.pullRequest.closingIssuesReferences.nodes[]
89+
| select(any(.labels.nodes[]?; .name == "✨ Feature"))
90+
| select(any(.comments.nodes[]?;
91+
(.authorAssociation == "OWNER" or
92+
.authorAssociation == "MEMBER" or
93+
.authorAssociation == "COLLABORATOR") and
94+
(.body | test("(^|\\n)\\s*/accept-feature-request\\s*($|\\n)"))
95+
))
96+
| .url
97+
' <<<"$pr_json"
98+
)"
99+
100+
if [[ -n "$accepted_issue_urls" ]]; then
101+
echo "Found accepted feature request issue:"
102+
echo "$accepted_issue_urls"
103+
exit 0
104+
fi
105+
106+
cat <<'EOF' >&2
107+
External feature PRs must link an accepted feature request issue.
108+
109+
To satisfy this check:
110+
1. Open a feature request issue.
111+
2. Wait for a maintainer to accept it by commenting `/accept-feature-request` on that issue.
112+
3. Link the accepted issue from this PR with a closing keyword such as `Closes #123`.
113+
114+
EOF
115+
exit 1

0 commit comments

Comments
 (0)