import { readFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = join(__dirname, "../../.."); const FRONTEND_SOURCE = join( REPO_ROOT, "frontend/src/app/components/workflows/builtinWorkflows.ts", ); /** @typedef {import("../../../frontend/src/app/components/shared/types").MikeWorkflow} MikeWorkflow */ /** * Parse the pre-cutover frontend builtin workflow registry by evaluating its * literal array definition. No template interpolation is used in the source file. * @returns {MikeWorkflow[]} */ export function parseFrontendBuiltinWorkflows(sourcePath = FRONTEND_SOURCE) { const src = readFileSync(sourcePath, "utf8"); const trimmed = src .replace(/^import.*\n/m, "") .replace(/export const BUILT_IN_IDS[\s\S]*$/, "") .replace( /export const BUILT_IN_WORKFLOWS:\s*MikeWorkflow\[\]\s*=/, "const BUILT_IN_WORKFLOWS =", ); const fn = new Function(`${trimmed}; return BUILT_IN_WORKFLOWS;`); return fn(); } /** @param {MikeWorkflow[]} workflows */ export function workflowInventory(workflows) { const orderedIds = workflows.map((wf) => wf.id); const perWorkflowColumnCount = Object.fromEntries( workflows.map((wf) => [wf.id, wf.columns_config?.length ?? 0]), ); const normalized = stableStringify(workflows); return { orderedIds, totalWorkflowCount: workflows.length, perWorkflowColumnCount, totalColumnPromptCount: workflows.reduce( (sum, wf) => sum + (wf.columns_config?.filter((col) => typeof col.prompt === "string") .length ?? 0), 0, ), sha256: createHash("sha256").update(normalized).digest("hex"), workflows, }; } /** @param {unknown} value */ export function stableStringify(value) { return `${JSON.stringify(value, null, 2)}\n`; } export function parseFrontendInventory(sourcePath = FRONTEND_SOURCE) { const workflows = parseFrontendBuiltinWorkflows(sourcePath); return workflowInventory(workflows); }