vouch-check-pr.yml 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. name: vouch-check-pr
  2. on:
  3. pull_request_target:
  4. types: [opened]
  5. permissions:
  6. contents: read
  7. pull-requests: write
  8. jobs:
  9. check:
  10. runs-on: ubuntu-latest
  11. steps:
  12. - name: Check if PR author is denounced
  13. uses: actions/github-script@v7
  14. with:
  15. script: |
  16. const author = context.payload.pull_request.user.login;
  17. const prNumber = context.payload.pull_request.number;
  18. // Skip bots
  19. if (author.endsWith('[bot]')) {
  20. core.info(`Skipping bot: ${author}`);
  21. return;
  22. }
  23. // Read the VOUCHED.td file via API (no checkout needed)
  24. let content;
  25. try {
  26. const response = await github.rest.repos.getContent({
  27. owner: context.repo.owner,
  28. repo: context.repo.repo,
  29. path: '.github/VOUCHED.td',
  30. });
  31. content = Buffer.from(response.data.content, 'base64').toString('utf-8');
  32. } catch (error) {
  33. if (error.status === 404) {
  34. core.info('No .github/VOUCHED.td file found, skipping check.');
  35. return;
  36. }
  37. throw error;
  38. }
  39. // Parse the .td file for denounced users
  40. const denounced = new Map();
  41. for (const line of content.split('\n')) {
  42. const trimmed = line.trim();
  43. if (!trimmed || trimmed.startsWith('#')) continue;
  44. if (!trimmed.startsWith('-')) continue;
  45. const rest = trimmed.slice(1).trim();
  46. if (!rest) continue;
  47. const spaceIdx = rest.indexOf(' ');
  48. const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
  49. const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
  50. // Handle platform:username or bare username
  51. // Only match bare usernames or github: prefix (skip other platforms)
  52. const colonIdx = handle.indexOf(':');
  53. if (colonIdx !== -1) {
  54. const platform = handle.slice(0, colonIdx).toLowerCase();
  55. if (platform !== 'github') continue;
  56. }
  57. const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
  58. if (!username) continue;
  59. denounced.set(username.toLowerCase(), reason);
  60. }
  61. // Check if the author is denounced
  62. const reason = denounced.get(author.toLowerCase());
  63. if (reason === undefined) {
  64. core.info(`User ${author} is not denounced. Allowing PR.`);
  65. return;
  66. }
  67. // Author is denounced — close the PR
  68. await github.rest.issues.createComment({
  69. owner: context.repo.owner,
  70. repo: context.repo.repo,
  71. issue_number: prNumber,
  72. body: 'This pull request has been automatically closed.',
  73. });
  74. await github.rest.pulls.update({
  75. owner: context.repo.owner,
  76. repo: context.repo.repo,
  77. pull_number: prNumber,
  78. state: 'closed',
  79. });
  80. core.info(`Closed PR #${prNumber} from denounced user ${author}`);