UNPKG

forest-cli

Version:

The Lumberjacks' toolbelt is the Forest CLI which makes easy to manage your back office application directly from the terminal.

307 lines 28.9 kB
"use strict"; /** * Compiles a declarative workflow step-graph into the BPMN XML the server's * workflow engine accepts. A workflow's *elements* (steps) live entirely in * this BPMN artifact — there is no per-element patch path; the file is uploaded * whole (presigned S3) and linked via `bpmnAwsS3Identifier`. This is exactly * what the Forest UI does (verified against a captured request trace). * * The parser reads structure + prompt + execution flags; per-field args and * action/MCP wiring are resolved at runtime. The spec sets the shape. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.compileWorkflowToBpmn = exports.validateWorkflowSpec = exports.WorkflowSpecError = void 0; /** Error in a workflow `steps` spec (authoring-time, before anything is sent). */ class WorkflowSpecError extends Error { constructor(message) { super(message); this.name = 'WorkflowSpecError'; } } exports.WorkflowSpecError = WorkflowSpecError; /** Step types → BPMN element + `forest:alternative` (task subtype). */ const STEP_TYPES = { action: { alternative: 'trigger-action', element: 'serviceTask' }, condition: { element: 'exclusiveGateway' }, end: { element: 'endEvent' }, escalation: { element: 'intermediateThrowEvent' }, guidance: { alternative: 'guideline', element: 'userTask' }, 'load-related': { alternative: 'load-related-record', element: 'serviceTask' }, mcp: { alternative: 'mcp-server', element: 'serviceTask' }, read: { alternative: 'get-data', element: 'serviceTask' }, update: { alternative: 'update-data', element: 'serviceTask' }, }; const ID_RE = /^[A-Z_a-z][\w-]*$/; /** Ids the compiler generates itself — a step id colliding with one of these * (or with a generated `flow_*` sequence-flow / `*_di` diagram id) would produce * duplicate XML ids, which bpmn-js refuses to import. */ const RESERVED_IDS = new Set([ 'Start_1', 'Process_1', 'Definitions_1', 'BPMNDiagram_1', 'BPMNPlane_1', ]); /** The step types that compile to a `serviceTask` (the only element supporting * `forest:automaticExecution`/`forest:automaticCompletion`). */ const SERVICE_TASK_TYPES = Object.entries(STEP_TYPES) .filter(([, def]) => def.element === 'serviceTask') .map(([type]) => type); function xml(value) { return value .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;'); } /** The step ids a step can transition to (branch targets for conditions, else `next`). */ function stepTargets(step) { if (step.type === 'condition') return (step.branches ?? []).map(branch => branch.next); return step.next ? [step.next] : []; } function validateStepIdentity(step, seen) { if (!step.id || !ID_RE.test(step.id)) { throw new WorkflowSpecError(`Each step needs an \`id\` matching ${ID_RE} (got "${step.id ?? ''}").`); } if (RESERVED_IDS.has(step.id) || step.id.startsWith('flow_') || step.id.endsWith('_di')) { throw new WorkflowSpecError(`Step "${step.id}": this id is reserved for generated BPMN elements ` + `(${[...RESERVED_IDS].join(', ')}, \`flow_*\`, \`*_di\`).`); } if (seen.has(step.id)) throw new WorkflowSpecError(`Duplicate step id "${step.id}".`); seen.add(step.id); if (!(step.type in STEP_TYPES)) { const supported = Object.keys(STEP_TYPES).join(', '); throw new WorkflowSpecError(`Step "${step.id}": unsupported type "${step.type}". Supported: ${supported}.`); } } function validateConditionStep(step) { if (!Array.isArray(step.branches) || step.branches.length < 2) { throw new WorkflowSpecError(`Condition "${step.id}" needs at least two \`branches\`.`); } step.branches.forEach(branch => { if (!branch.answer || !branch.next) { throw new WorkflowSpecError(`Condition "${step.id}": each branch needs \`answer\` and \`next\`.`); } }); } function validateLinkStep(step) { if (!step.next) throw new WorkflowSpecError(`Step "${step.id}" needs a \`next\` step.`); if (step.type === 'mcp' && !step.mcpServerId) { throw new WorkflowSpecError(`Step "${step.id}" (mcp) needs \`mcpServerId\`.`); } if (step.type === 'escalation' && !step.inboxId) { throw new WorkflowSpecError(`Step "${step.id}" (escalation) needs \`inboxId\`.`); } } function validateStep(step, seen) { validateStepIdentity(step, seen); if ((step.auto || step.autoComplete) && STEP_TYPES[step.type].element !== 'serviceTask') { throw new WorkflowSpecError(`Step "${step.id}": \`auto\`/\`autoComplete\` are only supported on ` + `${SERVICE_TASK_TYPES.join(', ')} steps.`); } if (step.type === 'end') { if (step.next || step.branches) { throw new WorkflowSpecError(`Step "${step.id}" (end) must not have \`next\`/\`branches\`.`); } } else if (step.type === 'condition') { validateConditionStep(step); } else { validateLinkStep(step); } } /** Validate a (possibly partial) spec object and normalize it (entry step, segments). */ function validateWorkflowSpec(spec) { if (!spec.name || typeof spec.name !== 'string') throw new WorkflowSpecError('`name` is required.'); if (!spec.collection || typeof spec.collection !== 'string') { throw new WorkflowSpecError('`collection` is required.'); } if (!Array.isArray(spec.steps) || spec.steps.length === 0) { throw new WorkflowSpecError('`steps` must be a non-empty list.'); } const ids = new Set(); spec.steps.forEach(step => validateStep(step, ids)); spec.steps.forEach(step => { stepTargets(step).forEach(target => { if (!ids.has(target)) { throw new WorkflowSpecError(`Step "${step.id}" points to unknown step "${target}".`); } }); }); const entry = spec.start ?? spec.steps[0].id; if (!ids.has(entry)) throw new WorkflowSpecError(`\`start\` "${entry}" is not a defined step.`); if (!spec.steps.some(step => step.type === 'end')) { throw new WorkflowSpecError('The workflow needs at least one `end` step.'); } return { collection: spec.collection, name: spec.name, segments: spec.segments ?? [], start: entry, steps: spec.steps, }; } exports.validateWorkflowSpec = validateWorkflowSpec; /** * Allocate a unique, deterministic sequence-flow id. `flow_${source}_${target}` * alone is not unique — two condition branches can point at the same target, and * underscores in step ids can make distinct pairs render the same string — so * collisions get a stable numeric suffix (`_2`, `_3`, …) in spec order. */ function allocateFlowId(source, target, used) { const base = `flow_${source}_${target}`; let id = base; for (let n = 2; used.has(id); n += 1) id = `${base}_${n}`; used.add(id); return id; } /** The outgoing transitions of a step: a condition's branches, else its `next`. */ function branchesOf(step) { if (step.type === 'condition') return step.branches ?? []; return step.next ? [{ answer: '', next: step.next }] : []; } /** One `<bpmn:sequenceFlow>` (template only, no concatenation). */ function renderFlow(flow) { const nameAttr = flow.branch.answer ? ` name="${xml(flow.branch.answer)}"` : ''; const colorAttr = flow.branch.color ? ` forest:buttonColor="${xml(flow.branch.color)}"` : ''; return ` <bpmn:sequenceFlow id="${flow.id}" sourceRef="${flow.source}" targetRef="${flow.branch.next}"${nameAttr}${colorAttr} />`; } /** The `forest:*` attribute string for a step element. */ function stepAttributes(step) { const def = STEP_TYPES[step.type]; const attrs = []; if ('alternative' in def) attrs.push(`forest:alternative="${def.alternative}"`); // `automaticExecution`/`automaticCompletion` are only meaningful on serviceTask // steps — a `guidance` step is a manual userTask the UI cannot auto-complete, so // emitting the flag there produces BPMN the editor can't round-trip. if (def.element === 'serviceTask') { if (step.auto) attrs.push('forest:automaticExecution="true"'); if (step.autoComplete) attrs.push('forest:automaticCompletion="true"'); } if (step.prompt) attrs.push(`forest:description="${xml(step.prompt)}"`); if (step.type === 'mcp' && step.mcpServerId) attrs.push(`forest:mcpServerId="${xml(step.mcpServerId)}"`); if (step.type === 'escalation' && step.inboxId) attrs.push(`forest:inboxId="${xml(step.inboxId)}"`); return attrs.length > 0 ? ` ${attrs.join(' ')}` : ''; } /** Build the BPMN element + its outgoing sequenceFlows for one step. */ function renderStep(step, flowSpecs) { const name = xml(step.title ?? step.id); const flows = flowSpecs.map(flow => renderFlow(flow)); if (step.type === 'end') { return { element: ` <bpmn:endEvent id="${step.id}" name="${name}"></bpmn:endEvent>`, flows }; } const tag = STEP_TYPES[step.type].element; const outgoing = flowSpecs.map(flow => `<bpmn:outgoing>${flow.id}</bpmn:outgoing>`).join(''); const element = ` <bpmn:${tag} id="${step.id}" name="${name}"${stepAttributes(step)}>${outgoing}</bpmn:${tag}>`; return { element, flows }; } /** BPMN element footprint by shape kind (events / gateways / tasks). */ function elementSize(tag) { if (tag.endsWith('Event')) return { h: 36, w: 36 }; if (tag === 'exclusiveGateway') return { h: 50, w: 50 }; return { h: 80, w: 100 }; } /** * A minimal left-to-right BPMN-DI diagram. bpmn-js (the workflow editor) refuses * to import a `definitions` with no `<bpmndi:BPMNDiagram>` ("Import BPMN Error"), * so every element needs a shape and every sequence flow an edge — a naive layout * the user can rearrange is enough to make the workflow editable. */ function renderDiagram(nodes, edges) { const laneCenter = 160; const gap = 60; let cursor = 160; const bounds = {}; nodes.forEach(node => { const { h, w } = elementSize(node.tag); bounds[node.id] = { h, w, x: cursor, y: laneCenter - h / 2 }; cursor += w + gap; }); const shapes = nodes .map(node => { const b = bounds[node.id]; if (!b) return ''; return ` <bpmndi:BPMNShape id="${node.id}_di" bpmnElement="${node.id}"><dc:Bounds x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" /></bpmndi:BPMNShape>`; }) .filter(Boolean); const flowEdges = edges .map(edge => { const from = bounds[edge.source]; const to = bounds[edge.target]; if (!from || !to) return ''; const x1 = from.x + from.w; const y1 = from.y + from.h / 2; const x2 = to.x; const y2 = to.y + to.h / 2; return ` <bpmndi:BPMNEdge id="${edge.id}_di" bpmnElement="${edge.id}"><di:waypoint x="${x1}" y="${y1}" /><di:waypoint x="${x2}" y="${y2}" /></bpmndi:BPMNEdge>`; }) .filter(Boolean); return [ ' <bpmndi:BPMNDiagram id="BPMNDiagram_1">', ' <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">', ...shapes, ...flowEdges, ' </bpmndi:BPMNPlane>', ' </bpmndi:BPMNDiagram>', ]; } /** Compile a workflow `steps` spec into BPMN XML (validates first). */ function compileWorkflowToBpmn(input) { const spec = validateWorkflowSpec(input); const entryId = spec.start; const usedFlowIds = new Set(); const entryFlowId = allocateFlowId('Start_1', entryId, usedFlowIds); const nodes = [{ id: 'Start_1', tag: 'startEvent' }]; const edges = [{ id: entryFlowId, source: 'Start_1', target: entryId }]; const elements = [ ` <bpmn:startEvent id="Start_1"><bpmn:outgoing>${entryFlowId}</bpmn:outgoing></bpmn:startEvent>`, ]; const flows = [ ` <bpmn:sequenceFlow id="${entryFlowId}" sourceRef="Start_1" targetRef="${entryId}" />`, ]; spec.steps.forEach(step => { const flowSpecs = branchesOf(step).map(branch => ({ branch, id: allocateFlowId(step.id, branch.next, usedFlowIds), source: step.id, })); const rendered = renderStep(step, flowSpecs); elements.push(rendered.element); flows.push(...rendered.flows); nodes.push({ id: step.id, tag: STEP_TYPES[step.type].element }); flowSpecs.forEach(flow => { edges.push({ id: flow.id, source: step.id, target: flow.branch.next }); }); }); return [ '<?xml version="1.0" encoding="UTF-8"?>', '<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:forest="https://app.forestadmin.com" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">', ' <bpmn:process id="Process_1" isExecutable="true">', ...elements, ...flows, ' </bpmn:process>', ...renderDiagram(nodes, edges), '</bpmn:definitions>', '', ].join('\n'); } exports.compileWorkflowToBpmn = compileWorkflowToBpmn; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2Zsb3ctYnBtbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9sYXlvdXQvd29ya2Zsb3ctYnBtbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7OztHQVNHOzs7QUFFSCxrRkFBa0Y7QUFDbEYsTUFBYSxpQkFBa0IsU0FBUSxLQUFLO0lBQzFDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsSUFBSSxHQUFHLG1CQUFtQixDQUFDO0lBQ2xDLENBQUM7Q0FDRjtBQUxELDhDQUtDO0FBRUQsdUVBQXVFO0FBQ3ZFLE1BQU0sVUFBVSxHQUFHO0lBQ2pCLE1BQU0sRUFBRSxFQUFFLFdBQVcsRUFBRSxnQkFBZ0IsRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFFO0lBQ2pFLFNBQVMsRUFBRSxFQUFFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRTtJQUMxQyxHQUFHLEVBQUUsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFO0lBQzVCLFVBQVUsRUFBRSxFQUFFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRTtJQUNqRCxRQUFRLEVBQUUsRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUU7SUFDM0QsY0FBYyxFQUFFLEVBQUUsV0FBVyxFQUFFLHFCQUFxQixFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUU7SUFDOUUsR0FBRyxFQUFFLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFFO0lBQzFELElBQUksRUFBRSxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRTtJQUN6RCxNQUFNLEVBQUUsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUU7Q0FDdEQsQ0FBQztBQTJCWCxNQUFNLEtBQUssR0FBRyxtQkFBbUIsQ0FBQztBQUVsQzs7eURBRXlEO0FB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