87 lines · 3.3 KB
import { evaluate, stringify } from "./expressions";
import type { ExprContext } from "./expressions";

/** A piece of interpolated text; non-text segments are resolved by the job script at runtime. */
export type Segment =
  | { kind: "text"; value: string }
  | { kind: "secret"; name: string }
  | { kind: "step"; variable: string }
  | { kind: "hash"; patterns: string[] };

const EXPRESSION = /\$\{\{([\s\S]*?)\}\}/g;
const SECRET_REFERENCE = /^\s*secrets\.([A-Za-z_][A-Za-z0-9_]*)\s*$/;
const STEP_REFERENCE =
  /^\s*steps\.([A-Za-z_][A-Za-z0-9_-]*)\.(?:outputs\.([A-Za-z_][A-Za-z0-9_-]*)|(outcome|conclusion))\s*$/;
const HASH_FILES = /^\s*hashFiles\(([\s\S]*)\)\s*$/i;
const QUOTED = /'((?:[^']|'')*)'/g;

export function shellName(value: string): string {
  return value.replace(/[^A-Za-z0-9_]/g, "_");
}

/** Shell variable holding a step output (`field` is an output name) or result (`outcome`/`conclusion`). */
export function stepVariable(stepId: string, field: string, kind: "output" | "result"): string {
  return kind === "output"
    ? `GITFOR_STEPOUT_${shellName(stepId)}__${shellName(field)}`
    : `GITFOR_STEP${field.toUpperCase()}_${shellName(stepId)}`;
}

function special(expression: string): Segment | null {
  const secret = SECRET_REFERENCE.exec(expression);
  if (secret) return { kind: "secret", name: secret[1] };
  const step = STEP_REFERENCE.exec(expression);
  if (step) {
    return {
      kind: "step",
      variable: step[2] ? stepVariable(step[1], step[2], "output") : stepVariable(step[1], step[3] ?? "", "result"),
    };
  }
  const hash = HASH_FILES.exec(expression);
  if (hash) {