// Bugzilla 5.0+ REST API client — thin wrapper around fetch import type { BugzillaConfig } from "./config" import type { BugzillaBug, BugzillaComment, BugzillaProduct, BugzillaComponent, BugzillaSearchParams, BugzillaCreatePayload, BugzillaUpdatePayload, BugzillaCreateProductPayload, BugzillaCreateComponentPayload, BugzillaSearchResponse, BugzillaCreateResponse, BugzillaUpdateResponse, BugzillaCommentsResponse, BugzillaProductResponse, } from "./types" export class BugzillaClient { private baseUrl: string private apiKey: string constructor(config: BugzillaConfig) { this.baseUrl = config.baseUrl.replace(/\/+$/, "") this.apiKey = config.apiKey } private async request(method: string, path: string, body?: unknown): Promise { // Auth: api_key in query for GET, in body for POST/PUT let url: string if (method === "GET") { const sep = path.includes("?") ? "&" : "?" url = `${this.baseUrl}${path}${sep}api_key=${encodeURIComponent(this.apiKey)}` } else { url = `${this.baseUrl}${path}` const bodyObj = (body ?? {}) as Record body = { ...bodyObj, api_key: this.apiKey } } const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: body ? JSON.stringify(body) : undefined, }) if (!res.ok) { const text = await res.text() throw new Error(`Bugzilla API ${method} ${path} failed (${res.status}): ${text}`) } return res.json() as Promise } async getBug(id: number): Promise { const data = await this.request("GET", `/bug/${id}`) if (!data.bugs?.length) throw new Error(`Bug ${id} not found`) return data.bugs[0] } async searchBugs(params: BugzillaSearchParams): Promise { const query = new URLSearchParams() if (params.product) query.set("product", params.product) if (params.component) query.set("component", params.component) if (params.summary) query.set("summary", params.summary) if (params.limit) query.set("limit", String(params.limit)) if (params.offset) query.set("offset", String(params.offset)) if (params.last_change_time) query.set("last_change_time", params.last_change_time) if (params.status) { const statuses = Array.isArray(params.status) ? params.status : [params.status] for (const s of statuses) query.append("status", s) } if (params.id) { const ids = Array.isArray(params.id) ? params.id : [params.id] for (const id of ids) query.append("id", String(id)) } if (params.alias) { const aliases = Array.isArray(params.alias) ? params.alias : [params.alias] for (const a of aliases) query.append("alias", a) } if (params.url) query.set("url", params.url) const qs = query.toString() const path = `/bug${qs ? `?${qs}` : ""}` const data = await this.request("GET", path) return data.bugs ?? [] } async createBug(payload: BugzillaCreatePayload): Promise { const data = await this.request("POST", "/bug", payload) return data.id } async updateBug(id: number, payload: BugzillaUpdatePayload): Promise { return this.request("PUT", `/bug/${id}`, payload) } async getComments(bugId: number): Promise { const data = await this.request("GET", `/bug/${bugId}/comment`) return data.bugs?.[String(bugId)]?.comments ?? [] } async addComment(bugId: number, comment: string): Promise { const data = await this.request<{ id: number }>("POST", `/bug/${bugId}/comment`, { comment }) return data.id } async tagComment(commentId: number, tags: string[]): Promise { await this.request("PUT", `/bug/comment/${commentId}/tags`, { add: tags }) } // Product operations async getProduct(nameOrId: string | number): Promise { const data = await this.request( "GET", `/product/${encodeURIComponent(String(nameOrId))}` ) if (!data.products?.length) throw new Error(`Product "${nameOrId}" not found`) return data.products[0] } async getAccessibleProducts(): Promise { const data = await this.request("GET", "/product?type=accessible") return data.products ?? [] } async createProduct(payload: BugzillaCreateProductPayload): Promise { const data = await this.request<{ id: number }>("POST", "/product", payload) return data.id } // Component operations async getComponents(product: string): Promise { const prod = await this.getProduct(product) return prod.components ?? [] } async createComponent(payload: BugzillaCreateComponentPayload): Promise { const data = await this.request<{ id: number }>("POST", "/component", payload) return data.id } }