name: PR Gate on: pull_request_target: types: [opened] jobs: check-contributor: runs-on: ubuntu-latest permissions: contents: read issues: write pull-requests: write steps: - name: Check if contributor is approved uses: actions/github-script@v7 with: script: | const prAuthor = context.payload.pull_request.user.login; const defaultBranch = context.payload.repository.default_branch; // Skip bots if (prAuthor.endsWith('[bot]') || prAuthor === 'dependabot[bot]') { console.log(`Skipping bot: ${prAuthor}`); return; } // Check if user is a collaborator (has write access) try { const { data: permissionLevel } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: prAuthor }); if (['admin', 'write'].includes(permissionLevel.permission)) { console.log(`${prAuthor} is a collaborator with ${permissionLevel.permission} access`); return; } } catch (e) { // User is not a collaborator, continue with check } // Fetch approved contributors list const { data: fileContent } = await github.rest.repos.getContent({ owner: context.repo.owner, repo: context.repo.repo, path: '.github/APPROVED_CONTRIBUTORS', ref: defaultBranch }); const content = Buffer.from(fileContent.content, 'base64').toString('utf8'); const approvedList = content .split('\n') .map(line => line.trim().toLowerCase()) .filter(line => line && !line.startsWith('#')); if (approvedList.includes(prAuthor.toLowerCase())) { console.log(`${prAuthor} is in the approved contributors list`); return; } // Not approved - close PR with comment console.log(`${prAuthor} is not approved, closing PR`); const message = [ `Hi @${prAuthor}, thanks for your interest in contributing!`, '', 'We ask new contributors to open an issue first before submitting a PR. This helps us discuss the approach and avoid wasted effort.', '', '**Next steps:**', '1. Open an issue describing what you want to change and why (keep it concise, write in your human voice, AI slop will be closed)', '2. Once a maintainer approves with `lgtm`, you\'ll be added to the approved contributors list', '3. Then you can submit your PR', '', `This PR will be closed automatically. See https://github.com/${context.repo.owner}/${context.repo.repo}/blob/${defaultBranch}/CONTRIBUTING.md for more details.` ].join('\n'); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, body: message }); await github.rest.pulls.update({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request.number, state: 'closed' });