"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}`);
}