bot-pr-checks.yml

 1name: Bot - PR Formatting Checks
 2
 3on:
 4  pull_request_target:
 5    types: [opened, edited, synchronize]
 6
 7jobs:
 8  check-pr:
 9    runs-on: ubuntu-latest
10    steps:
11      - name: Validate PR Title and Body
12        uses: actions/github-script@v9
13        env:
14          PR_BODY: ${{ github.event.pull_request.body }}
15          PR_TITLE: ${{ github.event.pull_request.title }}
16        with:
17          github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}
18          script: |
19            const title = process.env.PR_TITLE;
20            const body = process.env.PR_BODY || "";
21            const owner = context.repo.owner;
22            const repo = context.repo.repo;
23            const pull_number = context.issue.number;
24            
25            let errors = [];
26            
27            // 1. Check Conventional Commits and Title Length
28            const ccRegex = /^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9\-]+\))?:\s.+$/;
29            if (!ccRegex.test(title)) {
30              errors.push("- **Title**: Does not follow conventional commits (e.g., `feat: added something`, `fix(core): resolved crash`).");
31            }
32            if (title.length >= 40) {
33              errors.push(`- **Title**: Is too long (${title.length} characters). The PR title must be strictly under 40 characters.`);
34            }
35            
36            // 2. Check PR Template adherence
37            if (!body.includes("## What?") || !body.includes("## Why?")) {
38              errors.push("- **Body**: Missing the `## What?` or `## Why?` headings required by the PR template.");
39            }
40            
41            // Check if they just left the HTML comments from the template empty
42            const commentRegex = new RegExp("", "g");
43            const cleanedBody = body.replace(commentRegex, "").trim();
44            if (cleanedBody.length < 10) {
45              errors.push("- **Body**: The PR description is too short or hasn't replaced the template placeholders.");
46            }
47            
48            // 3. Request Changes or Dismiss previous requests
49            if (errors.length > 0) {
50              const message = `Hi @${context.actor}! Please fix the following issues with your PR:\n\n${errors.join("\n")}`;
51              
52              await github.rest.pulls.createReview({
53                owner,
54                repo,
55                pull_number,
56                body: message,
57                event: 'REQUEST_CHANGES'
58              });
59              
60              core.setFailed("PR formatting checks failed.");
61            } else {
62              // The PR is now valid. Let's find out who the bot is to dismiss its own reviews.
63              const { data: botUser } = await github.rest.users.getAuthenticated();
64              
65              // Fetch all reviews on this PR
66              const { data: reviews } = await github.rest.pulls.listReviews({
67                owner,
68                repo,
69                pull_number
70              });
71              
72              // Find active blocking reviews left by the bot account
73              const botReviews = reviews.filter(r => 
74                r.user.login === botUser.login && 
75                r.state === 'CHANGES_REQUESTED'
76              );
77              
78              // Dismiss them so the PR is unblocked
79              for (const review of botReviews) {
80                await github.rest.pulls.dismissReview({
81                  owner,
82                  repo,
83                  pull_number,
84                  review_id: review.id,
85                  message: 'Formatting issues have been resolved. Thank you!'
86                });
87              }
88            }