/** * Auto-paginate a list endpoint. * If opts includes a specific page, returns just that page (no auto-pagination). */ export async function autoPaginate( fetchPage: (page: number, perPage: number) => Promise, opts?: { page?: number; perPage?: number }, ): Promise { const perPage = opts?.perPage || 50; // If caller requests a specific page, return just that page if (opts?.page) { return fetchPage(opts.page, perPage); } // Otherwise auto-paginate through all pages const all: T[] = []; let page = 1; while (true) { const items = await fetchPage(page, perPage); all.push(...items); if (items.length < perPage) break; page++; } return all; } /** * Compute duration in seconds from two ISO timestamps. */ export function computeDuration(startedAt?: string, completedAt?: string): number { if (!startedAt || !completedAt) return 0; const ms = new Date(completedAt).getTime() - new Date(startedAt).getTime(); return ms > 0 ? Math.round(ms / 1000) : 0; } /** * Gitea uses `status` for run state (running, waiting, completed) * and `conclusion` for the actual result (success, failure, cancelled, skipped). * When status is "completed", the conclusion carries the meaningful status. */ export function resolveGiteaStatus(status: string, conclusion: string): string { if (status === 'completed' && conclusion) { return conclusion; } return status || conclusion || ''; } /** * Extract a human-readable ref from the Gitea `path` field. * path format: "workflow.yaml@refs/tags/v1.0.0" or "workflow.yaml@refs/heads/main" */ export function extractRefFromPath(path?: string): string { if (!path) return ''; const atIdx = path.indexOf('@'); if (atIdx < 0) return ''; const ref = path.substring(atIdx + 1); return ref.replace(/^refs\/tags\//, '').replace(/^refs\/heads\//, ''); } /** * Extract the workflow filename from the Gitea `path` field. * path format: "workflow.yaml@refs/tags/v1.0.0" → "workflow.yaml" */ export function extractWorkflowIdFromPath(path?: string): string { if (!path) return ''; const atIdx = path.indexOf('@'); return atIdx >= 0 ? path.substring(0, atIdx) : path; } /** * Translate normalized status names to Gitea API-native query parameter values. * Gitea accepts: pending, queued, in_progress, failure, success, skipped */ export function toGiteaApiStatus(status?: string): string | undefined { if (!status) return undefined; const map: Record = { running: 'in_progress', failed: 'failure', canceled: 'cancelled', pending: 'pending', success: 'success', skipped: 'skipped', waiting: 'queued', }; return map[status] || status; }