Skip to content

Rename + Comment

Rename + Comment #2080

name: Rename + Comment
on:
workflow_run:
workflows:
- Validate
types:
- completed
permissions: {}
jobs:
comment:
# Only leave comments on PRs
if: github.event.workflow_run.event == 'pull_request'
name: PR comment
runs-on: ubuntu-latest
permissions:
actions: read
checks: read
pull-requests: write
steps:
- name: Generate comment + rename PR if needed
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
// Find associated PR for this workflow run, as workflow_run.pull_requests is empty when from a fork.
const pr = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
head: `${context.payload.workflow_run.head_repository.owner.login}:${context.payload.workflow_run.head_branch}`,
sort: "updated",
direction: "desc",
per_page: 100,
}).then(r => r.data[0]);
if (!pr) throw new Error("Could not find associated PR for workflow run");
// Skip Dependabot PRs as they skip validation
if (pr.user.login === "dependabot[bot]") return;
// Locate the validation checks for the workflow run
const workflowRun = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const checks = await github.rest.checks.listForSuite({
owner: context.repo.owner,
repo: context.repo.repo,
check_suite_id: workflowRun.data.check_suite_id,
});
const cnamesCheck = checks.data.check_runs.find(
check => check.name === "cnames_active.js"
);
if (!cnamesCheck) throw new Error("Could not find cnames_active.js check run");
const descriptionCheck = checks.data.check_runs.find(
check => check.name === "PR description"
);
if (!descriptionCheck) throw new Error("Could not find PR description check run");
const messages = [];
// ----------------------------------------------------
// 1. cnames_active.js validation result
// ----------------------------------------------------
if (cnamesCheck.conclusion === "failure") {
messages.push(
"**❌ `cnames_active.js` validation failed due to being incorrectly formatted.**",
`_[Check file annotations for failure details](${pr.html_url}/files)._`,
"", "---", "",
);
}
// ----------------------------------------------------
// 2. PR description validation result
// ----------------------------------------------------
if (descriptionCheck.conclusion === "failure") {
const annotations = await github.rest.checks.listAnnotations({
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: descriptionCheck.id,
});
if (!annotations.data[0]) {
throw new Error("Could not find annotations for PR description check run");
}
messages.push(
"**❌ PR description validation failed due to being incorrectly formatted.**",
`_[Use the provided template and ensure all information is provided](https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${context.payload.repository.default_branch}/PULL_REQUEST_TEMPLATE.md)._`,
"",
annotations.data[0].message.split("\n").slice(1).join("\n"),
"", "---", "",
);
}
// ----------------------------------------------------
// 3. Final validation message
// ----------------------------------------------------
if (messages.length) {
messages.push(
"**⚠️ You must resolve these issues before your JS.org subdomain request can be accepted.**",
"_[Edit your PR to fix the issues, do not create a new PR](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request#making-changes-to-files-in-your-pull-request)._",
);
} else {
messages.push("✅ All validations passed! Please wait for the JS.org maintainers to process your subdomain request.");
}
// ----------------------------------------------------
// 4. PR RENAMING (independent of description check)
// ----------------------------------------------------
if (cnamesCheck.conclusion === "success") {
const comparison = await github.rest.repos.compareCommits({
owner: context.repo.owner,
repo: context.repo.repo,
base: pr.base.sha,
head: pr.head.sha,
});
const file = comparison.data.files.find(
f => f.filename === "cnames_active.js"
);
if (file?.patch) {
const keys = new Set();
for (const line of file.patch.split("\n")) {
if (!line.startsWith("+")) continue;
const match = line.match(/^\+\s*"([^"]+)"\s*:/);
if (match) keys.add(match[1]);
}
if (keys.size === 1) {
const [subdomain] = [...keys];
const expectedTitle = `${subdomain}.js.org`;
if (pr.title.trim() !== expectedTitle) {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
title: expectedTitle,
});
}
}
}
}
// ----------------------------------------------------
// 5. Create or update comment
// ----------------------------------------------------
const body = `<!-- jsorg-validation -->\n\n${messages.join("\n")}`;
const existingComment = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
}).then(r =>
r.data.find(c =>
c.user.login === "github-actions[bot]" &&
c.body.startsWith("<!-- jsorg-validation -->")
)
);
if (existingComment) {
if (existingComment.body !== body) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body,
});
}
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body,
});
}