From cd783fc6165693b1de8a9e903d3da3809e0b3a5e Mon Sep 17 00:00:00 2001 From: William Harrison <87287585+wdhdev@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:10:23 +0800 Subject: [PATCH 1/3] Enhance PR workflow with template validation Updated PR workflow to include additional event types and added a template validation step. --- .github/workflows/pr.yml | 246 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..d596986cc4 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,246 @@ +name: PR + +on: + pull_request_target: + types: + - opened + - reopened + - edited + - synchronize + - labeled + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + label: + name: Label + runs-on: ubuntu-latest + + steps: + - name: Check changed paths + id: changes + uses: dorny/paths-filter@v3 + with: + filters: | + ci: + - ".github/workflows/*" + - "tests/*" + - "util/*" + - "package.json" + - "package-lock.json" + - "dnsconfig.js" + domain: + - "domains/*" + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ github.event.repository.default_branch }} + + - name: Check trusted user + id: check + env: + PR_AUTHOR_ID: ${{ github.event.pull_request.user.id }} + run: | + node - <<'NODE' + const trustedUsers = require("./util/trusted.json"); + + const authorId = Number(process.env.PR_AUTHOR_ID); + + if (!trustedUsers.some((u) => u.id === authorId)) { + console.log("PR author is not a trusted user."); + process.exit(1); + } + + console.log("PR author is a trusted user."); + NODE + + - name: Add labels + uses: actions/github-script@v8 + env: + CI_CHANGED: ${{ steps.changes.outputs.ci }} + DOMAIN_CHANGED: ${{ steps.changes.outputs.domain }} + MAINTAINER: ${{ steps.check.outcome == 'success' }} + with: + github-token: ${{ secrets.BOT }} + script: | + const labels = []; + + if (process.env.CI_CHANGED === "true") + labels.push("ci"); + + if (process.env.DOMAIN_CHANGED === "true") + labels.push("domain"); + + if (process.env.MAINTAINER === "true") + labels.push("maintainer"); + + if (labels.length) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels + }); + } + + template: + name: Template + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ github.event.repository.default_branch }} + + - name: Validate PR template + id: validate + continue-on-error: true + env: + PR_BODY: ${{ github.event.pull_request.body }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + PR_AUTHOR_ID: ${{ github.event.pull_request.user.id }} + PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }} + run: node util/check-pr-template.cjs + + - name: Handle incomplete PR + if: steps.validate.outcome == 'failure' + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.BOT }} + script: | + const label = "reason: incomplete pr"; + + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + color: "D93F0B", + description: "Your PR template is not complete. (e.g. missing the requirements form, missing website preview)" + }); + } else { + throw error; + } + } + + const labels = await github.rest.issues.listLabelsOnIssue({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + if (!labels.data.some((item) => item.name === label)) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [label] + }); + } + + const marker = ""; + + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + } + ); + + const alreadyCommented = comments.some( + (comment) => + comment.user?.login === "is-a-dev-bot" && + comment.body?.includes(marker) + ); + + if (!alreadyCommented) { + const body = [ + marker, + "## ❌ Incomplete PR template", + "", + "This pull request is missing required information from the PR template.", + "", + "***Your PR will not be approved without the template being properly filled out!***", + "", + "Please make sure that:", + "", + "- The PR template has not been modified.", + " - *Modifying the PR template will make this check fail.*", + "- All requirement checkboxes are checked.", + " - *All checkboxes must be changed exactly to `[x]`, NOT something like `[ x ]`.*", + "- **Website Preview** is filled out.", + " - *Make sure it is filled between the start/end comment markers.*", + "- **Website Purpose** is filled out.", + " - *Make sure it is filled between the start/end comment markers.*", + "---", + "Once the template has been properly completed, this check will run again automatically." + ].join("\n"); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } + + - name: Remove incomplete label and comment + if: steps.validate.outcome == 'success' + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.BOT }} + script: | + const label = "reason: incomplete pr"; + const marker = ""; + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: label + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + + const comments = await github.paginate( + github.rest.issues.listComments, + { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + } + ); + + for (const comment of comments) { + if ( + comment.user?.login === "is-a-dev-bot" && + comment.body?.includes(marker) + ) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id + }); + } + } + + - name: Fail if PR template is incomplete + if: steps.validate.outcome == 'failure' + run: | + echo "::error::PR template is incomplete." + exit 1 From aa1a997741e88525e2cad2493106d69175ad24b8 Mon Sep 17 00:00:00 2001 From: William Harrison <87287585+wdhdev@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:20:22 +0800 Subject: [PATCH 2/3] Update pr.yml --- .github/workflows/pr.yml | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d596986cc4..06539107bb 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,13 +34,25 @@ jobs: - "dnsconfig.js" domain: - "domains/*" + documentation: + - ".github/PULL_REQUEST_TEMPLATE.md" + - ".github/copilot-instructions.md" + - "README.md" + r-william: + - ".github/CODEOWNERS" + - "CODE_OF_CONDUCT.md" + - "CONTRIBUTING.md" + - "SECURITY.md" + - "TERMS_OF_SERVICE.md" + - "LICENSE" - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: ref: ${{ github.event.repository.default_branch }} - name: Check trusted user - id: check + id: check-trusted + continue-on-error: true env: PR_AUTHOR_ID: ${{ github.event.pull_request.user.id }} run: | @@ -61,28 +73,32 @@ jobs: uses: actions/github-script@v8 env: CI_CHANGED: ${{ steps.changes.outputs.ci }} + DOCUMENTATION: ${{ steps.changes.outputs.documentation }} DOMAIN_CHANGED: ${{ steps.changes.outputs.domain }} - MAINTAINER: ${{ steps.check.outcome == 'success' }} + MAINTAINER: ${{ steps.check-trusted.outcome == 'success' }} + R_WILLIAM: ${{ steps.changes.outputs.r-william }} with: github-token: ${{ secrets.BOT }} script: | const labels = []; - if (process.env.CI_CHANGED === "true") + if (process.env.CI_CHANGED === "true") { labels.push("ci"); + labels.push("r: william"); + } + if (process.env.DOCUMENTATION === "true") labels.push("documentation"); + if (process.env.DOMAIN_CHANGED === "true") labels.push("domain"); + if (process.env.MAINTAINER === "true") labels.push("maintainer"); + if (process.env.R_WILLIAM === "true") labels.push("r: william"); - if (process.env.DOMAIN_CHANGED === "true") - labels.push("domain"); + const uniqueLabels = [...new Set(labels)]; - if (process.env.MAINTAINER === "true") - labels.push("maintainer"); - - if (labels.length) { + if (uniqueLabels.length) { await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - labels + labels: uniqueLabels }); } From 706fb0c18db11cb062e92ead9741ce1f5b88c720 Mon Sep 17 00:00:00 2001 From: William Harrison <87287585+wdhdev@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:20:49 +0800 Subject: [PATCH 3/3] Delete .github/workflows/pr-template.yml --- .github/workflows/pr-template.yml | 175 ------------------------------ 1 file changed, 175 deletions(-) delete mode 100644 .github/workflows/pr-template.yml diff --git a/.github/workflows/pr-template.yml b/.github/workflows/pr-template.yml deleted file mode 100644 index 5a46992a46..0000000000 --- a/.github/workflows/pr-template.yml +++ /dev/null @@ -1,175 +0,0 @@ -name: PR Template - -on: - pull_request_target: - types: - - opened - - reopened - - edited - - synchronize - - labeled - -permissions: - contents: read - pull-requests: write - issues: write - -jobs: - validate: - name: Validate - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - ref: ${{ github.event.repository.default_branch }} - - - name: Validate PR template - id: validate - continue-on-error: true - env: - PR_BODY: ${{ github.event.pull_request.body }} - PR_AUTHOR: ${{ github.event.pull_request.user.login }} - PR_AUTHOR_ID: ${{ github.event.pull_request.user.id }} - PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }} - run: node util/check-pr-template.cjs - - - name: Handle incomplete PR - if: steps.validate.outcome == 'failure' - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.BOT }} - script: | - const label = "reason: incomplete pr"; - - try { - await github.rest.issues.getLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label - }); - } catch (error) { - if (error.status === 404) { - await github.rest.issues.createLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label, - color: "D93F0B", - description: "Your PR template is not complete. (e.g. missing the requirements form, missing website preview)" - }); - } else { - throw error; - } - } - - const labels = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - }); - - if (!labels.data.some((item) => item.name === label)) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: [label] - }); - } - - const marker = ""; - - const comments = await github.paginate( - github.rest.issues.listComments, - { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - } - ); - - const alreadyCommented = comments.some( - (comment) => - comment.user?.login === "is-a-dev-bot" && - comment.body?.includes(marker) - ); - - if (!alreadyCommented) { - const body = [ - marker, - "## ❌ Incomplete PR template", - "", - "This pull request is missing required information from the PR template.", - "", - "***Your PR will not be approved without the template being properly filled out!***", - "", - "Please make sure that:", - "", - "- The PR template has not been modified.", - " - *Modifying the PR template will make this check fail.*", - "- All requirement checkboxes are checked.", - " - *All checkboxes must be changed exactly to `[x]`, NOT something like `[ x ]`.*", - "- **Website Preview** is filled out.", - " - *Make sure it is filled between the start/end comment markers.*", - "- **Website Purpose** is filled out.", - " - *Make sure it is filled between the start/end comment markers.*", - "---", - "Once the template has been properly completed, this check will run again automatically." - ].join("\n"); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body - }); - } - - - name: Remove incomplete label and comment - if: steps.validate.outcome == 'success' - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.BOT }} - script: | - const label = "reason: incomplete pr"; - const marker = ""; - - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - name: label - }); - } catch (error) { - if (error.status !== 404) { - throw error; - } - } - - const comments = await github.paginate( - github.rest.issues.listComments, - { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - } - ); - - for (const comment of comments) { - if ( - comment.user?.login === "is-a-dev-bot" && - comment.body?.includes(marker) - ) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: comment.id - }); - } - } - - - name: Fail if PR template is incomplete - if: steps.validate.outcome == 'failure' - run: | - echo "::error::PR template is incomplete." - exit 1