35 lines · 1.5 KB
import { stringify } from "./expressions";
import type { ExprValue } from "./expressions";
import type { JobSpec } from "./spec";

const MAX_MATRIX_COMBINATIONS = 64;

export function expandMatrix(spec: JobSpec): Array<Record<string, ExprValue>> {
  const matrix = spec.strategy?.matrix;
  if (!matrix) return [{}];
  const { include, exclude, ...axes } = matrix;
  let combos: Array<Record<string, ExprValue>> = [{}];
  for (const [axis, values] of Object.entries(axes)) {
    if (!Array.isArray(values)) continue;
    combos = combos.flatMap((combo) => values.map((value) => ({ ...combo, [axis]: value })));
  }
  if (Object.keys(axes).length === 0) combos = [];
  const matches = (combo: Record<string, ExprValue>, row: Record<string, ExprValue>) =>
    Object.entries(row).every(([key, value]) => combo[key] === value);
  combos = combos.filter((combo) => !(exclude ?? []).some((row) => matches(combo, row)));
  for (const row of include ?? []) {
    const target = combos.find((combo) =>
      Object.keys(axes).every((axis) => !(axis in row) || combo[axis] === row[axis]),
    );
    if (target && Object.keys(row).some((key) => key in axes)) Object.assign(target, row);
    else combos.push({ ...row });
  }
  if (combos.length > MAX_MATRIX_COMBINATIONS)
    throw new Error(`matrix expands to more than ${MAX_MATRIX_COMBINATIONS} jobs`);
  return combos.length > 0 ? combos : [{}];
}

export function matrixLabel(values: Record<string, ExprValue>): string {
  return Object.values(values).map(stringify).join(", ");
}