import { repoDb } from "../scoped-db"; import { notFoundError } from "../errors"; import { assertBelowCap, CAPS } from "../limits"; import { cancelActiveJob, completeJob } from "./jobs"; import type { WorkflowJobRow, WorkflowRunRow } from "./jobs"; import { startRun } from "./scheduler"; export async function loadRunByNumber(repoId: string, number: number): Promise { const row = await repoDb(repoId).first( "SELECT * FROM workflow_runs WHERE repo_id = ? AND run_number = ?", repoId, number, ); if (!row) throw notFoundError("Run not found"); return row; } /** Cancels every unfinished job of a run; running jobs are stopped through their runner. */ export async function cancelRunRow(runRow: WorkflowRunRow): Promise { const db = repoDb(runRow.repo_id); const jobs = await db.all( "SELECT * FROM workflow_jobs WHERE run_id = ? AND status != 'completed'", runRow.id, ); for (const job of jobs) { if (job.status === "in_progress") await cancelActiveJob(job, "cancelled by user"); else await completeJob(job.id, "cancelled"); } if (runRow.approval_required === 1 && runRow.status === "queued") { await db.run( "UPDATE workflow_runs SET status = 'completed', conclusion = 'cancelled', completed_at = ? WHERE id = ?", Date.now(), runRow.id, ); } } /** Approves a fork run awaiting a maintainer and starts it, within the per-repo active run cap. */ export async function approveRunRow(repoId: string, runRow: WorkflowRunRow): Promise { const db = repoDb(repoId); const active = await db.first<{ count: number }>( "SELECT COUNT(*) AS count FROM workflow_runs WHERE repo_id = ? AND status != 'completed' AND approval_required = 0", repoId, ); assertBelowCap(active?.count ?? 0, CAPS.activeRunsPerRepo, "active workflow runs; wait for some to finish"); const updated = await db.run( "UPDATE workflow_runs SET approval_required = 0 WHERE id = ? AND approval_required = 1 AND status = 'queued'", runRow.id, ); if (updated.meta.changes === 1) await startRun(runRow.id); }