compliance-close.yml 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. name: compliance-close
  2. on:
  3. schedule:
  4. # Run every 30 minutes to check for expired compliance windows
  5. - cron: "*/30 * * * *"
  6. workflow_dispatch:
  7. permissions:
  8. contents: read
  9. issues: write
  10. pull-requests: write
  11. jobs:
  12. close-non-compliant:
  13. runs-on: ubuntu-latest
  14. steps:
  15. - name: Close non-compliant issues and PRs after 2 hours
  16. uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
  17. with:
  18. script: |
  19. const { data: items } = await github.rest.issues.listForRepo({
  20. owner: context.repo.owner,
  21. repo: context.repo.repo,
  22. labels: 'needs:compliance',
  23. state: 'open',
  24. per_page: 100,
  25. });
  26. if (items.length === 0) {
  27. core.info('No open issues/PRs with needs:compliance label');
  28. return;
  29. }
  30. const now = Date.now();
  31. const twoHours = 2 * 60 * 60 * 1000;
  32. const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
  33. for (const item of items) {
  34. const isPR = !!item.pull_request;
  35. const kind = isPR ? 'PR' : 'issue';
  36. if (teamAssociations.includes(item.author_association)) {
  37. core.info(`Skipping ${kind} #${item.number}: author association is ${item.author_association}`);
  38. try {
  39. await github.rest.issues.removeLabel({
  40. owner: context.repo.owner,
  41. repo: context.repo.repo,
  42. issue_number: item.number,
  43. name: 'needs:compliance',
  44. });
  45. } catch (e) {}
  46. continue;
  47. }
  48. const { data: comments } = await github.rest.issues.listComments({
  49. owner: context.repo.owner,
  50. repo: context.repo.repo,
  51. issue_number: item.number,
  52. });
  53. const complianceComment = comments.find(c => c.body.includes('<!-- issue-compliance -->'));
  54. if (!complianceComment) continue;
  55. const commentAge = now - new Date(complianceComment.created_at).getTime();
  56. if (commentAge < twoHours) {
  57. core.info(`${kind} #${item.number} still within 2-hour window (${Math.round(commentAge / 60000)}m elapsed)`);
  58. continue;
  59. }
  60. const closeMessage = isPR
  61. ? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new pull request that follows our guidelines.'
  62. : 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new issue that follows our issue templates.';
  63. await github.rest.issues.createComment({
  64. owner: context.repo.owner,
  65. repo: context.repo.repo,
  66. issue_number: item.number,
  67. body: closeMessage,
  68. });
  69. try {
  70. await github.rest.issues.removeLabel({
  71. owner: context.repo.owner,
  72. repo: context.repo.repo,
  73. issue_number: item.number,
  74. name: 'needs:compliance',
  75. });
  76. } catch (e) {}
  77. if (isPR) {
  78. await github.rest.pulls.update({
  79. owner: context.repo.owner,
  80. repo: context.repo.repo,
  81. pull_number: item.number,
  82. state: 'closed',
  83. });
  84. } else {
  85. await github.rest.issues.update({
  86. owner: context.repo.owner,
  87. repo: context.repo.repo,
  88. issue_number: item.number,
  89. state: 'closed',
  90. state_reason: 'not_planned',
  91. });
  92. }
  93. core.info(`Closed non-compliant ${kind} #${item.number} after 2-hour window`);
  94. }