Rename service to Patchbay
All checks were successful
check / check (push) Successful in 37s

This commit is contained in:
matamune 2026-05-12 23:18:13 +00:00
parent 816881c2cc
commit 3379abf99b
Signed by: matamune
GPG key ID: 3BB8E7D3B968A324
9 changed files with 57 additions and 36 deletions

View file

@ -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

View file

@ -3,7 +3,7 @@
"configVersion": 1,
"workspaces": {
"": {
"name": "@peezy.tech/git-webhooks",
"name": "@peezy.tech/patchbay",
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.9.3",

View file

@ -1,5 +1,5 @@
{
"name": "@peezy.tech/git-webhooks",
"name": "@peezy.tech/patchbay",
"version": "0.1.0",
"private": true,
"type": "module",

View file

@ -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",
},
},
],

View file

@ -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 });

View file

@ -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 () => {

View file

@ -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,

View file

@ -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");
});

View file

@ -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" },
},
});