62 lines · 2.9 KB
import { repoDb } from "../scoped-db";
import { loadRepoById } from "../repos";
import { compileWorkflow } from "./compile";
import type { CompiledJob, CompileInput, NeedsEntry } from "./compile";
import { environmentIdFor, loadVariables } from "./config-values";
import { parseWorkflow } from "./spec";
import type { WorkflowJobRow, WorkflowRunRow } from "./jobs";

function aggregateResult(conclusions: Array<string | null>): string {
  if (conclusions.some((value) => value === "failure")) return "failure";
  if (conclusions.some((value) => value === "cancelled")) return "cancelled";
  if (conclusions.length > 0 && conclusions.every((value) => value === "skipped")) return "skipped";
  return "success";
}

export function needsContext(dependencies: string[], siblings: WorkflowJobRow[]): Record<string, NeedsEntry> {
  const needs: Record<string, NeedsEntry> = {};
  for (const dependency of dependencies) {
    const rows = siblings.filter((row) => {
      const spec: CompiledJob = JSON.parse(row.spec);
      return spec.baseKey === dependency;
    });
    const outputs: Record<string, string> = {};
    for (const row of rows) Object.assign(outputs, row.outputs ? JSON.parse(row.outputs) : {});
    needs[dependency] = { result: aggregateResult(rows.map((row) => row.conclusion)), outputs };
  }
  return needs;
}

/**
 * Recompiles a job once its dependencies finished so `needs.<job>.outputs`, `needs.<job>.result` and
 * environment-scoped `vars` resolve, persisting the result as the job's spec.
 */
export async function recompileJob(runRow: WorkflowRunRow, job: WorkflowJobRow): Promise<CompiledJob> {
  const current: CompiledJob = JSON.parse(job.spec);
  if (!runRow.workflow_source || !runRow.context) return current;
  const parsed = parseWorkflow(runRow.workflow_source);
  if (!parsed.ok) throw new Error(parsed.error);
  const base: CompileInput = JSON.parse(runRow.context);
  const db = repoDb(runRow.repo_id);