Vectrum
Sign In
75 lines · 3.2 KB · TypeScript
"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}`);
}