diff --git a/.github/workflows/community-tested.yml b/.github/workflows/community-tested.yml new file mode 100644 index 000000000..b0fce2200 --- /dev/null +++ b/.github/workflows/community-tested.yml @@ -0,0 +1,115 @@ +name: Community Tested Label + +on: + issue_comment: + types: [created] + +permissions: + issues: write + pull-requests: write + +jobs: + community-tested: + # Only run on PR comments containing /tested + if: > + github.event.issue.pull_request != null && + contains(github.event.comment.body, '/tested') + runs-on: ubuntu-latest + + steps: + - name: Apply community-tested label + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + const prNumber = context.issue.number; + const commenter = context.actor; + + // Fetch the PR to get the author + const { data: pr } = await github.rest.pulls.get({ + owner, repo, pull_number: prNumber, + }); + + const prAuthor = pr.user.login; + + // PR must be open + if (pr.state !== 'open') { + console.log('PR is not open, skipping.'); + return; + } + + // PR author cannot test their own PR + if (commenter === prAuthor) { + const { data: feedback } = await github.rest.issues.createComment({ + owner, repo, issue_number: prNumber, + body: `> โš ๏ธ @${commenter} โ€” The PR author cannot mark their own PR as \`/tested\`.`, + }); + // Delete after 8 seconds (best-effort) + await new Promise(r => setTimeout(r, 8000)); + await github.rest.issues.deleteComment({ + owner, repo, comment_id: feedback.id, + }).catch(() => {}); + return; + } + + // Collect all PR comments to find unique non-author /tested users + const comments = await github.paginate( + github.rest.issues.listComments, + { owner, repo, issue_number: prNumber, per_page: 100 }, + ); + + const testers = new Set( + comments + .filter(c => + c.body.includes('/tested') && + c.user.login !== prAuthor + ) + .map(c => c.user.login) + ); + + const THRESHOLD = 2; + const count = testers.size; + + console.log(`Unique testers so far: ${count} (${[...testers].join(', ')})`); + + // Check if label already applied + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + owner, repo, issue_number: prNumber, + }); + const alreadyLabelled = labels.some(l => l.name === 'community-tested'); + + if (count >= THRESHOLD && !alreadyLabelled) { + // Ensure the label exists in the repo + await github.rest.issues.createLabel({ + owner, repo, + name: 'community-tested', + color: '0075ca', + description: 'Tested by 2+ community members', + }).catch(() => {}); // ignore if already exists + + await github.rest.issues.addLabels({ + owner, repo, issue_number: prNumber, + labels: ['community-tested'], + }); + + await github.rest.issues.createComment({ + owner, repo, issue_number: prNumber, + body: `โœ… This PR has been marked as **community-tested** by ${count} contributors: ${[...testers].map(u => `@${u}`).join(', ')}.`, + }); + + console.log('Label applied.'); + } else if (count >= THRESHOLD && alreadyLabelled) { + console.log('Label already applied, nothing to do.'); + } else { + const remaining = THRESHOLD - count; + const { data: feedback } = await github.rest.issues.createComment({ + owner, repo, issue_number: prNumber, + body: `> ๐Ÿงช @${commenter} โ€” Thanks for testing! **${count}/${THRESHOLD}** unique testers so far. Need ${remaining} more.`, + }); + // Delete after 15 seconds + await new Promise(r => setTimeout(r, 15000)); + await github.rest.issues.deleteComment({ + owner, repo, comment_id: feedback.id, + }).catch(() => {}); + }