diff --git a/README.md b/README.md index 96a18b0..b2dd230 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# git-webhooks +# patchbay Containerized Bun service for GitHub and jojo.build webhooks. @@ -6,10 +6,13 @@ Containerized Bun service for GitHub and jojo.build webhooks. ```text GET /healthz -POST /git-webhooks/jojo -POST /git-webhooks/github +POST /patchbay/jojo +POST /patchbay/github ``` +Existing `/git-webhooks/jojo` and `/git-webhooks/github` routes remain +compatibility aliases for existing webhook registrations. + ## Environment ```text diff --git a/bun.lock b/bun.lock index 51b29cb..9eece58 100644 --- a/bun.lock +++ b/bun.lock @@ -3,7 +3,7 @@ "configVersion": 1, "workspaces": { "": { - "name": "@peezy.tech/git-webhooks", + "name": "@peezy.tech/patchbay", "devDependencies": { "@types/bun": "latest", "typescript": "^5.9.3", diff --git a/package.json b/package.json index b4a2c13..52bc8e6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@peezy.tech/git-webhooks", + "name": "@peezy.tech/patchbay", "version": "0.1.0", "private": true, "type": "module", diff --git a/src/discord.ts b/src/discord.ts index 7d9aa47..1e2bfb7 100644 --- a/src/discord.ts +++ b/src/discord.ts @@ -132,7 +132,7 @@ export function buildDiscordPayload(input: DiscordNotification): DiscordPayload ].filter((item): item is DiscordEmbedField => item !== null); return { - username: "git-webhooks", + username: "patchbay", embeds: [ { title: feedTitle(signal).slice(0, 256), @@ -166,7 +166,7 @@ export function buildDiscordPayload(input: DiscordNotification): DiscordPayload ].filter((item): item is DiscordEmbedField => item !== null); return { - username: "git-webhooks", + username: "patchbay", embeds: [ { title: eventTitle(event).slice(0, 256), @@ -175,7 +175,7 @@ export function buildDiscordPayload(input: DiscordNotification): DiscordPayload fields, timestamp: event.receivedAt, footer: { - text: "git-webhooks", + text: "patchbay", }, }, ], diff --git a/src/server.ts b/src/server.ts index 72dcdfa..b4c4a6b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -101,10 +101,10 @@ export function createHandler(config: ServerConfig): (request: Request) => Promi if (url.pathname === "/healthz") { return textResponse("ok\n"); } - if (url.pathname === "/git-webhooks/github") { + if (url.pathname === "/patchbay/github" || url.pathname === "/git-webhooks/github") { return handleGithub(request, config, store); } - if (url.pathname === "/git-webhooks/jojo") { + if (url.pathname === "/patchbay/jojo" || url.pathname === "/git-webhooks/jojo") { return handleJojo(request, config, store); } return jsonResponse({ error: "not_found" }, { status: 404 }); diff --git a/test/discord.test.ts b/test/discord.test.ts index fe2b8cb..88641d9 100644 --- a/test/discord.test.ts +++ b/test/discord.test.ts @@ -10,8 +10,8 @@ const pushEvent: GitWebhookEvent = { receivedAt: "2026-05-12T21:00:00.000Z", repo: { owner: "peezy-tech", - name: "git-webhooks", - fullName: "peezy-tech/git-webhooks", + name: "patchbay", + fullName: "peezy-tech/patchbay", }, sender: { username: "matamune", @@ -20,7 +20,7 @@ const pushEvent: GitWebhookEvent = { after: "0123456789abcdef", raw: { head_commit: { - url: "https://jojo.build/peezy-tech/git-webhooks/commit/0123456789abcdef", + url: "https://jojo.build/peezy-tech/patchbay/commit/0123456789abcdef", }, }, }; @@ -68,7 +68,7 @@ describe("discord notifications", () => { id: "jojo:delivery-1:main_push", kind: "main_push", provider: "jojo", - repoFullName: "peezy-tech/git-webhooks", + repoFullName: "peezy-tech/patchbay", ref: "refs/heads/main", sha: "0123456789abcdef", deliveryId: "delivery-1", @@ -76,9 +76,9 @@ describe("discord notifications", () => { }, }); - expect(payload.username).toBe("git-webhooks"); - expect(payload.embeds[0].title).toBe("[jojo] peezy-tech/git-webhooks push to main"); - expect(payload.embeds[0].url).toBe("https://jojo.build/peezy-tech/git-webhooks/commit/0123456789abcdef"); + expect(payload.username).toBe("patchbay"); + expect(payload.embeds[0].title).toBe("[jojo] peezy-tech/patchbay push to main"); + expect(payload.embeds[0].url).toBe("https://jojo.build/peezy-tech/patchbay/commit/0123456789abcdef"); expect(payload.embeds[0].fields).toContainEqual({ name: "Queued", value: "main_push", inline: true }); }); @@ -115,7 +115,7 @@ describe("discord notifications", () => { return new Response(null, { status: 204 }); }); - expect(JSON.parse(body).embeds[0].title).toBe("[jojo] peezy-tech/git-webhooks push to main"); + expect(JSON.parse(body).embeds[0].title).toBe("[jojo] peezy-tech/patchbay push to main"); }); test("throws on Discord failure", async () => { diff --git a/test/feed.test.ts b/test/feed.test.ts index 415b24d..b4ff84b 100644 --- a/test/feed.test.ts +++ b/test/feed.test.ts @@ -91,7 +91,7 @@ describe("feed watcher", () => { }); test("first poll primes state without emitting old entries", async () => { - const dataDir = await mkdtemp(join(tmpdir(), "git-webhooks-feed-")); + const dataDir = await mkdtemp(join(tmpdir(), "patchbay-feed-")); const sourcesPath = join(dataDir, "sources.json"); await writeFile(sourcesPath, JSON.stringify({ sources: [source] }), "utf8"); @@ -105,7 +105,7 @@ describe("feed watcher", () => { }); test("later polls emit new entries and release fork sync jobs", async () => { - const dataDir = await mkdtemp(join(tmpdir(), "git-webhooks-feed-")); + const dataDir = await mkdtemp(join(tmpdir(), "patchbay-feed-")); const sourcesPath = join(dataDir, "sources.json"); const releaseSource: FeedSourceConfig = { ...source, diff --git a/test/providers.test.ts b/test/providers.test.ts index 5eacfee..13bd086 100644 --- a/test/providers.test.ts +++ b/test/providers.test.ts @@ -3,10 +3,10 @@ import { normalizeGithubEvent } from "../src/providers/github"; import { normalizeJojoEvent } from "../src/providers/jojo"; const repository = { - name: "git-webhooks", - full_name: "peezy-tech/git-webhooks", - clone_url: "https://example.test/peezy-tech/git-webhooks.git", - ssh_url: "git@example.test:peezy-tech/git-webhooks.git", + name: "patchbay", + full_name: "peezy-tech/patchbay", + clone_url: "https://example.test/peezy-tech/patchbay.git", + ssh_url: "git@example.test:peezy-tech/patchbay.git", default_branch: "main", owner: { login: "peezy-tech" }, }; @@ -28,7 +28,7 @@ describe("provider normalization", () => { expect(event.provider).toBe("github"); expect(event.event).toBe("push"); - expect(event.repo?.fullName).toBe("peezy-tech/git-webhooks"); + expect(event.repo?.fullName).toBe("peezy-tech/patchbay"); expect(event.sender?.username).toBe("peezy"); }); diff --git a/test/server.test.ts b/test/server.test.ts index 3a8766e..bc782d9 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -23,15 +23,15 @@ async function signedRequest(path: string, provider: "github" | "jojo", secret: describe("server", () => { test("healthz returns ok", async () => { - const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", dataDir: await mkdtemp(join(tmpdir(), "git-webhooks-")) }); + const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", dataDir: await mkdtemp(join(tmpdir(), "patchbay-")) }); const response = await handler(new Request("http://localhost/healthz")); expect(response.status).toBe(200); expect(await response.text()).toBe("ok\n"); }); test("rejects invalid signatures", async () => { - const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", dataDir: await mkdtemp(join(tmpdir(), "git-webhooks-")) }); - const response = await handler(new Request("http://localhost/git-webhooks/github", { + const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", dataDir: await mkdtemp(join(tmpdir(), "patchbay-")) }); + const response = await handler(new Request("http://localhost/patchbay/github", { method: "POST", headers: { "x-hub-signature-256": "sha256=bad" }, body: "{}", @@ -40,14 +40,14 @@ describe("server", () => { }); test("accepts jojo main pushes and queues a job", async () => { - const dataDir = await mkdtemp(join(tmpdir(), "git-webhooks-")); + const dataDir = await mkdtemp(join(tmpdir(), "patchbay-")); const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", dataDir }); - const request = await signedRequest("/git-webhooks/jojo", "jojo", "jojo", { + const request = await signedRequest("/patchbay/jojo", "jojo", "jojo", { ref: "refs/heads/main", after: "abc123", repository: { - name: "git-webhooks", - full_name: "peezy-tech/git-webhooks", + name: "patchbay", + full_name: "peezy-tech/patchbay", owner: { username: "peezy-tech" }, }, }); @@ -58,12 +58,30 @@ describe("server", () => { expect(await readFile(join(dataDir, "jobs.jsonl"), "utf8")).toContain("\"kind\":\"main_push\""); }); + test("keeps legacy git-webhooks routes as aliases", async () => { + const dataDir = await mkdtemp(join(tmpdir(), "patchbay-")); + const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", dataDir }); + const request = await signedRequest("/git-webhooks/jojo", "jojo", "jojo", { + ref: "refs/heads/main", + after: "abc123", + repository: { + name: "patchbay", + full_name: "peezy-tech/patchbay", + owner: { username: "peezy-tech" }, + }, + }); + + const response = await handler(request); + expect(response.status).toBe(202); + expect(await readFile(join(dataDir, "events.jsonl"), "utf8")).toContain("\"provider\":\"jojo\""); + }); + test("continues accepting webhooks when Discord returns an error", async () => { const originalFetch = globalThis.fetch; globalThis.fetch = (async () => new Response("bad", { status: 500 })) as unknown as typeof fetch; try { - const dataDir = await mkdtemp(join(tmpdir(), "git-webhooks-")); + const dataDir = await mkdtemp(join(tmpdir(), "patchbay-")); const handler = createHandler({ githubSecret: "gh", jojoSecret: "jojo", @@ -73,12 +91,12 @@ describe("server", () => { notifyEvents: new Set(["push"]), }, }); - const request = await signedRequest("/git-webhooks/jojo", "jojo", "jojo", { + const request = await signedRequest("/patchbay/jojo", "jojo", "jojo", { ref: "refs/heads/main", after: "abc123", repository: { - name: "git-webhooks", - full_name: "peezy-tech/git-webhooks", + name: "patchbay", + full_name: "peezy-tech/patchbay", owner: { username: "peezy-tech" }, }, });