From 494a19d32e1fd2eda111149d9b484540accef49b Mon Sep 17 00:00:00 2001 From: Kian Newman-Hazel Date: Thu, 27 Feb 2025 20:48:49 +0000 Subject: [PATCH] [Docs Site] Post PR comment when CI fails --- .github/workflows/ci.yml | 7 ++ bin/post-pr-ci-failure-comment/constants.ts | 4 + bin/post-pr-ci-failure-comment/index.ts | 101 ++++++++++++++++++++ package-lock.json | 8 ++ package.json | 1 + 5 files changed, 121 insertions(+) create mode 100644 bin/post-pr-ci-failure-comment/constants.ts create mode 100644 bin/post-pr-ci-failure-comment/index.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4da57414843a8de..07e381de046f83f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,3 +57,10 @@ jobs: - name: Tests run: npm run test + + - name: Post PR CI failure comment + if: always() + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx tsx bin/post-pr-ci-failure-comment/index.ts diff --git a/bin/post-pr-ci-failure-comment/constants.ts b/bin/post-pr-ci-failure-comment/constants.ts new file mode 100644 index 000000000000000..66bc4bbc2309479 --- /dev/null +++ b/bin/post-pr-ci-failure-comment/constants.ts @@ -0,0 +1,4 @@ +export const GITHUB_ACTIONS_BOT_ID = 41898282; +export const DOCS_BASE_URL = "https://developers.cloudflare.com"; +export const CONTENT_BASE_PATH = "src/content"; +export const PREVIEW_URL_REGEX = /^\*\*Preview URL:\*\* (.*)$/m; diff --git a/bin/post-pr-ci-failure-comment/index.ts b/bin/post-pr-ci-failure-comment/index.ts new file mode 100644 index 000000000000000..d4fd6fcd53a08c5 --- /dev/null +++ b/bin/post-pr-ci-failure-comment/index.ts @@ -0,0 +1,101 @@ +import * as core from "@actions/core"; +import * as github from "@actions/github"; +import type { PullRequestEvent } from "@octokit/webhooks-types"; + +import { GITHUB_ACTIONS_BOT_ID } from "./constants"; + +async function run(): Promise { + try { + if (!process.env.GITHUB_TOKEN) { + core.setFailed(`Could not find GITHUB_TOKEN in env`); + process.exit(); + } + + const octokit = github.getOctokit(process.env.GITHUB_TOKEN); + const payload = github.context.payload as PullRequestEvent; + + const { owner, repo } = github.context.repo; + const pullRequestNumber = payload.number; + const runId = github.context.runId; + + const { data: run } = await octokit.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: runId, + }); + + const job = run.jobs.findLast((job) => job.name === "Compiles"); + + if (!job) { + core.setFailed(`Could not find a job called 'Compiles'`); + process.exit(); + } + + const failedStep = job.steps?.find((step) => step.conclusion === "failure"); + + if (failedStep) { + core.info(`Found failed step ${failedStep.name}`); + } + + const conclusion = failedStep ? "failure" : "success"; + + const { data: comments } = await octokit.rest.issues.listComments({ + owner, + repo, + issue_number: pullRequestNumber, + per_page: 100, + }); + + const existingComment = comments.find( + (comment) => + comment.user?.id === GITHUB_ACTIONS_BOT_ID && + comment.body?.includes("**CI run failed:**"), + ); + + if (existingComment) { + core.info(`Found existing comment with ID ${existingComment.id}`); + } else { + core.info(`No existing comment found`); + } + + const url = `https://github.com/${owner}/${repo}/actions/runs/${runId}/job/${job.id}`; + const comment = `**CI run failed:** [build logs](${url})`; + + if (conclusion === "failure") { + if (existingComment) { + core.info( + `Updating ${existingComment.id} with ${JSON.stringify(comment)}`, + ); + await octokit.rest.issues.updateComment({ + owner, + repo, + comment_id: existingComment.id, + body: comment, + }); + } else { + core.info(`Creating new comment with ${JSON.stringify(comment)}`); + await octokit.rest.issues.createComment({ + owner, + repo, + issue_number: pullRequestNumber, + body: comment, + }); + } + } else if (conclusion === "success" && existingComment) { + core.info(`Removing ${existingComment.id}`); + await octokit.rest.issues.deleteComment({ + owner, + repo, + issue_number: pullRequestNumber, + comment_id: existingComment.id, + }); + } + } catch (error) { + if (error instanceof Error) { + core.setFailed(error.message); + } + process.exit(); + } +} + +run(); diff --git a/package-lock.json b/package-lock.json index 8ef5b2b77c6a7bf..854f9443f876204 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@expressive-code/plugin-collapsible-sections": "0.40.2", "@iarna/toml": "2.2.5", "@marsidev/react-turnstile": "1.1.0", + "@octokit/webhooks-types": "7.6.1", "@stoplight/json-schema-tree": "4.0.0", "@types/hast": "3.0.4", "@types/he": "1.2.3", @@ -3131,6 +3132,13 @@ "@octokit/openapi-types": "^23.0.1" } }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "dev": true, + "license": "MIT" + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", diff --git a/package.json b/package.json index 09b6b0b78e86c2d..c52c791331a1320 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@expressive-code/plugin-collapsible-sections": "0.40.2", "@iarna/toml": "2.2.5", "@marsidev/react-turnstile": "1.1.0", + "@octokit/webhooks-types": "7.6.1", "@stoplight/json-schema-tree": "4.0.0", "@types/hast": "3.0.4", "@types/he": "1.2.3",