| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- name: Check pull request template
- # Runs on pull_request_target so the token can comment, label, and close pull
- # requests that come from forks. Only the base branch is checked out, and the
- # script reads nothing but the pull request body, so the head commit never runs.
- #
- # Run it by hand from the Actions tab to check existing pull requests: give a
- # number to check one, or leave the field empty to check every open pull request.
- on:
- pull_request_target:
- types: [opened, edited, reopened]
- workflow_dispatch:
- inputs:
- pull_request:
- description: "Pull request number (empty = all open pull requests)"
- required: false
- dry_run:
- description: "Dry run: log what would happen, change nothing"
- type: boolean
- default: false
- permissions:
- pull-requests: write
- jobs:
- template:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{ github.event.repository.default_branch }}
- sparse-checkout: .github/scripts
- - uses: actions/github-script@v7
- env:
- INPUT_PULL_REQUEST: ${{ github.event.inputs.pull_request }}
- DRY_RUN: ${{ github.event.inputs.dry_run }}
- with:
- script: |
- const { checkTemplate } = require("./.github/scripts/check-pr-template.js");
- const { owner, repo } = context.repo;
- const SKIP = ["wsvincent", "jefftriplett", "dependabot[bot]"];
- const LABEL = "needs-template";
- const MARKER = "<!-- pr-template-check -->";
- const dry = process.env.DRY_RUN === "true";
- if (dry) core.info("Dry run: nothing will be commented, labeled, closed, or reopened.");
- async function check(pr) {
- const issue_number = pr.number;
- if (SKIP.includes(pr.user.login)) {
- core.info(`#${issue_number}: skipped (${pr.user.login})`);
- return;
- }
- const { missing, company, hasPricing } = checkTemplate(pr.body);
- const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number, per_page: 100 });
- const existing = comments.find((c) => c.body && c.body.includes(MARKER));
- async function say(body) {
- body = `${MARKER}\n${body}`;
- if (dry) {
- core.info(`#${issue_number}: would ${existing ? "update" : "post"} comment:\n${body}`);
- } else if (existing) {
- await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
- } else {
- await github.rest.issues.createComment({ owner, repo, issue_number, body });
- }
- }
- if (missing.length) {
- core.info(`#${issue_number}: missing ${missing.length} items`);
- const list = missing.map((m) => `- ${m}`).join("\n");
- await say(
- `Thanks for the submission! This pull request does not fill out the ` +
- `[pull request template](https://github.com/${owner}/${repo}/blob/main/.github/pull_request_template.md), ` +
- `so it was closed automatically.\n\nMissing:\n${list}\n\n` +
- `Edit the pull request description to add the missing items. ` +
- `The check runs again on every edit and reopens the pull request when the template is complete.`
- );
- if (dry) {
- core.info(`#${issue_number}: would add label "${LABEL}"${pr.state === "open" ? " and close the pull request" : ""}`);
- return;
- }
- await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [LABEL] });
- if (pr.state === "open") {
- await github.rest.pulls.update({ owner, repo, pull_number: issue_number, state: "closed" });
- }
- return;
- }
- core.info(`#${issue_number}: complete`);
- const hadLabel = pr.labels.some((l) => l.name === LABEL);
- if (hadLabel && dry) {
- core.info(`#${issue_number}: would remove label "${LABEL}"${pr.state === "closed" && !pr.merged_at ? " and reopen the pull request" : ""}`);
- } else if (hadLabel) {
- await github.rest.issues.removeLabel({ owner, repo, issue_number, name: LABEL }).catch(() => {});
- if (pr.state === "closed" && !pr.merged_at) {
- await github.rest.pulls.update({ owner, repo, pull_number: issue_number, state: "open" });
- }
- }
- let note = "The pull request template is complete. Thanks! A maintainer will review it.";
- if (company) {
- note +=
- `\n\nThis submission is on behalf of a company. Please read the ` +
- `[commercial products and services](https://github.com/${owner}/${repo}/blob/main/contributing.md#commercial-products-and-services) ` +
- `section of the contribution guidelines.`;
- if (!hasPricing) {
- note +=
- `\n\nIf this is a paid product, please add a link to your public pricing page under ` +
- `question 5 of the template, or a short note on how it is priced.`;
- }
- }
- if (existing || company) await say(note);
- }
- if (context.payload.pull_request) {
- await check(context.payload.pull_request);
- } else if (process.env.INPUT_PULL_REQUEST) {
- const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: Number(process.env.INPUT_PULL_REQUEST) });
- await check(pr);
- } else {
- const prs = await github.paginate(github.rest.pulls.list, { owner, repo, state: "open", per_page: 100 });
- for (const pr of prs) await check(pr);
- }
|