|
1 | | -name: Close Unvetted Non-Maintainer PRs |
| 1 | +name: Validate PR |
2 | 2 |
|
3 | 3 | on: |
4 | 4 | pull_request_target: |
5 | | - types: [opened] |
| 5 | + types: [opened, reopened] |
6 | 6 |
|
7 | 7 | jobs: |
8 | 8 | validate-non-maintainer-pr: |
|
11 | 11 | permissions: |
12 | 12 | pull-requests: write |
13 | 13 | contents: write |
| 14 | + outputs: |
| 15 | + was-closed: ${{ steps.validate.outputs.was-closed }} |
14 | 16 | steps: |
15 | 17 | - name: Generate GitHub App token |
16 | 18 | id: app-token |
|
20 | 22 | private-key: ${{ secrets.SDK_MAINTAINER_BOT_PRIVATE_KEY }} |
21 | 23 |
|
22 | 24 | - name: Validate PR |
| 25 | + id: validate |
23 | 26 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 |
24 | 27 | with: |
25 | 28 | github-token: ${{ steps.app-token.outputs.token }} |
@@ -101,12 +104,12 @@ jobs: |
101 | 104 |
|
102 | 105 | core.info(`Found ${issueRefs.length} issue reference(s): ${[...seen].join(', ')}`); |
103 | 106 |
|
104 | | - // --- Helper: close PR with comment and label --- |
105 | | - async function closePR(message) { |
| 107 | + // --- Helper: close PR with comment and labels --- |
| 108 | + async function closePR(message, reasonLabel) { |
106 | 109 | await github.rest.issues.addLabels({ |
107 | 110 | ...repo, |
108 | 111 | issue_number: pullRequest.number, |
109 | | - labels: ['violating-contribution-guidelines'], |
| 112 | + labels: ['violating-contribution-guidelines', reasonLabel], |
110 | 113 | }); |
111 | 114 |
|
112 | 115 | await github.rest.issues.createComment({ |
@@ -120,6 +123,8 @@ jobs: |
120 | 123 | pull_number: pullRequest.number, |
121 | 124 | state: 'closed', |
122 | 125 | }); |
| 126 | +
|
| 127 | + core.setOutput('was-closed', 'true'); |
123 | 128 | } |
124 | 129 |
|
125 | 130 | // --- Step 3: No issue references --- |
@@ -134,7 +139,7 @@ jobs: |
134 | 139 | '3. Once a maintainer has acknowledged your proposed approach, open a new PR referencing the issue', |
135 | 140 | '', |
136 | 141 | `Please review our [contributing guidelines](${contributingUrl}) for more details.`, |
137 | | - ].join('\n')); |
| 142 | + ].join('\n'), 'missing-issue-reference'); |
138 | 143 | return; |
139 | 144 | } |
140 | 145 |
|
@@ -222,7 +227,7 @@ jobs: |
222 | 227 | 'If you believe this assignment is outdated, please comment on the issue to discuss before opening a new PR.', |
223 | 228 | '', |
224 | 229 | `Please review our [contributing guidelines](${contributingUrl}) for more details.`, |
225 | | - ].join('\n')); |
| 230 | + ].join('\n'), 'issue-already-assigned'); |
226 | 231 | return; |
227 | 232 | } |
228 | 233 |
|
@@ -234,7 +239,7 @@ jobs: |
234 | 239 | 'To avoid wasted effort on both sides, please discuss your proposed approach in the issue first and wait for a maintainer to respond before opening a PR.', |
235 | 240 | '', |
236 | 241 | `Please review our [contributing guidelines](${contributingUrl}) for more details.`, |
237 | | - ].join('\n')); |
| 242 | + ].join('\n'), 'missing-maintainer-discussion'); |
238 | 243 | return; |
239 | 244 | } |
240 | 245 |
|
@@ -249,4 +254,74 @@ jobs: |
249 | 254 | '3. Once a maintainer has acknowledged your proposed approach, open a new PR referencing the issue', |
250 | 255 | '', |
251 | 256 | `Please review our [contributing guidelines](${contributingUrl}) for more details.`, |
252 | | - ].join('\n')); |
| 257 | + ].join('\n'), 'missing-issue-reference'); |
| 258 | +
|
| 259 | + enforce-draft: |
| 260 | + name: Enforce Draft PR |
| 261 | + needs: [validate-non-maintainer-pr] |
| 262 | + if: | |
| 263 | + always() |
| 264 | + && github.event.pull_request.draft == false |
| 265 | + && needs.validate-non-maintainer-pr.outputs.was-closed != 'true' |
| 266 | + runs-on: ubuntu-24.04 |
| 267 | + permissions: |
| 268 | + pull-requests: write |
| 269 | + contents: write |
| 270 | + steps: |
| 271 | + - name: Generate GitHub App token |
| 272 | + id: app-token |
| 273 | + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2 |
| 274 | + with: |
| 275 | + app-id: ${{ vars.SDK_MAINTAINER_BOT_APP_ID }} |
| 276 | + private-key: ${{ secrets.SDK_MAINTAINER_BOT_PRIVATE_KEY }} |
| 277 | + |
| 278 | + - name: Convert PR to draft |
| 279 | + env: |
| 280 | + GH_TOKEN: ${{github.token}} |
| 281 | + PR_URL: ${{ github.event.pull_request.html_url }} |
| 282 | + run: | |
| 283 | + gh pr ready "$PR_URL" --undo |
| 284 | +
|
| 285 | + - name: Label and comment |
| 286 | + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 |
| 287 | + with: |
| 288 | + github-token: ${{ steps.app-token.outputs.token }} |
| 289 | + script: | |
| 290 | + const pullRequest = context.payload.pull_request; |
| 291 | + const repo = context.repo; |
| 292 | +
|
| 293 | + // Label the PR so maintainers can filter/track violations |
| 294 | + await github.rest.issues.addLabels({ |
| 295 | + ...repo, |
| 296 | + issue_number: pullRequest.number, |
| 297 | + labels: ['converted-to-draft'], |
| 298 | + }); |
| 299 | +
|
| 300 | + // Check for existing bot comment to avoid duplicates on reopen |
| 301 | + const comments = await github.rest.issues.listComments({ |
| 302 | + ...repo, |
| 303 | + issue_number: pullRequest.number, |
| 304 | + }); |
| 305 | + const botComment = comments.data.find(c => |
| 306 | + c.user.type === 'Bot' && |
| 307 | + c.body.includes('automatically converted to draft') |
| 308 | + ); |
| 309 | + if (botComment) { |
| 310 | + core.info('Bot comment already exists, skipping.'); |
| 311 | + return; |
| 312 | + } |
| 313 | +
|
| 314 | + const contributingUrl = `https://github.com/${repo.owner}/${repo.repo}/blob/master/CONTRIBUTING.md`; |
| 315 | +
|
| 316 | + await github.rest.issues.createComment({ |
| 317 | + ...repo, |
| 318 | + issue_number: pullRequest.number, |
| 319 | + body: [ |
| 320 | + `This PR has been automatically converted to draft. All PRs must start as drafts per our [contributing guidelines](${contributingUrl}).`, |
| 321 | + '', |
| 322 | + '**Next steps:**', |
| 323 | + '1. Ensure CI passes', |
| 324 | + '2. Fill in the PR description completely', |
| 325 | + '3. Mark as "Ready for review" when you\'re done' |
| 326 | + ].join('\n') |
| 327 | + }); |