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@v7
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
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
33            // 2. Check PR Template adherence
34            if (!body.includes("## What?") || !body.includes("## Why?")) {
35              errors.push("- **Body**: Missing the `## What?` or `## Why?` headings required by the PR template.");
36            }
37
38            // Check if they just left the HTML comments from the template empty
39            const cleanedBody = body.replace(//g, "").trim();
40            if (cleanedBody.length < 10) {
41              errors.push("- **Body**: The PR description is too short or hasn't replaced the template placeholders.");
42            }
43
44            // 3. Request Changes or Dismiss previous requests
45            if (errors.length > 0) {
46              const message = `Hi @${context.actor}! Please fix the following issues with your PR:\n\n${errors.join("\n")}`;
47
48              await github.rest.pulls.createReview({
49                owner,
50                repo,
51                pull_number,
52                body: message,
53                event: 'REQUEST_CHANGES'
54              });
55
56              core.setFailed("PR formatting checks failed.");
57            } else {
58              // The PR is now valid. Let's find out who the bot is to dismiss its own reviews.
59              const { data: botUser } = await github.rest.users.getAuthenticated();
60
61              // Fetch all reviews on this PR
62              const { data: reviews } = await github.rest.pulls.listReviews({
63                owner,
64                repo,
65                pull_number
66              });
67
68              // Find active blocking reviews left by the bot account
69              const botReviews = reviews.filter(r =>
70                r.user.login === botUser.login &&
71                r.state === 'CHANGES_REQUESTED'
72              );
73
74              // Dismiss them so the PR is unblocked
75              for (const review of botReviews) {
76                await github.rest.pulls.dismissReview({
77                  owner,
78                  repo,
79                  pull_number,
80                  review_id: review.id,
81                  message: 'Formatting issues have been resolved. Thank you!'
82                });
83              }
84            }