From 05e4b7d57291e02208b70d208d25ae111283cdb8 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Fri, 13 Oct 2023 14:24:11 +0300 Subject: [PATCH] feat: added discord webhook (#12) --- .github/workflows/ecosystem-ci-selected.yml | 8 + .github/workflows/ecosystem-ci.yml | 8 + discord-webhook.ts | 173 ++++++++++++++++++++ nx-logo.png | Bin 0 -> 19105 bytes 4 files changed, 189 insertions(+) create mode 100644 discord-webhook.ts create mode 100644 nx-logo.png diff --git a/.github/workflows/ecosystem-ci-selected.yml b/.github/workflows/ecosystem-ci-selected.yml index ec7965d..a5ad013 100644 --- a/.github/workflows/ecosystem-ci-selected.yml +++ b/.github/workflows/ecosystem-ci-selected.yml @@ -60,3 +60,11 @@ jobs: STATUS: ${{ job.status }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: always() + run: pnpm tsx discord-webhook.ts + env: + WORKFLOW_NAME: ci-selected + SUITE: ${{ inputs.suite }} + STATUS: ${{ job.status }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ecosystem-ci.yml b/.github/workflows/ecosystem-ci.yml index f9ca464..0fcf231 100644 --- a/.github/workflows/ecosystem-ci.yml +++ b/.github/workflows/ecosystem-ci.yml @@ -62,3 +62,11 @@ jobs: STATUS: ${{ job.status }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: always() + run: pnpm tsx discord-webhook.ts + env: + WORKFLOW_NAME: ci + SUITE: ${{ matrix.suite }} + STATUS: ${{ job.status }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/discord-webhook.ts b/discord-webhook.ts new file mode 100644 index 0000000..78814c0 --- /dev/null +++ b/discord-webhook.ts @@ -0,0 +1,173 @@ +import fetch from 'node-fetch' +import { setupEnvironment } from './utils' + +type Status = 'success' | 'failure' | 'cancelled' +type Env = { + WORKFLOW_NAME?: string + SUITE?: string + STATUS?: Status + DISCORD_WEBHOOK_URL?: string +} + +const statusConfig = { + success: { + color: parseInt('57ab5a', 16), + emoji: ':white_check_mark:', + }, + failure: { + color: parseInt('e5534b', 16), + emoji: ':x:', + }, + cancelled: { + color: parseInt('768390', 16), + emoji: ':stop_button:', + }, +} + +async function run() { + if (!process.env.GITHUB_ACTIONS) { + throw new Error('This script can only run on GitHub Actions.') + } + if (!process.env.DISCORD_WEBHOOK_URL) { + console.warn( + "Skipped beacuse process.env.DISCORD_WEBHOOK_URL was empty or didn't exist", + ) + return + } + if (!process.env.GITHUB_TOKEN) { + console.warn( + "Not using a token because process.env.GITHUB_TOKEN was empty or didn't exist", + ) + } + + const env = process.env as Env + + assertEnv('WORKFLOW_NAME', env.WORKFLOW_NAME) + assertEnv('SUITE', env.SUITE) + assertEnv('STATUS', env.STATUS) + assertEnv('DISCORD_WEBHOOK_URL', env.DISCORD_WEBHOOK_URL) + + await setupEnvironment() + const nxText = await nxRepoInfo() + + const webhookContent = { + username: `nx-ecosystem-ci (${env.WORKFLOW_NAME})`, + avatar_url: + 'https://raw.githubusercontent.com/nrwl/nx-ecosystem-ci/main/nx-logo.png', + embeds: [ + { + title: `${statusConfig[env.STATUS].emoji} ${env.SUITE}`, + description: await createDescription(env.SUITE, nxText), + color: statusConfig[env.STATUS].color, + }, + ], + } + + const res = await fetch(env.DISCORD_WEBHOOK_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(webhookContent), + }) + if (res.ok) { + console.log('Sent Webhook') + } else { + console.error(`Webhook failed ${res.status}:`, await res.text()) + } +} + +function assertEnv( + name: string, + value: T, +): asserts value is Exclude { + if (!value) { + throw new Error(`process.env.${name} is empty or does not exist.`) + } +} + +async function createRunUrl(suite: string) { + const result = await fetchJobs() + if (!result) { + return undefined + } + + if (result.total_count <= 0) { + console.warn('total_count was 0') + return undefined + } + + const job = result.jobs.find((job) => job.name === process.env.GITHUB_JOB) + if (job) { + return job.html_url + } + + // when matrix + const jobM = result.jobs.find( + (job) => job.name === `${process.env.GITHUB_JOB} (${suite})`, + ) + return jobM?.html_url +} + +interface GitHubActionsJob { + name: string + html_url: string +} + +async function fetchJobs() { + const url = `${process.env.GITHUB_API_URL}/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}/jobs` + const res = await fetch(url, { + headers: { + Accept: 'application/vnd.github.v3+json', + ...(process.env.GITHUB_TOKEN + ? { + Authorization: `token ${process.env.GITHUB_TOKEN}`, + // eslint-disable-next-line no-mixed-spaces-and-tabs + } + : undefined), + }, + }) + if (!res.ok) { + console.warn( + `Failed to fetch jobs (${res.status} ${res.statusText}): ${res.text()}`, + ) + return null + } + + const result = await res.json() + return result as { + total_count: number + jobs: GitHubActionsJob[] + } +} + +async function createDescription(suite: string, targetText: string) { + const runUrl = await createRunUrl(suite) + const open = runUrl === undefined ? 'Null' : `[Open](${runUrl})` + + return ` +:scroll:\u00a0\u00a0${open}\u3000\u3000:zap:\u00a0\u00a0${targetText} +`.trim() +} + +async function nxRepoInfo() { + const repoText = 'nrwl/nx' + const nextVersion = await nextNxVersion() + + const link = `https://github.com/nrwl/nx/commits/${nextVersion}` + return `[${repoText}@${nextVersion}](${link})` +} + +async function nextNxVersion(): Promise { + return fetch(`https://registry.npmjs.org/nx`) + .then((response) => response.json()) + .then( + (jsonData) => + (jsonData as any)?.['dist-tags']?.['next'] ?? + (jsonData as any)?.['dist-tags']?.['latest'], + ) +} + +run().catch((e) => { + console.error('Error sending webhook:', e) +}) diff --git a/nx-logo.png b/nx-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..dad82d69f554b2bd9a947e8a26638d404451da98 GIT binary patch literal 19105 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 literal 0 HcmV?d00001