"use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { person } from "@/lib/actor"; import { CiError, dispatchWorkflow, rerunWorkflow } from "@/lib/ci"; import { createRunner, deleteRunner, RunnerError, type RunnerScope } from "@/lib/runners"; import { cancelRun, getRun, isFinished } from "@/lib/runs"; import { ActionError, repoForAction, viewerOrThrow } from "./guard"; import type { FormState } from "./types"; /** A repository's runners (`owner/name`, managed by its owner) or the viewer's own (null). */ async function runnerScope(repo: { owner: string; name: string } | null): Promise<{ scope: RunnerScope; path: string }> { if (repo) { const { repo: r } = await repoForAction(repo.owner, repo.name, "admin"); return { scope: { repoId: r.id }, path: `/${r.owner}/${r.name}/settings/runners` }; } const viewer = await viewerOrThrow(); return { scope: { userId: viewer.id }, path: "/settings/runners" }; } export async function createRunnerAction(repo: { owner: string; name: string } | null, _prev: FormState, formData: FormData) { const { scope, path } = await runnerScope(repo); try { const expiry = String(formData.get("expiry") ?? ""); const token = await createRunner(scope, { name: String(formData.get("name") ?? ""), labels: String(formData.get("labels") ?? ""), expiresInDays: expiry === "never" ? null : Number(expiry), }); revalidatePath(path); return { token }; } catch (e) { if (e instanceof RunnerError) return { error: e.message }; throw e; } } export async function deleteRunnerAction(repo: { owner: string; name: string } | null, runnerId: string) { const { scope, path } = await runnerScope(repo); await deleteRunner(scope, String(runnerId)); revalidatePath(path); } export async function dispatchWorkflowAction(owner: string, name: string, workflowPath: string, _prev: FormState, formData: FormData) { let destination: string; try { const { repo, viewer } = await repoForAction(owner, name, "write"); const number = await dispatchWorkflow(repo, workflowPath, String(formData.get("branch") ?? ""), person(viewer.id)); destination = `/${repo.owner}/${repo.name}/actions/runs/${number}`; } catch (e) { if (e instanceof CiError || e instanceof ActionError) return { error: e.message }; throw e; } redirect(destination); } async function runForAction(owner: string, name: string, number: number) { const { repo, viewer } = await repoForAction(owner, name, "write"); const run = await getRun(repo.id, number); if (!run) throw new ActionError("That run doesn't exist."); return { repo, viewer, run }; } export async function cancelRunAction(owner: string, name: string, number: number) { const { repo, run } = await runForAction(owner, name, number); if (!isFinished(run.status)) await cancelRun(run.id); revalidatePath(`/${repo.owner}/${repo.name}/actions`, "layout"); } export async function rerunAction(owner: string, name: string, number: number) { const { repo, viewer, run } = await runForAction(owner, name, number); const next = await rerunWorkflow(repo, run, person(viewer.id)); redirect(`/${repo.owner}/${repo.name}/actions/runs/${next}`); }