import type { CreateDeploymentInput, PreviewDeployment } from './types'; const API_BASE = '/api/preview-deployer'; export class PreviewDeployerApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.name = 'PreviewDeployerApiError'; this.status = status; } } export interface PreviewDeployerSession { authenticated: boolean; } export interface BranchRef { name: string; commitHash?: string | null; } export interface CommitRef { commitHash: string; shortHash: string; subject?: string | null; } export async function getSession(signal?: AbortSignal) { const session = await request('/session', { signal, }); return session ?? { authenticated: true }; } export function createSession(accessToken: string) { return request('/session', { method: 'POST', body: { accessToken }, }); } export function deleteSession() { return request('/session', { method: 'DELETE' }); } export async function listDeployments(signal?: AbortSignal) { const payload = await request< PreviewDeployment[] | { deployments: PreviewDeployment[] } >('/deployments', { signal }); return Array.isArray(payload) ? payload : payload.deployments; } export async function searchBranches(query: string, signal?: AbortSignal) { const payload = await request( `/refs/branches?q=${encodeURIComponent(query)}`, { signal }, ); return Array.isArray(payload) ? payload : payload.items; } export async function searchCommits( branch: string, query: string, signal?: AbortSignal, ) { const payload = await request( `/refs/commits?branch=${encodeURIComponent(branch)}&q=${encodeURIComponent(query)}`, { signal }, ); return Array.isArray(payload) ? payload : payload.items; } export function createDeployment(input: CreateDeploymentInput) { return request('/deployments', { method: 'POST', body: input, }); } export function uninstallDeployment(deploymentId: string) { return request( `/deployments/${encodeURIComponent(deploymentId)}/uninstall`, { method: 'POST', body: {} }, ); } interface RequestOptions { method?: string; body?: unknown; signal?: AbortSignal; } async function request( path: string, options: RequestOptions = {}, ): Promise { const headers: Record = { Accept: 'application/json' }; const init: RequestInit = { method: options.method ?? 'GET', headers, signal: options.signal, credentials: 'same-origin', }; if (options.body !== undefined) { headers['Content-Type'] = 'application/json'; init.body = JSON.stringify(options.body); } const response = await fetch(`${API_BASE}${path}`, init); const responseText = await response.text(); const payload = parseJson(responseText); if (!response.ok) { throw new PreviewDeployerApiError( readErrorMessage(payload) || `请求失败(HTTP ${response.status})`, response.status, ); } return unwrapPayload(payload); } function parseJson(value: string): unknown { if (!value.trim()) { return null; } try { return JSON.parse(value) as unknown; } catch { return value; } } function unwrapPayload(payload: unknown): T { if (isRecord(payload) && 'data' in payload) { return payload.data as T; } return payload as T; } function readErrorMessage(payload: unknown) { if (typeof payload === 'string') { return payload.trim(); } if (!isRecord(payload)) { return ''; } if (typeof payload.message === 'string') { return payload.message; } if (isRecord(payload.error) && typeof payload.error.message === 'string') { return payload.error.message; } return ''; } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null; }