import { WorkflowEntrypoint } from "cloudflare:workers";
import type { WorkflowEvent, WorkflowStep } from "cloudflare:workers";
import { runDb } from "./scope";
import { loadRepoById } from "../repos";
import { emitWebhook, publicUrl } from "../events/emit";
import { cancelActiveJob, completeJob } from "./jobs";
import type { WorkflowRunRow } from "./jobs";
import { expireStaleJobs, loadStates, scheduleTick } from "./dispatch";
import type { JobState } from "./dispatch";
import { deploySitesForRun } from "../sites/triggers";
import { notifyRunCompleted } from "../notify/ci";
import type { Conclusion } from "~/lib/types";
export type CiRunParams = { runId: string };
const WAIT_TIMEOUT = "5 minutes";
const GATED_WAIT_TIMEOUT_SECONDS = 60 * 60;
const MAX_ITERATIONS = 1000;
const CAPACITY_RETRY = "1 minute";
function runConclusion(jobs: JobState[]): Conclusion {
if (jobs.length === 0) return "skipped";
if (jobs.some((job) => job.conclusion === "failure")) return "failure";
if (jobs.some((job) => job.conclusion === "cancelled")) return "cancelled";
if (jobs.every((job) => job.conclusion === "skipped")) return "skipped";
return "success";
}
/** Gated jobs wake on review events; wait timers wake at their deadline. */
function gatedTimeout(wakeAt: number | null): `${number} seconds` {
const seconds = wakeAt ? Math.ceil((wakeAt - Date.now()) / 1000) : GATED_WAIT_TIMEOUT_SECONDS;
return `${Math.min(GATED_WAIT_TIMEOUT_SECONDS, Math.max(1, seconds))} seconds`;
}
/** Durable orchestration of one workflow run: dispatches jobs as their `needs` resolve and waits for completion events. */
export class CiRunWorkflow extends WorkflowEntrypoint<Env, CiRunParams> {
override async run(event: Readonly<WorkflowEvent<CiRunParams>>, step: WorkflowStep): Promise<void> {
const { runId } = event.payload;
const started = await step.do("start", async () => {
const db = await runDb(runId);