close-external-prs.yml 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. name: Close External PRs
  2. on:
  3. pull_request_target:
  4. types: [opened]
  5. permissions:
  6. pull-requests: write
  7. issues: write
  8. contents: read
  9. jobs:
  10. check-membership:
  11. if: vars.DISABLE_EXTERNAL_PR_CHECK != 'true'
  12. runs-on: ubuntu-latest
  13. steps:
  14. # pull_request_target: checks out the BASE repo (trusted), so the allowlist + shared
  15. # script below are this repo's versions, never the fork's.
  16. - uses: actions/checkout@v4
  17. - name: Close PR unless author is a member or the PR is an in-scope external contribution
  18. uses: actions/github-script@v7
  19. with:
  20. script: |
  21. const author = context.payload.pull_request.user.login;
  22. const { evaluate, isExemptAuthor } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
  23. // Members (write/admin) and the repo's own automation bot (bump SHA PRs) are never
  24. // auto-closed.
  25. const ex = await isExemptAuthor({ github, context });
  26. if (ex.exempt) {
  27. console.log(`${ex.reason} — allowing PR`);
  28. return;
  29. }
  30. // Non-member: allow the PR to stay open ONLY if it is an in-scope external
  31. // contribution — it adds marketplace.json entries whose source repo ALREADY backs
  32. // a live plugin here, and changes nothing else. (No maintained allowlist: the set
  33. // of allowed repos is derived from the live marketplace.) This grants only the
  34. // right to open a reviewable PR; the validate + scan checks and a maintainer
  35. // approval still gate the merge (the External PR Scope Guard is advisory signal,
  36. // not a required check).
  37. const result = await evaluate({ github, context });
  38. if (result.ok && result.added.length > 0) {
  39. console.log(`In-scope external contribution (adds: ${result.added.join(', ')}) — allowing PR.`);
  40. return;
  41. }
  42. console.log(`Closing PR from ${author}: ${result.problems.join('; ') || 'out of scope'}`);
  43. await github.rest.issues.createComment({
  44. owner: context.repo.owner,
  45. repo: context.repo.repo,
  46. issue_number: context.payload.pull_request.number,
  47. body: `Thanks for your interest! This repo only accepts contributions from Anthropic team members. If you'd like to submit a plugin to the marketplace, please submit your plugin [here](https://clau.de/plugin-directory-submission).`
  48. });
  49. await github.rest.pulls.update({
  50. owner: context.repo.owner,
  51. repo: context.repo.repo,
  52. pull_number: context.payload.pull_request.number,
  53. state: 'closed'
  54. });