Submatter/apps/web/server/lib/forge/adapters.ts

146 lines
3.9 KiB
TypeScript
Raw Normal View History

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<RepoMetadata>;
}
async function readJsonResponse(response: Response): Promise<unknown> {
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<ForgeType, ForgeAdapter> = {
github: githubForgeAdapter,
forgejo: forgejoForgeAdapter,
gitlab: forgejoForgeAdapter,
bitbucket: forgejoForgeAdapter,
};
export function getForgeAdapter(forgeType: ForgeType): ForgeAdapter {
return ADAPTERS[forgeType];
}