import { createBobbinClient, type BobbinContext, type QueryValue } from "$lib/api/client"; import type { XrpcRequestInit } from "$lib/api/client"; import { jsonGet } from "$lib/api/_request"; import { hostForServiceDid } from "$lib/auth/agent"; import type * as GetPipeline from "$lib/api/lexicons/types/org/tangled/ci/getPipeline"; import type * as Pipeline from "$lib/api/lexicons/types/org/tangled/ci/pipeline"; import type * as QueryPipelines from "$lib/api/lexicons/types/org/tangled/ci/queryPipelines"; import type { PipelineStatus, PipelineSummary, PipelineTrigger } from "$lib/components/repo/types"; const asParams = (params: object): Record => params as unknown as Record; export const spindleUrl = (host: string): string => { const raw = (hostForServiceDid(host.trim()) ?? host.trim()).replace(/\/+$/, ""); if (raw.includes("://")) return raw; return raw.startsWith("localhost:") ? `http://${raw}` : `https://${raw}`; }; export const createSpindleClient = (host: string, fetch: typeof globalThis.fetch): BobbinContext => createBobbinClient({ serviceUrl: spindleUrl(host), fetch }); // queryPipelines filters by trigger nsid const TRIGGER_NSIDS = { push: "org.tangled.event.push", pull_request: "org.tangled.event.pullRequest", manual: "org.tangled.ci.trigger.manual" } as const; export const triggersOf = (filter: PipelineTrigger["kind"] | "all") => filter === "all" ? undefined : [TRIGGER_NSIDS[filter]]; export const queryPipelines = ( ctx: BobbinContext, params: QueryPipelines.$params, init?: XrpcRequestInit ) => jsonGet(ctx, "org.tangled.ci.queryPipelines", asParams(params), init); export const getPipeline = ( ctx: BobbinContext, params: GetPipeline.$params, init?: XrpcRequestInit ) => jsonGet(ctx, "org.tangled.ci.getPipeline", asParams(params), init); const toTrigger = (trigger: Pipeline.Main["trigger"], repoDid?: string): PipelineTrigger => { switch (trigger.$type) { case "org.tangled.event.push": return { kind: "push", targetRef: trigger.ref.replace(/^refs\/(?:heads|tags)\//, "") }; case "org.tangled.event.pullRequest": { // org.tangled.event.pullRequest is still empty in the lexicons, so its fields are read loosely const fields = trigger as Record; const str = (key: string) => typeof fields[key] === "string" ? (fields[key] as string) : ""; const sourceRepo = str("sourceRepo"); return { kind: "pull_request", targetRef: str("targetBranch"), // fork ci labels are handled by the page resolving the name of the fork first sourceLabel: str("sourceBranch"), ...(sourceRepo && sourceRepo !== repoDid ? { sourceRepo } : {}) }; } default: return { kind: "manual" }; } }; const durationOf = (workflow: Pipeline.Workflow): number => { if (!workflow.startedAt || !workflow.finishedAt) return 0; const started = Date.parse(workflow.startedAt); const finished = Date.parse(workflow.finishedAt); if (Number.isNaN(started) || Number.isNaN(finished)) return 0; return Math.max(0, finished - started); }; export const toPipelineSummary = (pipeline: Pipeline.Main, repoDid?: string): PipelineSummary => ({ id: pipeline.id, sha: pipeline.commit, createdAt: pipeline.createdAt ?? "", trigger: toTrigger(pipeline.trigger, repoDid), workflows: (pipeline.workflows ?? []).map((workflow) => ({ // id is the key logs and getPipeline go by, name is optional name: workflow.id, status: (workflow.status === "scheduled" ? "pending" : workflow.status) as PipelineStatus, duration: durationOf(workflow), error: workflow.error, startedAt: workflow.startedAt, finishedAt: workflow.finishedAt })) });