UNPKG

@workflow-manager/runner

Version:

CLI runner for in-memory and markdown workflow orchestration using ATEP-like envelopes

100 lines (99 loc) • 3.4 kB
import fs from "node:fs"; import path from "node:path"; function appendRecord(filePath, record) { fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, { encoding: "utf-8", mode: 0o600 }); } export function defaultRunArchiveDirectory() { return path.join(process.cwd(), ".wfm", "runs"); } export function createRunArchive(workflow, runId, directory = defaultRunArchiveDirectory()) { fs.mkdirSync(directory, { recursive: true, mode: 0o700 }); fs.chmodSync(directory, 0o700); const archivePath = path.join(directory, `${runId}.jsonl`); fs.writeFileSync(archivePath, "", { encoding: "utf-8", mode: 0o600 }); fs.chmodSync(archivePath, 0o600); const archive = new RunArchive(archivePath); archive.writeMetadata(workflow, runId); return Object.assign(archive, { path: archivePath, runId }); } export class RunArchive { path; constructor(path) { this.path = path; } writeMetadata(workflow, runId) { appendRecord(this.path, { type: "metadata", occurredAt: new Date().toISOString(), data: { runId, workflowKey: workflow.key, workflowTitle: workflow.title, }, }); } onEvent(event) { appendRecord(this.path, { type: "event", occurredAt: event.occurredAt, data: { id: event.id, sequence: event.sequenceNumber, type: event.type, runId: event.runId, stepKey: event.stepRunId, actor: event.actor, payload: event.payload, }, }); } onSnapshot(snapshot, stepDetails) { appendRecord(this.path, { type: "snapshot", occurredAt: snapshot.updatedAt ?? new Date().toISOString(), data: { snapshot, stepDetails, }, }); } onLog() { } } export function readRunArchive(filePath) { const resolvedPath = path.resolve(filePath); let raw; try { raw = fs.readFileSync(resolvedPath, "utf-8"); } catch (error) { return `Could not read run archive ${resolvedPath}: ${error.message}`; } const records = []; for (const [index, line] of raw.split("\n").entries()) { if (!line.trim()) continue; try { const parsed = JSON.parse(line); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return `Run archive ${resolvedPath} has an invalid record on line ${index + 1}`; } const record = parsed; if ((record.type !== "metadata" && record.type !== "event" && record.type !== "snapshot") || typeof record.occurredAt !== "string" || !record.data || typeof record.data !== "object" || Array.isArray(record.data)) { return `Run archive ${resolvedPath} has an invalid record on line ${index + 1}`; } records.push({ type: record.type, occurredAt: record.occurredAt, data: record.data, }); } catch { return `Run archive ${resolvedPath} is not valid JSONL (line ${index + 1})`; } } return records; }