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 0000000..dad82d6 Binary files /dev/null and b/nx-logo.png differ