146 lines
3.9 KiB
TypeScript
146 lines
3.9 KiB
TypeScript
|
|
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];
|
||
|
|
}
|