import { MARKER_PREFIX } from "./script";
import type { JobExecution } from "./jobs";
const MIN_MASK_LENGTH = 4;
/** Single lines of multi-line secrets are only masked when long enough not to redact ordinary log text. */
const MIN_LINE_MASK_LENGTH = 8;
const BASE64_GROUP_BYTES = 3;
type Marker =
| { type: "step-start"; index: number }
| { type: "step-end"; index: number; code: number }
| { type: "step-skip"; index: number }
| { type: "artifact"; name: string }
| { type: "output"; name: string; value: string }
| { type: "job-end"; failed: boolean }
| { type: "setup-start" }
| { type: "setup-end"; code: number };
function decodeBase64(value: string): string {
try {
return new TextDecoder().decode(Uint8Array.from(atob(value), (char) => char.charCodeAt(0)));
} catch {
return "";
}
}
/** Markers share stdout with user steps, so only lines carrying the job's secret nonce count. */
export function parseMarker(line: string, nonce: string | null): Marker | null {
const prefix = nonce === null ? MARKER_PREFIX : `${MARKER_PREFIX}${nonce} `;
if (!line.startsWith(prefix)) return null;
const [type, ...args] = line.slice(prefix.length).trim().split(" ");
const number = (value: string | undefined) => Number(value ?? "0");
switch (type) {
case "step-start":
return { type, index: number(args[0]) };
case "step-end":
return { type, index: number(args[0]), code: number(args[1]) };
case "step-skip":
return { type, index: number(args[0]) };
case "artifact":