@synergenius/flow-weaver 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/annotation-generator.d.ts +6 -1
- package/dist/annotation-generator.js +75 -24
- package/dist/api/generate.d.ts +5 -0
- package/dist/api/generate.js +8 -1
- package/dist/api/validate.d.ts +5 -2
- package/dist/api/validate.js +4 -4
- package/dist/ast/types.d.ts +3 -1
- package/dist/cli/commands/implement.d.ts +9 -0
- package/dist/cli/commands/implement.js +96 -0
- package/dist/cli/commands/status.d.ts +9 -0
- package/dist/cli/commands/status.js +121 -0
- package/dist/cli/flow-weaver.mjs +1001 -457
- package/dist/cli/index.js +32 -0
- package/dist/cli/templates/workflows/aggregator.js +1 -1
- package/dist/cli/templates/workflows/sequential.js +1 -1
- package/dist/function-like.d.ts +2 -0
- package/dist/function-like.js +1 -0
- package/dist/generator/unified.js +5 -1
- package/dist/jsdoc-parser.js +13 -2
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tools-model.d.ts +3 -0
- package/dist/mcp/tools-model.js +253 -0
- package/dist/parser.js +9 -4
- package/dist/validator.d.ts +2 -0
- package/dist/validator.js +41 -1
- package/docs/reference/concepts.md +3 -3
- package/docs/reference/iterative-development.md +8 -9
- package/docs/reference/node-conversion.md +2 -5
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TNodeInstanceAST, TPortDefinition, TWorkflowAST } from "./ast/index.js";
|
|
1
|
+
import type { TNodeTypeAST, TNodeInstanceAST, TPortDefinition, TWorkflowAST } from "./ast/index.js";
|
|
2
2
|
export interface GenerateAnnotationsOptions {
|
|
3
3
|
includeComments?: boolean;
|
|
4
4
|
includeMetadata?: boolean;
|
|
@@ -41,5 +41,10 @@ export declare function assignPortOrders(ports: [string, TPortDefinition][], _di
|
|
|
41
41
|
* Exported for reuse in generate-in-place.ts to maintain DRY principle
|
|
42
42
|
*/
|
|
43
43
|
export declare function generateNodeInstanceTag(instance: TNodeInstanceAST): string;
|
|
44
|
+
/**
|
|
45
|
+
* Generate a TypeScript function signature from a node type definition.
|
|
46
|
+
* Handles three modes: stub (declare function), expression (pure function), normal (execute/onSuccess/onFailure).
|
|
47
|
+
*/
|
|
48
|
+
export declare function generateFunctionSignature(nodeType: TNodeTypeAST): string[];
|
|
44
49
|
export declare const annotationGenerator: AnnotationGenerator;
|
|
45
50
|
//# sourceMappingURL=annotation-generator.d.ts.map
|
|
@@ -24,6 +24,10 @@ export class AnnotationGenerator {
|
|
|
24
24
|
if (nodeType.variant === 'COERCION') {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
+
// Skip inferred stubs — they have no @flowWeaver annotation and were auto-detected
|
|
28
|
+
if (nodeType.variant === 'STUB' && nodeType.inferred) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
27
31
|
lines.push(...this.generateNodeTypeAnnotation(nodeType, indent, includeComments, includeMetadata));
|
|
28
32
|
lines.push("");
|
|
29
33
|
});
|
|
@@ -47,8 +51,14 @@ export class AnnotationGenerator {
|
|
|
47
51
|
}
|
|
48
52
|
lines.push(` *`);
|
|
49
53
|
}
|
|
50
|
-
// @flowWeaver
|
|
51
|
-
|
|
54
|
+
// @flowWeaver marker: use 'node' shorthand for stubs and expression nodes
|
|
55
|
+
const isStub = nodeType.variant === 'STUB';
|
|
56
|
+
if (isStub || nodeType.expression) {
|
|
57
|
+
lines.push(" * @flowWeaver node");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
lines.push(" * @flowWeaver nodeType");
|
|
61
|
+
}
|
|
52
62
|
// Add label if present
|
|
53
63
|
if (includeMetadata && nodeType.label) {
|
|
54
64
|
lines.push(` * @label ${nodeType.label}`);
|
|
@@ -117,28 +127,7 @@ export class AnnotationGenerator {
|
|
|
117
127
|
return `{ ${props.join(", ")} }`;
|
|
118
128
|
}
|
|
119
129
|
generateFunctionSignature(nodeType) {
|
|
120
|
-
|
|
121
|
-
// Build parameters (excluding execute which will be first)
|
|
122
|
-
const params = ["execute: boolean"];
|
|
123
|
-
Object.entries(nodeType.inputs).forEach(([name, port]) => {
|
|
124
|
-
if (isExecutePort(name))
|
|
125
|
-
return;
|
|
126
|
-
const optional = port.optional ? "?" : "";
|
|
127
|
-
const defaultVal = port.default !== undefined ? ` = ${JSON.stringify(port.default)}` : "";
|
|
128
|
-
params.push(`${name}${optional}: ${this.mapDataTypeToTS(port.dataType)}${defaultVal}`);
|
|
129
|
-
});
|
|
130
|
-
// Build return type (including onSuccess/onFailure)
|
|
131
|
-
const returns = ["onSuccess: boolean", "onFailure: boolean"];
|
|
132
|
-
Object.entries(nodeType.outputs).forEach(([name, port]) => {
|
|
133
|
-
if (isSuccessPort(name) || isFailurePort(name))
|
|
134
|
-
return;
|
|
135
|
-
returns.push(`${name}: ${this.mapDataTypeToTS(port.dataType)}`);
|
|
136
|
-
});
|
|
137
|
-
lines.push(`function ${nodeType.functionName}(${params.join(", ")}) {`);
|
|
138
|
-
lines.push(` if (!execute) return { onSuccess: false, onFailure: false };`);
|
|
139
|
-
lines.push(` return { onSuccess: true, onFailure: false };`);
|
|
140
|
-
lines.push(`}`);
|
|
141
|
-
return lines;
|
|
130
|
+
return generateFunctionSignature(nodeType);
|
|
142
131
|
}
|
|
143
132
|
generateWorkflowAnnotation(workflow, indent, includeComments, skipParamReturns = false) {
|
|
144
133
|
const lines = [];
|
|
@@ -329,6 +318,11 @@ export class AnnotationGenerator {
|
|
|
329
318
|
}
|
|
330
319
|
generateWorkflowFunctionSignature(workflow) {
|
|
331
320
|
const lines = [];
|
|
321
|
+
// Stub workflows use const declaration format
|
|
322
|
+
if (workflow.stub) {
|
|
323
|
+
lines.push(`export const ${workflow.functionName} = 'flowWeaver:draft';`);
|
|
324
|
+
return lines;
|
|
325
|
+
}
|
|
332
326
|
const startPorts = workflow.startPorts || {};
|
|
333
327
|
const exitPorts = workflow.exitPorts || {};
|
|
334
328
|
// Build parameter types (excluding execute)
|
|
@@ -614,5 +608,62 @@ export function generateNodeInstanceTag(instance) {
|
|
|
614
608
|
}
|
|
615
609
|
return ` * @node ${instance.id} ${instance.nodeType}${parent}${labelAttr}${portOrderAttr}${portLabelAttr}${exprAttr}${pullExecutionAttr}${minimizedAttr}${colorAttr}${iconAttr}${tagsAttr}${sizeAttr}${positionAttr}`;
|
|
616
610
|
}
|
|
611
|
+
/**
|
|
612
|
+
* Generate a TypeScript function signature from a node type definition.
|
|
613
|
+
* Handles three modes: stub (declare function), expression (pure function), normal (execute/onSuccess/onFailure).
|
|
614
|
+
*/
|
|
615
|
+
export function generateFunctionSignature(nodeType) {
|
|
616
|
+
const lines = [];
|
|
617
|
+
const isStub = nodeType.variant === 'STUB';
|
|
618
|
+
const isExpression = isStub || nodeType.expression;
|
|
619
|
+
if (isExpression) {
|
|
620
|
+
const params = [];
|
|
621
|
+
Object.entries(nodeType.inputs).forEach(([name, port]) => {
|
|
622
|
+
if (isExecutePort(name))
|
|
623
|
+
return;
|
|
624
|
+
const optional = port.optional ? "?" : "";
|
|
625
|
+
params.push(`${name}${optional}: ${mapToTypeScript(port.dataType)}`);
|
|
626
|
+
});
|
|
627
|
+
const returns = [];
|
|
628
|
+
Object.entries(nodeType.outputs).forEach(([name, port]) => {
|
|
629
|
+
if (isSuccessPort(name) || isFailurePort(name))
|
|
630
|
+
return;
|
|
631
|
+
returns.push(`${name}: ${mapToTypeScript(port.dataType)}`);
|
|
632
|
+
});
|
|
633
|
+
const returnType = returns.length === 1
|
|
634
|
+
? mapToTypeScript((Object.entries(nodeType.outputs).find(([n]) => !isSuccessPort(n) && !isFailurePort(n))?.[1].dataType || 'ANY'))
|
|
635
|
+
: `{ ${returns.join("; ")} }`;
|
|
636
|
+
if (isStub) {
|
|
637
|
+
lines.push(`declare function ${nodeType.functionName}(${params.join(", ")}): ${returnType};`);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
lines.push(`function ${nodeType.functionName}(${params.join(", ")}): ${returnType} {`);
|
|
641
|
+
lines.push(` // TODO: implement`);
|
|
642
|
+
lines.push(` throw new Error('Not implemented');`);
|
|
643
|
+
lines.push(`}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
const params = ["execute: boolean"];
|
|
648
|
+
Object.entries(nodeType.inputs).forEach(([name, port]) => {
|
|
649
|
+
if (isExecutePort(name))
|
|
650
|
+
return;
|
|
651
|
+
const optional = port.optional ? "?" : "";
|
|
652
|
+
const defaultVal = port.default !== undefined ? ` = ${JSON.stringify(port.default)}` : "";
|
|
653
|
+
params.push(`${name}${optional}: ${mapToTypeScript(port.dataType)}${defaultVal}`);
|
|
654
|
+
});
|
|
655
|
+
const returns = ["onSuccess: boolean", "onFailure: boolean"];
|
|
656
|
+
Object.entries(nodeType.outputs).forEach(([name, port]) => {
|
|
657
|
+
if (isSuccessPort(name) || isFailurePort(name))
|
|
658
|
+
return;
|
|
659
|
+
returns.push(`${name}: ${mapToTypeScript(port.dataType)}`);
|
|
660
|
+
});
|
|
661
|
+
lines.push(`function ${nodeType.functionName}(${params.join(", ")}) {`);
|
|
662
|
+
lines.push(` if (!execute) return { onSuccess: false, onFailure: false };`);
|
|
663
|
+
lines.push(` return { onSuccess: true, onFailure: false };`);
|
|
664
|
+
lines.push(`}`);
|
|
665
|
+
}
|
|
666
|
+
return lines;
|
|
667
|
+
}
|
|
617
668
|
export const annotationGenerator = new AnnotationGenerator();
|
|
618
669
|
//# sourceMappingURL=annotation-generator.js.map
|
package/dist/api/generate.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ export interface GenerateOptions extends Partial<ASTGenerateOptions> {
|
|
|
37
37
|
* @example { 'add': '../node-types/add.js', 'greet': '../node-types/greet.js' }
|
|
38
38
|
*/
|
|
39
39
|
externalNodeTypes?: Record<string, string>;
|
|
40
|
+
/**
|
|
41
|
+
* Allow generation even when stub nodes exist. Stub nodes will emit
|
|
42
|
+
* a throw statement at runtime. Default: false (refuse to generate with stubs).
|
|
43
|
+
*/
|
|
44
|
+
generateStubs?: boolean;
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
47
|
* Generate an import statement in the appropriate module format
|
package/dist/api/generate.js
CHANGED
|
@@ -38,7 +38,14 @@ export function generateModuleExports(functionNames) {
|
|
|
38
38
|
return `module.exports = { ${functionNames.join(', ')} };`;
|
|
39
39
|
}
|
|
40
40
|
export function generateCode(ast, options) {
|
|
41
|
-
const { production = false, sourceMap = false, allWorkflows = [], moduleFormat = 'esm', externalRuntimePath, constants = [], externalNodeTypes = {}, } = options || {};
|
|
41
|
+
const { production = false, sourceMap = false, allWorkflows = [], moduleFormat = 'esm', externalRuntimePath, constants = [], externalNodeTypes = {}, generateStubs = false, } = options || {};
|
|
42
|
+
// Check for stub nodes — refuse to generate unless explicitly allowed
|
|
43
|
+
const stubNodeTypes = ast.nodeTypes.filter((nt) => nt.variant === 'STUB');
|
|
44
|
+
if (stubNodeTypes.length > 0 && !generateStubs) {
|
|
45
|
+
const stubNames = stubNodeTypes.map((nt) => nt.functionName).join(', ');
|
|
46
|
+
throw new Error(`Cannot generate code: workflow has ${stubNodeTypes.length} stub node(s) without implementation: ${stubNames}. ` +
|
|
47
|
+
`Implement them or pass { generateStubs: true } to emit placeholder throws.`);
|
|
48
|
+
}
|
|
42
49
|
// Determine if workflow should be async based on node composition
|
|
43
50
|
const { shouldBeAsync, warning } = validateWorkflowAsync(ast, ast.nodeTypes);
|
|
44
51
|
if (warning && !production) {
|
package/dist/api/validate.d.ts
CHANGED
|
@@ -18,8 +18,11 @@ export interface ValidationResult {
|
|
|
18
18
|
* Agent rules are always applied automatically.
|
|
19
19
|
*
|
|
20
20
|
* @param ast - The workflow AST to validate
|
|
21
|
-
* @param
|
|
21
|
+
* @param options - Validation options: custom rules and/or draft mode
|
|
22
22
|
* @returns ValidationResult with errors and warnings
|
|
23
23
|
*/
|
|
24
|
-
export declare function validateWorkflow(ast: TWorkflowAST,
|
|
24
|
+
export declare function validateWorkflow(ast: TWorkflowAST, options?: {
|
|
25
|
+
customRules?: TValidationRule[];
|
|
26
|
+
mode?: 'strict' | 'draft';
|
|
27
|
+
}): ValidationResult;
|
|
25
28
|
//# sourceMappingURL=validate.d.ts.map
|
package/dist/api/validate.js
CHANGED
|
@@ -13,14 +13,14 @@ import { getAgentValidationRules } from "../validation/agent-rules.js";
|
|
|
13
13
|
* Agent rules are always applied automatically.
|
|
14
14
|
*
|
|
15
15
|
* @param ast - The workflow AST to validate
|
|
16
|
-
* @param
|
|
16
|
+
* @param options - Validation options: custom rules and/or draft mode
|
|
17
17
|
* @returns ValidationResult with errors and warnings
|
|
18
18
|
*/
|
|
19
|
-
export function validateWorkflow(ast,
|
|
19
|
+
export function validateWorkflow(ast, options) {
|
|
20
20
|
// Use the consolidated validator
|
|
21
|
-
const result = validator.validate(ast);
|
|
21
|
+
const result = validator.validate(ast, { mode: options?.mode });
|
|
22
22
|
// Apply agent-specific rules + any custom rules
|
|
23
|
-
const allRules = [...getAgentValidationRules(), ...(customRules || [])];
|
|
23
|
+
const allRules = [...getAgentValidationRules(), ...(options?.customRules || [])];
|
|
24
24
|
for (const rule of allRules) {
|
|
25
25
|
const ruleResults = rule.validate(ast);
|
|
26
26
|
for (const err of ruleResults) {
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -104,6 +104,8 @@ export type TWorkflowAST = {
|
|
|
104
104
|
availableFunctionNames?: string[];
|
|
105
105
|
/** Sugar macros (@map, @filter) that expand to full scope patterns. Stored for round-trip preservation. */
|
|
106
106
|
macros?: TWorkflowMacro[];
|
|
107
|
+
/** Whether this workflow was defined as a stub (const declaration, no function body). */
|
|
108
|
+
stub?: boolean;
|
|
107
109
|
/** Reserved for plugin extensibility */
|
|
108
110
|
metadata?: TWorkflowMetadata;
|
|
109
111
|
};
|
|
@@ -240,7 +242,7 @@ export type TNodeTypeAST = {
|
|
|
240
242
|
/** Multiple scopes this node creates */
|
|
241
243
|
scopes?: string[];
|
|
242
244
|
/** Variant identifier (set by app layer) */
|
|
243
|
-
variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR' | 'COERCION';
|
|
245
|
+
variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR' | 'COERCION' | 'STUB';
|
|
244
246
|
/** File path for external node types */
|
|
245
247
|
path?: string;
|
|
246
248
|
/** Function reference for function-based node types */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implement command — replaces a stub node with a real function skeleton
|
|
3
|
+
*/
|
|
4
|
+
export interface ImplementOptions {
|
|
5
|
+
workflowName?: string;
|
|
6
|
+
preview?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function implementCommand(input: string, nodeName: string, options?: ImplementOptions): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=implement.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implement command — replaces a stub node with a real function skeleton
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { parseWorkflow } from '../../api/index.js';
|
|
7
|
+
import { generateFunctionSignature } from '../../annotation-generator.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
10
|
+
/**
|
|
11
|
+
* Find a `declare function <name>(...): ...;` declaration in source text,
|
|
12
|
+
* handling multiline signatures. Returns the full matched text and its
|
|
13
|
+
* leading indentation, or null if not found.
|
|
14
|
+
*/
|
|
15
|
+
function findDeclareFunction(source, functionName) {
|
|
16
|
+
const lines = source.split('\n');
|
|
17
|
+
const escaped = functionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
18
|
+
const startPattern = new RegExp(`^(\\s*)declare\\s+function\\s+${escaped}\\s*\\(`);
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
const m = lines[i].match(startPattern);
|
|
21
|
+
if (!m)
|
|
22
|
+
continue;
|
|
23
|
+
const indent = m[1] || '';
|
|
24
|
+
// Accumulate lines until we find one ending with ;
|
|
25
|
+
let accumulated = lines[i];
|
|
26
|
+
let j = i;
|
|
27
|
+
while (!accumulated.trimEnd().endsWith(';') && j < lines.length - 1) {
|
|
28
|
+
j++;
|
|
29
|
+
accumulated += '\n' + lines[j];
|
|
30
|
+
}
|
|
31
|
+
return { match: accumulated, indent };
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export async function implementCommand(input, nodeName, options = {}) {
|
|
36
|
+
const { workflowName, preview = false } = options;
|
|
37
|
+
try {
|
|
38
|
+
const filePath = path.resolve(input);
|
|
39
|
+
if (!fs.existsSync(filePath)) {
|
|
40
|
+
logger.error(`File not found: ${input}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
44
|
+
if (parseResult.errors.length > 0) {
|
|
45
|
+
logger.error('Parse errors:');
|
|
46
|
+
parseResult.errors.forEach((e) => logger.error(` ${e}`));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const ast = parseResult.ast;
|
|
50
|
+
// Find the stub node type
|
|
51
|
+
const stubNodeType = ast.nodeTypes.find((nt) => nt.variant === 'STUB' && (nt.functionName === nodeName || nt.name === nodeName));
|
|
52
|
+
if (!stubNodeType) {
|
|
53
|
+
const existingNt = ast.nodeTypes.find((nt) => nt.functionName === nodeName || nt.name === nodeName);
|
|
54
|
+
if (existingNt) {
|
|
55
|
+
logger.warn(`Node "${nodeName}" is already implemented.`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
const available = ast.nodeTypes
|
|
59
|
+
.filter((nt) => nt.variant === 'STUB')
|
|
60
|
+
.map((nt) => nt.functionName);
|
|
61
|
+
if (available.length === 0) {
|
|
62
|
+
logger.error('No stub nodes found in this workflow.');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
logger.error(`Stub node "${nodeName}" not found. Available stubs: ${available.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
70
|
+
const found = findDeclareFunction(source, stubNodeType.functionName);
|
|
71
|
+
if (!found) {
|
|
72
|
+
logger.error(`Could not find "declare function ${stubNodeType.functionName}" in source file.`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Generate the real function signature
|
|
76
|
+
const implementedType = { ...stubNodeType, variant: 'FUNCTION' };
|
|
77
|
+
const signatureLines = generateFunctionSignature(implementedType);
|
|
78
|
+
const replacement = signatureLines.map((line) => found.indent + line).join('\n');
|
|
79
|
+
if (preview) {
|
|
80
|
+
logger.section(`Preview: ${stubNodeType.functionName}`);
|
|
81
|
+
logger.newline();
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.log(replacement);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const updated = source.replace(found.match, replacement);
|
|
87
|
+
fs.writeFileSync(filePath, updated, 'utf8');
|
|
88
|
+
logger.success(`Implemented ${stubNodeType.functionName} in ${path.basename(filePath)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
logger.error(`Implement failed: ${getErrorMessage(error)}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=implement.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command — reports implementation progress for stub workflows
|
|
3
|
+
*/
|
|
4
|
+
export interface StatusOptions {
|
|
5
|
+
workflowName?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function statusCommand(input: string, options?: StatusOptions): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command — reports implementation progress for stub workflows
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { parseWorkflow } from '../../api/index.js';
|
|
7
|
+
import { validator } from '../../validator.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { getErrorMessage } from '../../utils/error-utils.js';
|
|
10
|
+
function formatPortList(ports) {
|
|
11
|
+
return Object.entries(ports)
|
|
12
|
+
.filter(([name]) => name !== 'execute' && name !== 'onSuccess' && name !== 'onFailure')
|
|
13
|
+
.map(([name, port]) => `${name}(${port.dataType.toLowerCase()})`);
|
|
14
|
+
}
|
|
15
|
+
export async function statusCommand(input, options = {}) {
|
|
16
|
+
const { workflowName, json = false } = options;
|
|
17
|
+
try {
|
|
18
|
+
const filePath = path.resolve(input);
|
|
19
|
+
if (!fs.existsSync(filePath)) {
|
|
20
|
+
if (json) {
|
|
21
|
+
console.log(JSON.stringify({ error: `File not found: ${input}` }));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
logger.error(`File not found: ${input}`);
|
|
25
|
+
}
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const parseResult = await parseWorkflow(filePath, { workflowName });
|
|
29
|
+
if (parseResult.errors.length > 0) {
|
|
30
|
+
if (json) {
|
|
31
|
+
console.log(JSON.stringify({ error: `Parse errors: ${parseResult.errors.join(', ')}` }));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
logger.error('Parse errors:');
|
|
35
|
+
parseResult.errors.forEach((e) => logger.error(` ${e}`));
|
|
36
|
+
}
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const ast = parseResult.ast;
|
|
40
|
+
// Collect node statuses
|
|
41
|
+
const instanceTypeMap = new Map();
|
|
42
|
+
for (const nt of ast.nodeTypes) {
|
|
43
|
+
instanceTypeMap.set(nt.name, nt);
|
|
44
|
+
if (nt.functionName !== nt.name) {
|
|
45
|
+
instanceTypeMap.set(nt.functionName, nt);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const nodes = [];
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
for (const instance of ast.instances) {
|
|
51
|
+
const nt = instanceTypeMap.get(instance.nodeType);
|
|
52
|
+
if (!nt || seen.has(nt.functionName))
|
|
53
|
+
continue;
|
|
54
|
+
seen.add(nt.functionName);
|
|
55
|
+
nodes.push({
|
|
56
|
+
name: nt.functionName,
|
|
57
|
+
status: nt.variant === 'STUB' ? 'STUB' : 'OK',
|
|
58
|
+
inputs: formatPortList(nt.inputs),
|
|
59
|
+
outputs: formatPortList(nt.outputs),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const implemented = nodes.filter((n) => n.status === 'OK').length;
|
|
63
|
+
const total = nodes.length;
|
|
64
|
+
// Run draft validation for structural checks
|
|
65
|
+
const validation = validator.validate(ast, { mode: 'draft' });
|
|
66
|
+
const structuralErrors = validation.errors
|
|
67
|
+
.filter((e) => e.code !== 'STUB_NODE')
|
|
68
|
+
.map((e) => e.message);
|
|
69
|
+
const result = {
|
|
70
|
+
name: ast.name,
|
|
71
|
+
implemented,
|
|
72
|
+
total,
|
|
73
|
+
nodes,
|
|
74
|
+
structurallyValid: structuralErrors.length === 0,
|
|
75
|
+
structuralErrors,
|
|
76
|
+
};
|
|
77
|
+
if (json) {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
logger.section(`${ast.name}: ${implemented}/${total} nodes implemented`);
|
|
83
|
+
logger.newline();
|
|
84
|
+
const maxNameLen = Math.max(...nodes.map((n) => n.name.length), 0);
|
|
85
|
+
for (const node of nodes) {
|
|
86
|
+
const tag = node.status === 'STUB' ? '[STUB]' : '[OK] ';
|
|
87
|
+
const paddedName = node.name.padEnd(maxNameLen);
|
|
88
|
+
const inputStr = node.inputs.length > 0 ? `inputs: ${node.inputs.join(', ')}` : '';
|
|
89
|
+
const outputStr = node.outputs.length > 0 ? `outputs: ${node.outputs.join(', ')}` : '';
|
|
90
|
+
const arrow = inputStr && outputStr ? ' → ' : '';
|
|
91
|
+
const portsStr = `${inputStr}${arrow}${outputStr}`;
|
|
92
|
+
if (node.status === 'STUB') {
|
|
93
|
+
logger.warn(` ${paddedName} ${tag} ${portsStr}`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
logger.success(` ${paddedName} ${tag} ${portsStr}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (structuralErrors.length > 0) {
|
|
100
|
+
logger.newline();
|
|
101
|
+
logger.error(`Structural errors (${structuralErrors.length}):`);
|
|
102
|
+
structuralErrors.forEach((e) => logger.error(` - ${e}`));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
logger.newline();
|
|
106
|
+
logger.success('Graph structure is valid.');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (json) {
|
|
112
|
+
// eslint-disable-next-line no-console
|
|
113
|
+
console.log(JSON.stringify({ error: getErrorMessage(error) }));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
logger.error(`Status failed: ${getErrorMessage(error)}`);
|
|
117
|
+
}
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=status.js.map
|