import type { ForgeType, RepoMetadata } from "@tasks/shared/connected-repos"; export type ForgeAdapterContext = { apiBaseUrl: string; owner: string; repo: string; token?: string; }; export interface ForgeAdapter { readonly forgeType: ForgeType; fetchRepoMetadata(ctx: ForgeAdapterContext): Promise; } async function readJsonResponse(response: Response): Promise { const text = await response.text(); if (!text) { return null; } try { return JSON.parse(text) as unknown; } catch { throw new Error(`Forge API returned non-JSON response (${response.status}).`); } } function authHeaders(token: string | undefined): HeadersInit { if (!token) { return {}; } return { Authorization: `Bearer ${token}` }; } export const githubForgeAdapter: ForgeAdapter = { forgeType: "github", async fetchRepoMetadata(ctx) { const url = `${ctx.apiBaseUrl.replace(/\/$/, "")}/repos/${ctx.owner}/${ctx.repo}`; const response = await fetch(url, { headers: { Accept: "application/vnd.github+json", ...authHeaders(ctx.token), }, }); const body = await readJsonResponse(response); if (!response.ok) { const message = typeof body === "object" && body !== null && "message" in body && typeof body.message === "string" ? body.message : `GitHub API error (${response.status})`; throw new Error(message); } if ( typeof body !== "object" || body === null || !("default_branch" in body) || typeof body.default_branch !== "string" ) { throw new Error("GitHub API response missing default_branch."); } const cloneUrl = typeof body === "object" && body !== null && "clone_url" in body && typeof body.clone_url === "string" ? body.clone_url : `https://github.com/${ctx.owner}/${ctx.repo}.git`; const description = typeof body === "object" && body !== null && "description" in body && typeof body.description === "string" ? body.description : undefined; return { defaultBranch: body.default_branch, cloneUrl, description, }; }, }; export const forgejoForgeAdapter: ForgeAdapter = { forgeType: "forgejo", async fetchRepoMetadata(ctx) { const url = `${ctx.apiBaseUrl.replace(/\/$/, "")}/repos/${ctx.owner}/${ctx.repo}`; const response = await fetch(url, { headers: { Accept: "application/json", ...authHeaders(ctx.token), }, }); const body = await readJsonResponse(response); if (!response.ok) { const message = typeof body === "object" && body !== null && "message" in body && typeof body.message === "string" ? body.message : `Forge API error (${response.status})`; throw new Error(message); } if ( typeof body !== "object" || body === null || !("default_branch" in body) || typeof body.default_branch !== "string" ) { throw new Error("Forge API response missing default_branch."); } const cloneUrl = typeof body === "object" && body !== null && "clone_url" in body && typeof body.clone_url === "string" ? body.clone_url : `${ctx.apiBaseUrl.replace(/\/api\/v1$/, "")}/${ctx.owner}/${ctx.repo}.git`; const description = typeof body === "object" && body !== null && "description" in body && typeof body.description === "string" ? body.description : undefined; return { defaultBranch: body.default_branch, cloneUrl, description, }; }, }; const ADAPTERS: Record = { github: githubForgeAdapter, forgejo: forgejoForgeAdapter, gitlab: forgejoForgeAdapter, bitbucket: forgejoForgeAdapter, }; export function getForgeAdapter(forgeType: ForgeType): ForgeAdapter { return ADAPTERS[forgeType]; }