From 998b377078109a2056403b7482857a6cfcad23c1 Mon Sep 17 00:00:00 2001 From: Erwann Mest Date: Sun, 5 Jul 2026 01:23:26 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(api):=20centralis?= =?UTF-8?q?e=20error=20handling=20in=20apiFetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move try/catch from individual callers into apiFetch, which now returns null on any HTTP or network error and logs via console.error. Removes BugzillaError class. Callers (createBug, updateBug, createComment, createAttachment) are simplified to null-check the return value. Adds CI workflow running typecheck, build, and test on push/PR to main. --- .github/workflows/ci.yml | 20 ++++++++++ src/index.ts | 85 ++++++++++++++++------------------------ 2 files changed, 53 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..42c9e24 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: "24" + cache: npm + - run: npm ci + - run: npm run typecheck + - run: npm run build + - run: npm run test diff --git a/src/index.ts b/src/index.ts index d933748..bce346b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,15 +7,6 @@ const API_BASE = process.env["MCP_BUGZILLA_BASE_URL"] ?? "https://bugzilla.mozilla.org/rest" const API_KEY = process.env["MCP_BUGZILLA_API_KEY"] -export class BugzillaError extends Error { - constructor( - public status: number, - public body: string, - ) { - super(`Bugzilla API error ${status}: ${body}`) - } -} - export const apiFetch = async ( path: string, options: RequestInit = {}, @@ -26,12 +17,18 @@ export const apiFetch = async ( } if (API_KEY) headers["X-BUGZILLA-API-KEY"] = API_KEY - const response = await fetch(`${API_BASE}${path}`, { ...options, headers }) - if (!response.ok) { - const body = await response.text() - throw new BugzillaError(response.status, body) + try { + const response = await fetch(`${API_BASE}${path}`, { ...options, headers }) + if (!response.ok) { + const body = await response.text() + console.error(`Bugzilla API error ${response.status}: ${body} — ${path}`) + return null + } + return (await response.json()) as T + } catch (error) { + console.error(`Fetch failed: ${path}`, error) + return null } - return (await response.json()) as T } export const ok = (data: unknown) => ({ @@ -90,15 +87,11 @@ export const createBug = async (params: { target_milestone?: string }) => { if (!API_KEY) return err("MCP_BUGZILLA_API_KEY is required to create bugs") - try { - const data = await apiFetch<{ id: number }>("/bug", { - method: "POST", - body: JSON.stringify(params), - }) - return data?.id ? ok({ id: data.id }) : err("failed to create bug") - } catch (e) { - return err(e instanceof Error ? e.message : "failed to create bug") - } + const data = await apiFetch<{ id: number }>("/bug", { + method: "POST", + body: JSON.stringify(params), + }) + return data?.id ? ok({ id: data.id }) : err("failed to create bug") } export const updateBug = async (params: { @@ -117,15 +110,11 @@ export const updateBug = async (params: { }) => { if (!API_KEY) return err("MCP_BUGZILLA_API_KEY is required to update bugs") const { id, ...fields } = params - try { - const data = await apiFetch<{ bugs: unknown[] }>(`/bug/${id}`, { - method: "PUT", - body: JSON.stringify(fields), - }) - return data?.bugs ? ok(data.bugs) : err(`failed to update bug ${id}`) - } catch (e) { - return err(e instanceof Error ? e.message : `failed to update bug ${id}`) - } + const data = await apiFetch<{ bugs: unknown[] }>(`/bug/${id}`, { + method: "PUT", + body: JSON.stringify(fields), + }) + return data?.bugs ? ok(data.bugs) : err(`failed to update bug ${id}`) } export const getBugHistory = async ({ id }: { id: string }) => { @@ -159,15 +148,11 @@ export const createComment = async (params: { }) => { if (!API_KEY) return err("MCP_BUGZILLA_API_KEY is required to post comments") const { id, ...body } = params - try { - const data = await apiFetch<{ id: number }>(`/bug/${id}/comment`, { - method: "POST", - body: JSON.stringify(body), - }) - return data?.id ? ok({ id: data.id }) : err("failed to create comment") - } catch (e) { - return err(e instanceof Error ? e.message : "failed to create comment") - } + const data = await apiFetch<{ id: number }>(`/bug/${id}/comment`, { + method: "POST", + body: JSON.stringify(body), + }) + return data?.id ? ok({ id: data.id }) : err("failed to create comment") } export const searchCommentTags = async ({ @@ -224,17 +209,13 @@ export const createAttachment = async (params: { if (!API_KEY) return err("MCP_BUGZILLA_API_KEY is required to create attachments") const { id, ...body } = params - try { - const result = await apiFetch<{ ids: number[] }>(`/bug/${id}/attachment`, { - method: "POST", - body: JSON.stringify({ ...body, ids: [id] }), - }) - return result?.ids - ? ok({ ids: result.ids }) - : err("failed to create attachment") - } catch (e) { - return err(e instanceof Error ? e.message : "failed to create attachment") - } + const result = await apiFetch<{ ids: number[] }>(`/bug/${id}/attachment`, { + method: "POST", + body: JSON.stringify({ ...body, ids: [id] }), + }) + return result?.ids + ? ok({ ids: result.ids }) + : err("failed to create attachment") } export const updateAttachment = async (params: {