From 85afcf037d6e1b36524521d00f0dd5a0d3e00aa7 Mon Sep 17 00:00:00 2001 From: smmariquit Date: Fri, 3 Jul 2026 16:57:17 +0800 Subject: [PATCH 1/2] feat(notifications): Discord embed for proposal.reviewed outcomes Post approve/reject/needs-changes updates to #contributors when room-tba emits proposal.reviewed on admin review routes. Closes #4 --- docs/notifications.md | 83 +++++++++++++++---------- src/notifications/delivery/discord.ts | 73 +++++++++++++++++++--- src/notifications/notifications.test.ts | 31 ++++++++- src/notifications/types.ts | 13 ++++ src/server.test.ts | 25 ++++++++ 5 files changed, 184 insertions(+), 41 deletions(-) diff --git a/docs/notifications.md b/docs/notifications.md index b96f0d4..a40565a 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -39,25 +39,44 @@ type NotificationEvent = { | submitterName | string | | isAnonymous | boolean | +Routes to **`#contributors`**. + +## proposal.reviewed payload + +Posted from room-tba admin approve/reject/request-changes routes. + +| Field | Type | Notes | +| ------------- | -------------- | --------------------------------------- | +| proposalId | number | | +| outcome | string | `approved`, `rejected`, `needs_changes` | +| entityType | string | | +| entityId | number | `0` for create proposals | +| entityLabel | string | | +| submitterName | string | | +| reviewedBy | string | editor display name | +| adminNote | string \| null | reject / needs-changes note | + +Routes to **`#contributors`** (green approve, red reject, amber needs-changes). + ## CI E2E payloads (`ci.e2e.*`, `ci.staging-*`) Posted from room-tba `discord-notify-e2e.yml`. Routes to `#development` except `ci.staging-smoke.failed` → `#deploys`. -| Field | Type | Notes | -| ----- | ---- | ----- | -| workflow | string | e.g. `E2E`, `E2E staging` | -| workflowUrl | string | GitHub Actions run URL | -| branch | string | head branch | -| commitSha | string | full SHA | -| prNumber | number \| null | PR workflows only | -| prUrl | string \| null | | -| suite | string | `blocking`, `advisory`, `staging-smoke` | -| failedStep | string \| null | first failed job step name | -| integrationFailed | boolean | true when integration step failed | -| failedTests | string[] | optional; v2 | -| artifactName | string \| null | Playwright report artifact | -| durationSeconds | number | | -| trigger | string | `pull_request`, `push`, `schedule`, … | +| Field | Type | Notes | +| ----------------- | -------------- | --------------------------------------- | +| workflow | string | e.g. `E2E`, `E2E staging` | +| workflowUrl | string | GitHub Actions run URL | +| branch | string | head branch | +| commitSha | string | full SHA | +| prNumber | number \| null | PR workflows only | +| prUrl | string \| null | | +| suite | string | `blocking`, `advisory`, `staging-smoke` | +| failedStep | string \| null | first failed job step name | +| integrationFailed | boolean | true when integration step failed | +| failedTests | string[] | optional; v2 | +| artifactName | string \| null | Playwright report artifact | +| durationSeconds | number | | +| trigger | string | `pull_request`, `push`, `schedule`, … | ## Test inventory (`ci.test_inventory.updated`) @@ -65,23 +84,23 @@ Posted from room-tba [discord-test-inventory.yml](https://github.com/uplbtools/r The bot **edits in place** the pinned inventory message: one summary embed plus **separate tier embeds** (unit, Vitest, integration, E2E smoke/browse/admin/advisory/staging) listing every spec file inline — no download required. -| Field | Type | Notes | -| ----- | ---- | ----- | -| repo | string | e.g. `uplbtools/room-tba` | -| branch | string | ref name | -| commitSha | string | full SHA | -| commitUrl | string | GitHub commit link | -| docUrl | string | `docs/test-inventory.md` on GitHub | -| workflowUrl | string \| null | Actions run | -| generated | string | `YYYY-MM-DD` | -| total | number | all `*.test.ts` / `*.spec.ts` files | -| unit | number | Bun unit count | -| vitest | number | store + component | -| integration | number | | -| e2eBlocking | number | | -| e2eAdvisory | number | | -| e2eStaging | number | | -| tiers | object | File paths per tier (`unit`, `store`, `component`, `integration`, `e2eBlockingSmoke`, `e2eBlockingBrowse`, `e2eBlockingAdmin`, `e2eAdvisory`, `e2eStaging`) | +| Field | Type | Notes | +| ----------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| repo | string | e.g. `uplbtools/room-tba` | +| branch | string | ref name | +| commitSha | string | full SHA | +| commitUrl | string | GitHub commit link | +| docUrl | string | `docs/test-inventory.md` on GitHub | +| workflowUrl | string \| null | Actions run | +| generated | string | `YYYY-MM-DD` | +| total | number | all `*.test.ts` / `*.spec.ts` files | +| unit | number | Bun unit count | +| vitest | number | store + component | +| integration | number | | +| e2eBlocking | number | | +| e2eAdvisory | number | | +| e2eStaging | number | | +| tiers | object | File paths per tier (`unit`, `store`, `component`, `integration`, `e2eBlockingSmoke`, `e2eBlockingBrowse`, `e2eBlockingAdmin`, `e2eAdvisory`, `e2eStaging`) | ## Leaderboard API (room-tba, future) diff --git a/src/notifications/delivery/discord.ts b/src/notifications/delivery/discord.ts index 7c4b2e5..f4e0b6f 100644 --- a/src/notifications/delivery/discord.ts +++ b/src/notifications/delivery/discord.ts @@ -11,7 +11,12 @@ import { } from "../github-routing.js"; import { deliverTestInventory } from "../test-inventory.js"; import type { TestInventoryPayload } from "../test-inventory.js"; -import type { NotificationEvent, ProposalSubmittedPayload } from "../types.js"; +import type { + NotificationEvent, + ProposalReviewedPayload, + ProposalReviewOutcome, + ProposalSubmittedPayload, +} from "../types.js"; const seenKeys = new Map(); const IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1000; @@ -45,7 +50,9 @@ async function sendToChannel( await (channel as TextChannel).send(content); } -function proposalSubmittedEmbed(payload: ProposalSubmittedPayload): EmbedBuilder { +function proposalSubmittedEmbed( + payload: ProposalSubmittedPayload, +): EmbedBuilder { const reviewUrl = `${ROOM_TBA_BASE}/?editor=login`; return new EmbedBuilder() .setColor(0x7c2d12) @@ -66,6 +73,41 @@ function proposalSubmittedEmbed(payload: ProposalSubmittedPayload): EmbedBuilder .setFooter({ text: BOT_FOOTER }); } +const REVIEW_OUTCOME_META: Record< + ProposalReviewOutcome, + { title: string; color: number } +> = { + approved: { title: "Proposal approved", color: 0x16a34a }, + rejected: { title: "Proposal rejected", color: 0xdc2626 }, + needs_changes: { title: "Changes requested", color: 0xd97706 }, +}; + +function proposalReviewedEmbed(payload: ProposalReviewedPayload): EmbedBuilder { + const meta = REVIEW_OUTCOME_META[payload.outcome]; + const embed = new EmbedBuilder() + .setColor(meta.color) + .setTitle(meta.title) + .setDescription( + `**${payload.entityLabel}** (${payload.entityType} #${payload.entityId})`, + ) + .addFields( + { name: "Submitted by", value: payload.submitterName, inline: true }, + { name: "Reviewed by", value: payload.reviewedBy, inline: true }, + { name: "Proposal ID", value: String(payload.proposalId), inline: true }, + ) + .setURL(ROOM_TBA_BASE) + .setFooter({ text: BOT_FOOTER }); + + if (payload.adminNote?.trim()) { + embed.addFields({ + name: "Editor note", + value: payload.adminNote.trim().slice(0, 1024), + }); + } + + return embed; +} + export async function deliverToDiscord( client: Client, event: NotificationEvent, @@ -83,6 +125,13 @@ export async function deliverToDiscord( }); break; } + case "proposal.reviewed": { + const payload = event.payload as ProposalReviewedPayload; + await sendToChannel(client, config.channelContributorsId, { + embeds: [proposalReviewedEmbed(payload)], + }); + break; + } case "deploy.succeeded": case "deploy.failed": { const isProd = @@ -92,7 +141,9 @@ export async function deliverToDiscord( const embed = new EmbedBuilder() .setColor(color) .setTitle( - event.type === "deploy.succeeded" ? "Deploy succeeded" : "Deploy failed", + event.type === "deploy.succeeded" + ? "Deploy succeeded" + : "Deploy failed", ) .setDescription( String(event.payload.url ?? event.payload.name ?? "Vercel deploy"), @@ -105,7 +156,9 @@ export async function deliverToDiscord( }, { name: "Branch", - value: String(event.payload.branch ?? event.payload.gitBranch ?? "—"), + value: String( + event.payload.branch ?? event.payload.gitBranch ?? "—", + ), inline: true, }, ) @@ -127,9 +180,13 @@ export async function deliverToDiscord( } case "release.published": { const route = githubDiscordRoute("release.published"); - const channelId = route ? channelIdForGithubRoute(config, route) : config.channelAnnouncementsId; + const channelId = route + ? channelIdForGithubRoute(config, route) + : config.channelAnnouncementsId; await sendToChannel(client, channelId, { - embeds: [githubActivityEmbed(event.type, event.payload, event.occurredAt)], + embeds: [ + githubActivityEmbed(event.type, event.payload, event.occurredAt), + ], }); break; } @@ -169,7 +226,9 @@ export async function deliverToDiscord( const route = githubDiscordRoute(event.type); if (!route) break; await sendToChannel(client, channelIdForGithubRoute(config, route), { - embeds: [githubActivityEmbed(event.type, event.payload, event.occurredAt)], + embeds: [ + githubActivityEmbed(event.type, event.payload, event.occurredAt), + ], }); break; } diff --git a/src/notifications/notifications.test.ts b/src/notifications/notifications.test.ts index 4db3b3a..d14252c 100644 --- a/src/notifications/notifications.test.ts +++ b/src/notifications/notifications.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from "bun:test"; -import { translateGitHubRelease, translateVercelWebhook } from "./translators/index.js"; +import { + translateGitHubRelease, + translateVercelWebhook, +} from "./translators/index.js"; import { notificationEventSchema } from "./types.js"; describe("notificationEventSchema", () => { @@ -15,6 +18,27 @@ describe("notificationEventSchema", () => { expect(result.success).toBe(true); }); + test("accepts proposal.reviewed envelope", () => { + const result = notificationEventSchema.safeParse({ + schemaVersion: 1, + type: "proposal.reviewed", + source: "room-tba", + occurredAt: new Date().toISOString(), + idempotencyKey: "proposal:1:reviewed:approved", + payload: { + proposalId: 1, + outcome: "approved", + entityType: "room", + entityId: 2, + entityLabel: "ICS-255", + submitterName: "Test", + reviewedBy: "Editor", + adminNote: null, + }, + }); + expect(result.success).toBe(true); + }); + test("rejects wrong schema version", () => { const result = notificationEventSchema.safeParse({ schemaVersion: 2, @@ -96,7 +120,10 @@ describe("translateGitHubRelease", () => { test("ignores non-published actions", () => { expect( - translateGitHubRelease({ action: "created", release: { tag_name: "v1" } }), + translateGitHubRelease({ + action: "created", + release: { tag_name: "v1" }, + }), ).toBeNull(); }); }); diff --git a/src/notifications/types.ts b/src/notifications/types.ts index e3b5758..371b63e 100644 --- a/src/notifications/types.ts +++ b/src/notifications/types.ts @@ -41,3 +41,16 @@ export type ProposalSubmittedPayload = { submitterName: string; isAnonymous: boolean; }; + +export type ProposalReviewOutcome = "approved" | "rejected" | "needs_changes"; + +export type ProposalReviewedPayload = { + proposalId: number; + outcome: ProposalReviewOutcome; + entityType: string; + entityId: number; + entityLabel: string; + submitterName: string; + reviewedBy: string; + adminNote: string | null; +}; diff --git a/src/server.test.ts b/src/server.test.ts index b123fc1..ab737ec 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -40,4 +40,29 @@ describe("createServer", () => { expect(res.status).toBe(200); expect(res.body).toEqual({ ok: true }); }); + + test("POST /notifications accepts proposal.reviewed envelope", async () => { + const app = createServer(mockClient); + const res = await request(app) + .post("/notifications") + .send({ + schemaVersion: 1, + type: "proposal.reviewed", + source: "room-tba", + occurredAt: new Date().toISOString(), + idempotencyKey: "proposal:1:reviewed:approved", + payload: { + proposalId: 1, + outcome: "approved", + entityType: "room", + entityId: 2, + entityLabel: "ICS-255", + submitterName: "Test", + reviewedBy: "Editor", + adminNote: null, + }, + }); + expect(res.status).toBe(200); + expect(res.body).toEqual({ ok: true }); + }); }); From 7a1f78329b9ca505e1d68284ae9ad5716da3bf50 Mon Sep 17 00:00:00 2001 From: smmariquit Date: Fri, 3 Jul 2026 16:59:30 +0800 Subject: [PATCH 2/2] style: biome check --write (CI lint) --- scripts/post-channel-routing-pins.ts | 8 +++--- src/auth/maintainer.test.ts | 5 +--- src/http/verify-vercel-signature.test.ts | 2 +- src/notifications/ci-embeds.ts | 4 ++- src/notifications/delivery/discord.ts | 27 +++++-------------- src/notifications/github-embeds.ts | 27 +++++++++++++++---- src/notifications/github-routing.test.ts | 2 +- src/notifications/notifications.test.ts | 5 +--- src/notifications/test-inventory.test.ts | 20 +++++--------- src/notifications/test-inventory.ts | 15 +++-------- .../translators/github-repo.test.ts | 12 +++++++-- src/notifications/translators/github-repo.ts | 5 +++- src/server.ts | 4 +-- 13 files changed, 64 insertions(+), 72 deletions(-) diff --git a/scripts/post-channel-routing-pins.ts b/scripts/post-channel-routing-pins.ts index 4066bbe..9be3186 100644 --- a/scripts/post-channel-routing-pins.ts +++ b/scripts/post-channel-routing-pins.ts @@ -23,7 +23,8 @@ const PINS: ChannelPin[] = [ { id: process.env.CHANNEL_PRS_REVIEWS_ID ?? "1519351243229630637", name: "#prs-and-reviews", - content: "**PR reviews**\n\n• `pull_request_review` — approve, request changes, comment", + content: + "**PR reviews**\n\n• `pull_request_review` — approve, request changes, comment", }, { id: process.env.CHANNEL_DEVELOPMENT_ID ?? "1522313215927783587", @@ -67,10 +68,7 @@ if (process.env.CHANNEL_TEST_SUITE_ID?.trim()) { const API = "https://discord.com/api/v10"; -async function discord( - path: string, - init?: RequestInit, -): Promise { +async function discord(path: string, init?: RequestInit): Promise { return fetch(`${API}${path}`, { ...init, headers: { diff --git a/src/auth/maintainer.test.ts b/src/auth/maintainer.test.ts index 4944588..d89fe3e 100644 --- a/src/auth/maintainer.test.ts +++ b/src/auth/maintainer.test.ts @@ -20,9 +20,6 @@ describe("memberRoleIds", () => { }, } as Parameters[0]; - expect(memberRoleIds(interaction)).toEqual([ - "maintainer-role-id", - "@everyone", - ]); + expect(memberRoleIds(interaction)).toEqual(["maintainer-role-id", "@everyone"]); }); }); diff --git a/src/http/verify-vercel-signature.test.ts b/src/http/verify-vercel-signature.test.ts index 493a25c..12ff363 100644 --- a/src/http/verify-vercel-signature.test.ts +++ b/src/http/verify-vercel-signature.test.ts @@ -1,5 +1,5 @@ -import { createHmac } from "node:crypto"; import { describe, expect, test } from "bun:test"; +import { createHmac } from "node:crypto"; import { verifyVercelSignature } from "./verify-vercel-signature.js"; describe("verifyVercelSignature", () => { diff --git a/src/notifications/ci-embeds.ts b/src/notifications/ci-embeds.ts index c4afb71..72dcf19 100644 --- a/src/notifications/ci-embeds.ts +++ b/src/notifications/ci-embeds.ts @@ -130,7 +130,9 @@ export function ciE2eEmbed( return embed; } -export function channelIdForCiEvent(type: NotificationEvent["type"]): "development" | "deploys" { +export function channelIdForCiEvent( + type: NotificationEvent["type"], +): "development" | "deploys" { if (type === "ci.staging-smoke.failed") return "deploys"; return "development"; } diff --git a/src/notifications/delivery/discord.ts b/src/notifications/delivery/discord.ts index f4e0b6f..3464097 100644 --- a/src/notifications/delivery/discord.ts +++ b/src/notifications/delivery/discord.ts @@ -5,12 +5,9 @@ import { BOT_FOOTER, ROOM_TBA_BASE } from "../../constants.js"; import { log } from "../../log.js"; import { channelIdForCiEvent, ciE2eEmbed } from "../ci-embeds.js"; import { githubActivityEmbed } from "../github-embeds.js"; -import { - channelIdForGithubRoute, - githubDiscordRoute, -} from "../github-routing.js"; -import { deliverTestInventory } from "../test-inventory.js"; +import { channelIdForGithubRoute, githubDiscordRoute } from "../github-routing.js"; import type { TestInventoryPayload } from "../test-inventory.js"; +import { deliverTestInventory } from "../test-inventory.js"; import type { NotificationEvent, ProposalReviewedPayload, @@ -50,9 +47,7 @@ async function sendToChannel( await (channel as TextChannel).send(content); } -function proposalSubmittedEmbed( - payload: ProposalSubmittedPayload, -): EmbedBuilder { +function proposalSubmittedEmbed(payload: ProposalSubmittedPayload): EmbedBuilder { const reviewUrl = `${ROOM_TBA_BASE}/?editor=login`; return new EmbedBuilder() .setColor(0x7c2d12) @@ -141,9 +136,7 @@ export async function deliverToDiscord( const embed = new EmbedBuilder() .setColor(color) .setTitle( - event.type === "deploy.succeeded" - ? "Deploy succeeded" - : "Deploy failed", + event.type === "deploy.succeeded" ? "Deploy succeeded" : "Deploy failed", ) .setDescription( String(event.payload.url ?? event.payload.name ?? "Vercel deploy"), @@ -156,9 +149,7 @@ export async function deliverToDiscord( }, { name: "Branch", - value: String( - event.payload.branch ?? event.payload.gitBranch ?? "—", - ), + value: String(event.payload.branch ?? event.payload.gitBranch ?? "—"), inline: true, }, ) @@ -184,9 +175,7 @@ export async function deliverToDiscord( ? channelIdForGithubRoute(config, route) : config.channelAnnouncementsId; await sendToChannel(client, channelId, { - embeds: [ - githubActivityEmbed(event.type, event.payload, event.occurredAt), - ], + embeds: [githubActivityEmbed(event.type, event.payload, event.occurredAt)], }); break; } @@ -226,9 +215,7 @@ export async function deliverToDiscord( const route = githubDiscordRoute(event.type); if (!route) break; await sendToChannel(client, channelIdForGithubRoute(config, route), { - embeds: [ - githubActivityEmbed(event.type, event.payload, event.occurredAt), - ], + embeds: [githubActivityEmbed(event.type, event.payload, event.occurredAt)], }); break; } diff --git a/src/notifications/github-embeds.ts b/src/notifications/github-embeds.ts index 5463bae..8004fbc 100644 --- a/src/notifications/github-embeds.ts +++ b/src/notifications/github-embeds.ts @@ -7,7 +7,10 @@ function actionLabel(action: string, merged?: boolean): string { return action.replace(/_/g, " "); } -function linkField(name: string, url: unknown): { name: string; value: string; inline: boolean } | null { +function linkField( + name: string, + url: unknown, +): { name: string; value: string; inline: boolean } | null { const u = String(url ?? ""); if (!u.startsWith("http")) return null; return { name, value: u, inline: false }; @@ -50,7 +53,11 @@ export function githubActivityEmbed( case "github.pull_request": { const action = actionLabel(String(payload.action ?? ""), payload.merged === true); const color = - payload.merged === true ? 0x16a34a : payload.action === "closed" ? 0x6b7280 : 0x1d4ed8; + payload.merged === true + ? 0x16a34a + : payload.action === "closed" + ? 0x6b7280 + : 0x1d4ed8; embed .setColor(color) .setTitle(`PR ${action}: #${payload.number}`) @@ -69,7 +76,11 @@ export function githubActivityEmbed( case "github.pull_request_review": { const state = String(payload.reviewState ?? "review"); const color = - state === "approved" ? 0x16a34a : state === "changes_requested" ? 0xdc2626 : 0x1d4ed8; + state === "approved" + ? 0x16a34a + : state === "changes_requested" + ? 0xdc2626 + : 0x1d4ed8; embed .setColor(color) .setTitle(`PR review ${state.replace(/_/g, " ")}: #${payload.prNumber}`) @@ -104,7 +115,9 @@ export function githubActivityEmbed( embed .setColor(0xdc2626) .setTitle(`Workflow failed: ${payload.workflow}`) - .setDescription(`\`${payload.branch ?? "?"}\` @ \`${payload.commitSha ?? "?"}\``) + .setDescription( + `\`${payload.branch ?? "?"}\` @ \`${payload.commitSha ?? "?"}\``, + ) .addFields( { name: "Repo", value: repo, inline: true }, { name: "Trigger", value: String(payload.trigger ?? "—"), inline: true }, @@ -190,7 +203,11 @@ export function githubActivityEmbed( case "github.deployment_status": { const state = String(payload.state ?? ""); const color = - state === "success" ? 0x16a34a : state === "failure" || state === "error" ? 0xdc2626 : 0x6b7280; + state === "success" + ? 0x16a34a + : state === "failure" || state === "error" + ? 0xdc2626 + : 0x6b7280; embed .setColor(color) .setTitle(`Deployment ${state}: ${payload.environment ?? "environment"}`) diff --git a/src/notifications/github-routing.test.ts b/src/notifications/github-routing.test.ts index 52ddd1d..9e61d93 100644 --- a/src/notifications/github-routing.test.ts +++ b/src/notifications/github-routing.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { channelIdForGithubRoute, githubDiscordRoute } from "./github-routing.js"; import type { BotConfig } from "../config.js"; +import { channelIdForGithubRoute, githubDiscordRoute } from "./github-routing.js"; const cfg = { channelGithubId: "github-ch", diff --git a/src/notifications/notifications.test.ts b/src/notifications/notifications.test.ts index d14252c..fddf0d6 100644 --- a/src/notifications/notifications.test.ts +++ b/src/notifications/notifications.test.ts @@ -1,8 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { - translateGitHubRelease, - translateVercelWebhook, -} from "./translators/index.js"; +import { translateGitHubRelease, translateVercelWebhook } from "./translators/index.js"; import { notificationEventSchema } from "./types.js"; describe("notificationEventSchema", () => { diff --git a/src/notifications/test-inventory.test.ts b/src/notifications/test-inventory.test.ts index cac41de..25bcd01 100644 --- a/src/notifications/test-inventory.test.ts +++ b/src/notifications/test-inventory.test.ts @@ -34,10 +34,7 @@ const samplePayload = { describe("testInventorySummaryEmbed", () => { test("includes tier counts and doc link", () => { - const embed = testInventorySummaryEmbed( - samplePayload, - "2026-07-03T00:00:00.000Z", - ); + const embed = testInventorySummaryEmbed(samplePayload, "2026-07-03T00:00:00.000Z"); expect(embed.data.title).toBe("Room TBA test inventory"); const fields = embed.data.fields ?? []; @@ -51,22 +48,17 @@ describe("testInventorySummaryEmbed", () => { describe("buildTestInventoryEmbeds", () => { test("builds summary plus tier embeds without attachment", () => { - const embeds = buildTestInventoryEmbeds( - samplePayload, - "2026-07-03T00:00:00.000Z", - ); + const embeds = buildTestInventoryEmbeds(samplePayload, "2026-07-03T00:00:00.000Z"); expect(embeds.length).toBeGreaterThan(1); expect(embeds[0]?.data.title).toBe("Room TBA test inventory"); - expect( - embeds.some((e) => e.data.title?.startsWith("Unit tests (Bun)")), - ).toBe(true); + expect(embeds.some((e) => e.data.title?.startsWith("Unit tests (Bun)"))).toBe(true); expect( embeds.some((e) => e.data.description?.includes("src/lib/foo.test.ts")), ).toBe(true); - expect( - embeds.some((e) => e.data.title?.startsWith("E2E blocking — smoke")), - ).toBe(true); + expect(embeds.some((e) => e.data.title?.startsWith("E2E blocking — smoke"))).toBe( + true, + ); }); }); diff --git a/src/notifications/test-inventory.ts b/src/notifications/test-inventory.ts index 9b652e1..8ed49e2 100644 --- a/src/notifications/test-inventory.ts +++ b/src/notifications/test-inventory.ts @@ -1,9 +1,4 @@ -import { - EmbedBuilder, - type Client, - type Message, - type TextChannel, -} from "discord.js"; +import { type Client, EmbedBuilder, type Message, type TextChannel } from "discord.js"; import { config } from "../config.js"; import { BOT_FOOTER } from "../constants.js"; import { log } from "../log.js"; @@ -85,9 +80,7 @@ export function tierFileEmbeds( const partCount = chunks.length; return chunks.map((chunk, index) => { const title = - partCount > 1 - ? `${titleBase} (${index + 1}/${partCount})` - : titleBase; + partCount > 1 ? `${titleBase} (${index + 1}/${partCount})` : titleBase; return new EmbedBuilder() .setColor(color) .setTitle(title) @@ -232,9 +225,7 @@ export function buildTestInventoryEmbeds( }); } - const embeds: EmbedBuilder[] = [ - testInventorySummaryEmbed(payload, occurredAt), - ]; + const embeds: EmbedBuilder[] = [testInventorySummaryEmbed(payload, occurredAt)]; for (const section of sections) { embeds.push(...tierFileEmbeds(section.title, section.files, section.color)); diff --git a/src/notifications/translators/github-repo.test.ts b/src/notifications/translators/github-repo.test.ts index 677df52..0a70308 100644 --- a/src/notifications/translators/github-repo.test.ts +++ b/src/notifications/translators/github-repo.test.ts @@ -136,8 +136,16 @@ describe("translateGitHubRepoWebhook", () => { { action: "submitted", repository: { full_name: "uplbtools/room-tba" }, - pull_request: { number: 5, title: "Fix", html_url: "https://github.com/x/pull/5" }, - review: { state: "approved", user: { login: "dev" }, html_url: "https://github.com/x#review" }, + pull_request: { + number: 5, + title: "Fix", + html_url: "https://github.com/x/pull/5", + }, + review: { + state: "approved", + user: { login: "dev" }, + html_url: "https://github.com/x#review", + }, }, "delivery-9", ); diff --git a/src/notifications/translators/github-repo.ts b/src/notifications/translators/github-repo.ts index 34ebdb0..77aa904 100644 --- a/src/notifications/translators/github-repo.ts +++ b/src/notifications/translators/github-repo.ts @@ -246,7 +246,10 @@ export function translateGitHubRepoWebhook( }; } - if (eventName === "dependabot_alert" || eventName === "repository_vulnerability_alert") { + if ( + eventName === "dependabot_alert" || + eventName === "repository_vulnerability_alert" + ) { const action = String(data.action ?? ""); if (!["created", "fixed", "reintroduced", "reopened", "create"].includes(action)) { return null; diff --git a/src/server.ts b/src/server.ts index 6105fdb..9ad5774 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,7 +2,9 @@ import type { Client } from "discord.js"; import express from "express"; import { config } from "./config.js"; import { setLastDeployEvent } from "./deploy-cache.js"; +import { verifyGitHubSignature } from "./http/verify-github-signature.js"; import { verifySecret } from "./http/verify-secret.js"; +import { verifyVercelSignature } from "./http/verify-vercel-signature.js"; import { log } from "./log.js"; import { routeNotification } from "./notifications/router.js"; import { @@ -10,8 +12,6 @@ import { translateGitHubRepoWebhook, translateVercelWebhook, } from "./notifications/translators/index.js"; -import { verifyGitHubSignature } from "./http/verify-github-signature.js"; -import { verifyVercelSignature } from "./http/verify-vercel-signature.js"; import { notificationEventSchema } from "./notifications/types.js"; export function createServer(client: Client): express.Application {