0pflow 0.1.0-dev.4fd2ac2 → 0.1.0-dev.582d64d
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/__tests__/discover.integration.test.d.ts +2 -0
- package/dist/__tests__/discover.integration.test.d.ts.map +1 -0
- package/dist/__tests__/discover.integration.test.js +137 -0
- package/dist/__tests__/discover.integration.test.js.map +1 -0
- package/dist/__tests__/factory.test.js +7 -0
- package/dist/__tests__/factory.test.js.map +1 -1
- package/dist/__tests__/integration.e2e.test.js +2 -1
- package/dist/__tests__/integration.e2e.test.js.map +1 -1
- package/dist/__tests__/integration.test.js +87 -82
- package/dist/__tests__/integration.test.js.map +1 -1
- package/dist/agent.d.ts +7 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +59 -9
- package/dist/agent.js.map +1 -1
- package/dist/cli/__tests__/discovery.test.js +1 -1
- package/dist/cli/__tests__/discovery.test.js.map +1 -1
- package/dist/cli/app.d.ts +6 -0
- package/dist/cli/app.d.ts.map +1 -1
- package/dist/cli/app.js +27 -0
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/discovery.d.ts +10 -0
- package/dist/cli/discovery.d.ts.map +1 -1
- package/dist/cli/discovery.js +42 -0
- package/dist/cli/discovery.js.map +1 -1
- package/dist/cli/env.js +1 -1
- package/dist/cli/env.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +124 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/install.js +39 -9
- package/dist/cli/install.js.map +1 -1
- package/dist/cli/mcp/lib/scaffolding.d.ts +33 -0
- package/dist/cli/mcp/lib/scaffolding.d.ts.map +1 -0
- package/dist/cli/mcp/lib/scaffolding.js +219 -0
- package/dist/cli/mcp/lib/scaffolding.js.map +1 -0
- package/dist/cli/mcp/lib/templates.d.ts +1 -0
- package/dist/cli/mcp/lib/templates.d.ts.map +1 -1
- package/dist/cli/mcp/lib/templates.js.map +1 -1
- package/dist/cli/mcp/tools/createApp.d.ts +1 -0
- package/dist/cli/mcp/tools/createApp.d.ts.map +1 -1
- package/dist/cli/mcp/tools/createApp.js +12 -55
- package/dist/cli/mcp/tools/createApp.js.map +1 -1
- package/dist/cli/mcp/tools/createDatabase.d.ts.map +1 -1
- package/dist/cli/mcp/tools/createDatabase.js +2 -41
- package/dist/cli/mcp/tools/createDatabase.js.map +1 -1
- package/dist/cli/mcp/tools/getConnectionInfo.d.ts +19 -0
- package/dist/cli/mcp/tools/getConnectionInfo.d.ts.map +1 -0
- package/dist/cli/mcp/tools/getConnectionInfo.js +111 -0
- package/dist/cli/mcp/tools/getConnectionInfo.js.map +1 -0
- package/dist/cli/mcp/tools/getRun.d.ts +22 -0
- package/dist/cli/mcp/tools/getRun.d.ts.map +1 -0
- package/dist/cli/mcp/tools/getRun.js +80 -0
- package/dist/cli/mcp/tools/getRun.js.map +1 -0
- package/dist/cli/mcp/tools/getTrace.d.ts +32 -0
- package/dist/cli/mcp/tools/getTrace.d.ts.map +1 -0
- package/dist/cli/mcp/tools/getTrace.js +104 -0
- package/dist/cli/mcp/tools/getTrace.js.map +1 -0
- package/dist/cli/mcp/tools/index.d.ts +94 -1
- package/dist/cli/mcp/tools/index.d.ts.map +1 -1
- package/dist/cli/mcp/tools/index.js +16 -0
- package/dist/cli/mcp/tools/index.js.map +1 -1
- package/dist/cli/mcp/tools/listIntegrations.d.ts +14 -0
- package/dist/cli/mcp/tools/listIntegrations.d.ts.map +1 -0
- package/dist/cli/mcp/tools/listIntegrations.js +53 -0
- package/dist/cli/mcp/tools/listIntegrations.js.map +1 -0
- package/dist/cli/mcp/tools/listRuns.d.ts +21 -0
- package/dist/cli/mcp/tools/listRuns.d.ts.map +1 -0
- package/dist/cli/mcp/tools/listRuns.js +72 -0
- package/dist/cli/mcp/tools/listRuns.js.map +1 -0
- package/dist/cli/mcp/tools/listWorkflows.d.ts +15 -0
- package/dist/cli/mcp/tools/listWorkflows.d.ts.map +1 -0
- package/dist/cli/mcp/tools/listWorkflows.js +45 -0
- package/dist/cli/mcp/tools/listWorkflows.js.map +1 -0
- package/dist/cli/mcp/tools/runNode.d.ts +17 -0
- package/dist/cli/mcp/tools/runNode.d.ts.map +1 -0
- package/dist/cli/mcp/tools/runNode.js +74 -0
- package/dist/cli/mcp/tools/runNode.js.map +1 -0
- package/dist/cli/mcp/tools/runWorkflow.d.ts +16 -0
- package/dist/cli/mcp/tools/runWorkflow.d.ts.map +1 -0
- package/dist/cli/mcp/tools/runWorkflow.js +66 -0
- package/dist/cli/mcp/tools/runWorkflow.js.map +1 -0
- package/dist/cli/mcp/tools/setupAppSchema.d.ts +1 -1
- package/dist/cli/mcp/tools/setupAppSchema.d.ts.map +1 -1
- package/dist/cli/mcp/tools/setupAppSchema.js +11 -132
- package/dist/cli/mcp/tools/setupAppSchema.js.map +1 -1
- package/dist/cli/mcp/tools/utils.d.ts +7 -0
- package/dist/cli/mcp/tools/utils.d.ts.map +1 -0
- package/dist/cli/mcp/tools/utils.js +28 -0
- package/dist/cli/mcp/tools/utils.js.map +1 -0
- package/dist/cli/run.d.ts +2 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +391 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/trace.d.ts +5 -0
- package/dist/cli/trace.d.ts.map +1 -1
- package/dist/cli/trace.js +1 -1
- package/dist/cli/trace.js.map +1 -1
- package/dist/connections/cloud-auth.d.ts +46 -0
- package/dist/connections/cloud-auth.d.ts.map +1 -0
- package/dist/connections/cloud-auth.js +243 -0
- package/dist/connections/cloud-auth.js.map +1 -0
- package/dist/connections/cloud-client.d.ts +25 -0
- package/dist/connections/cloud-client.d.ts.map +1 -0
- package/dist/connections/cloud-client.js +59 -0
- package/dist/connections/cloud-client.js.map +1 -0
- package/dist/connections/cloud-integration-provider.d.ts +21 -0
- package/dist/connections/cloud-integration-provider.d.ts.map +1 -0
- package/dist/connections/cloud-integration-provider.js +26 -0
- package/dist/connections/cloud-integration-provider.js.map +1 -0
- package/dist/connections/index.d.ts +11 -0
- package/dist/connections/index.d.ts.map +1 -0
- package/dist/connections/index.js +9 -0
- package/dist/connections/index.js.map +1 -0
- package/dist/connections/integration-provider.d.ts +38 -0
- package/dist/connections/integration-provider.d.ts.map +1 -0
- package/dist/connections/integration-provider.js +20 -0
- package/dist/connections/integration-provider.js.map +1 -0
- package/dist/connections/local-integration-provider.d.ts +28 -0
- package/dist/connections/local-integration-provider.d.ts.map +1 -0
- package/dist/connections/local-integration-provider.js +54 -0
- package/dist/connections/local-integration-provider.js.map +1 -0
- package/dist/connections/nango-client.d.ts +14 -0
- package/dist/connections/nango-client.d.ts.map +1 -0
- package/dist/connections/nango-client.js +50 -0
- package/dist/connections/nango-client.js.map +1 -0
- package/dist/connections/resolver.d.ts +26 -0
- package/dist/connections/resolver.d.ts.map +1 -0
- package/dist/connections/resolver.js +48 -0
- package/dist/connections/resolver.js.map +1 -0
- package/dist/connections/schema.d.ts +8 -0
- package/dist/connections/schema.d.ts.map +1 -0
- package/dist/connections/schema.js +31 -0
- package/dist/connections/schema.js.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +4 -0
- package/dist/context.js.map +1 -1
- package/dist/dev-ui/api.d.ts +16 -0
- package/dist/dev-ui/api.d.ts.map +1 -0
- package/dist/dev-ui/api.js +237 -0
- package/dist/dev-ui/api.js.map +1 -0
- package/dist/dev-ui/dag/extractor.d.ts +19 -0
- package/dist/dev-ui/dag/extractor.d.ts.map +1 -0
- package/dist/dev-ui/dag/extractor.js +716 -0
- package/dist/dev-ui/dag/extractor.js.map +1 -0
- package/dist/dev-ui/dag/types.d.ts +42 -0
- package/dist/dev-ui/dag/types.d.ts.map +1 -0
- package/dist/dev-ui/dag/types.js +2 -0
- package/dist/dev-ui/dag/types.js.map +1 -0
- package/dist/dev-ui/dev-server.d.ts +18 -0
- package/dist/dev-ui/dev-server.d.ts.map +1 -0
- package/dist/dev-ui/dev-server.js +222 -0
- package/dist/dev-ui/dev-server.js.map +1 -0
- package/dist/dev-ui/index.d.ts +3 -0
- package/dist/dev-ui/index.d.ts.map +1 -0
- package/dist/dev-ui/index.js +2 -0
- package/dist/dev-ui/index.js.map +1 -0
- package/dist/dev-ui/pty.d.ts +16 -0
- package/dist/dev-ui/pty.d.ts.map +1 -0
- package/dist/dev-ui/pty.js +87 -0
- package/dist/dev-ui/pty.js.map +1 -0
- package/dist/dev-ui/watcher.d.ts +12 -0
- package/dist/dev-ui/watcher.d.ts.map +1 -0
- package/dist/dev-ui/watcher.js +162 -0
- package/dist/dev-ui/watcher.js.map +1 -0
- package/dist/dev-ui/ws.d.ts +52 -0
- package/dist/dev-ui/ws.d.ts.map +1 -0
- package/dist/dev-ui/ws.js +32 -0
- package/dist/dev-ui/ws.js.map +1 -0
- package/dist/dev-ui-client/assets/index-C-LxzUII.css +32 -0
- package/dist/dev-ui-client/assets/index-DAKTQEvj.js +1 -0
- package/dist/dev-ui-client/assets/index-aAIwXl4O.js +127 -0
- package/dist/dev-ui-client/index.html +13 -0
- package/dist/discover.d.ts +15 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +29 -0
- package/dist/discover.js.map +1 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +25 -12
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +1 -0
- package/dist/node.js.map +1 -1
- package/dist/nodes/agent/executor.d.ts +2 -0
- package/dist/nodes/agent/executor.d.ts.map +1 -1
- package/dist/nodes/agent/executor.js +11 -1
- package/dist/nodes/agent/executor.js.map +1 -1
- package/dist/types.d.ts +21 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow.d.ts +22 -0
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +97 -2
- package/dist/workflow.js.map +1 -1
- package/package.json +26 -5
- package/templates/app/dbos-config.yaml +6 -0
- package/templates/app/package.json +4 -1
- package/templates/app/src/app/api/workflow/[name]/route.ts +37 -0
- package/templates/app/src/instrumentation.ts +6 -0
- package/templates/app/src/lib/pflow.ts +29 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { resolve, dirname } from "node:path";
|
|
3
|
+
let parserReady = null;
|
|
4
|
+
async function getParser() {
|
|
5
|
+
if (parserReady)
|
|
6
|
+
return parserReady;
|
|
7
|
+
parserReady = (async () => {
|
|
8
|
+
const TreeSitter = (await import("web-tree-sitter")).default;
|
|
9
|
+
await TreeSitter.init();
|
|
10
|
+
// Locate the .wasm file from the tree-sitter-typescript package
|
|
11
|
+
// Use createRequire so Node's module resolution handles hoisted packages correctly
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
let wasmPath;
|
|
14
|
+
try {
|
|
15
|
+
const tsPkgJson = require.resolve("tree-sitter-typescript/package.json");
|
|
16
|
+
wasmPath = resolve(dirname(tsPkgJson), "tree-sitter-typescript.wasm");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error("Could not find tree-sitter-typescript.wasm. Make sure tree-sitter-typescript is installed.");
|
|
20
|
+
}
|
|
21
|
+
const TypeScript = await TreeSitter.Language.load(wasmPath);
|
|
22
|
+
const parser = new TreeSitter();
|
|
23
|
+
parser.setLanguage(TypeScript);
|
|
24
|
+
return parser;
|
|
25
|
+
})();
|
|
26
|
+
return parserReady;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Determine executable type from import path.
|
|
30
|
+
*/
|
|
31
|
+
function inferType(importSource) {
|
|
32
|
+
if (importSource === "0pflow" || importSource === "0pflow/nodes")
|
|
33
|
+
return "node";
|
|
34
|
+
if (importSource.includes("agents/"))
|
|
35
|
+
return "agent";
|
|
36
|
+
if (importSource.includes("workflows/"))
|
|
37
|
+
return "workflow";
|
|
38
|
+
if (importSource.includes("nodes/"))
|
|
39
|
+
return "node";
|
|
40
|
+
return "node";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Convert a camelCase or PascalCase identifier to a human-readable label.
|
|
44
|
+
* e.g. "querySalesforceLeads" → "Query Salesforce Leads"
|
|
45
|
+
* "pageSummarizer" → "Page Summarizer"
|
|
46
|
+
* "webRead" → "Web Read"
|
|
47
|
+
*/
|
|
48
|
+
function humanize(identifier) {
|
|
49
|
+
// Insert spaces before uppercase letters (camelCase → camel Case)
|
|
50
|
+
const spaced = identifier.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
51
|
+
// Capitalize first letter
|
|
52
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
53
|
+
}
|
|
54
|
+
function extractStringProperty(obj, propName) {
|
|
55
|
+
for (const prop of obj.namedChildren) {
|
|
56
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
57
|
+
const key = prop.namedChildren[0];
|
|
58
|
+
const value = prop.namedChildren[1];
|
|
59
|
+
if (key?.text === propName && value) {
|
|
60
|
+
if (value.type === "template_string") {
|
|
61
|
+
// Strip backticks
|
|
62
|
+
return value.text.replace(/^`|`$/g, "").trim();
|
|
63
|
+
}
|
|
64
|
+
if (value.type === "string") {
|
|
65
|
+
return value.text.replace(/^['"]|['"]$/g, "").trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract a string array property (e.g. integrations: ["salesforce", "hubspot"]) from an object node.
|
|
74
|
+
*/
|
|
75
|
+
function extractStringArrayProperty(obj, propName) {
|
|
76
|
+
for (const prop of obj.namedChildren) {
|
|
77
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
78
|
+
const key = prop.namedChildren[0];
|
|
79
|
+
const value = prop.namedChildren[1];
|
|
80
|
+
if (key?.text === propName && value?.type === "array") {
|
|
81
|
+
const items = [];
|
|
82
|
+
for (const elem of value.namedChildren) {
|
|
83
|
+
if (elem.type === "string") {
|
|
84
|
+
items.push(elem.text.replace(/^['"]|['"]$/g, ""));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return items.length > 0 ? items : null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse a node source file and extract the integrations array from its .create() config.
|
|
95
|
+
*/
|
|
96
|
+
export async function extractNodeIntegrations(source) {
|
|
97
|
+
const parser = await getParser();
|
|
98
|
+
const tree = parser.parse(source);
|
|
99
|
+
const root = tree.rootNode;
|
|
100
|
+
const calls = root.descendantsOfType("call_expression");
|
|
101
|
+
for (const call of calls) {
|
|
102
|
+
const fn = call.namedChildren[0];
|
|
103
|
+
if (!fn || fn.type !== "member_expression")
|
|
104
|
+
continue;
|
|
105
|
+
const prop = fn.namedChildren[1];
|
|
106
|
+
if (prop?.text !== "create")
|
|
107
|
+
continue;
|
|
108
|
+
const args = call.descendantsOfType("arguments")[0];
|
|
109
|
+
if (!args)
|
|
110
|
+
continue;
|
|
111
|
+
const obj = args.descendantsOfType("object")[0];
|
|
112
|
+
if (!obj)
|
|
113
|
+
continue;
|
|
114
|
+
const integrations = extractStringArrayProperty(obj, "integrations");
|
|
115
|
+
if (integrations)
|
|
116
|
+
return integrations;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Parse a node source file and extract the name from its .create() config.
|
|
122
|
+
*/
|
|
123
|
+
export async function extractNodeName(source) {
|
|
124
|
+
const parser = await getParser();
|
|
125
|
+
const tree = parser.parse(source);
|
|
126
|
+
const root = tree.rootNode;
|
|
127
|
+
const calls = root.descendantsOfType("call_expression");
|
|
128
|
+
for (const call of calls) {
|
|
129
|
+
const fn = call.namedChildren[0];
|
|
130
|
+
if (!fn || fn.type !== "member_expression")
|
|
131
|
+
continue;
|
|
132
|
+
const prop = fn.namedChildren[1];
|
|
133
|
+
if (prop?.text !== "create")
|
|
134
|
+
continue;
|
|
135
|
+
const args = call.descendantsOfType("arguments")[0];
|
|
136
|
+
if (!args)
|
|
137
|
+
continue;
|
|
138
|
+
const obj = args.descendantsOfType("object")[0];
|
|
139
|
+
if (!obj)
|
|
140
|
+
continue;
|
|
141
|
+
const name = extractStringProperty(obj, "name");
|
|
142
|
+
if (name)
|
|
143
|
+
return name;
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parse a node source file and extract the description from its .create() config.
|
|
149
|
+
*/
|
|
150
|
+
export async function extractNodeDescription(source) {
|
|
151
|
+
const parser = await getParser();
|
|
152
|
+
const tree = parser.parse(source);
|
|
153
|
+
const root = tree.rootNode;
|
|
154
|
+
// Find any .create() call
|
|
155
|
+
const calls = root.descendantsOfType("call_expression");
|
|
156
|
+
for (const call of calls) {
|
|
157
|
+
const fn = call.namedChildren[0];
|
|
158
|
+
if (!fn || fn.type !== "member_expression")
|
|
159
|
+
continue;
|
|
160
|
+
const prop = fn.namedChildren[1];
|
|
161
|
+
if (prop?.text !== "create")
|
|
162
|
+
continue;
|
|
163
|
+
const args = call.descendantsOfType("arguments")[0];
|
|
164
|
+
if (!args)
|
|
165
|
+
continue;
|
|
166
|
+
const obj = args.descendantsOfType("object")[0];
|
|
167
|
+
if (!obj)
|
|
168
|
+
continue;
|
|
169
|
+
const desc = extractStringProperty(obj, "description");
|
|
170
|
+
if (desc)
|
|
171
|
+
return desc;
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function extractImports(root) {
|
|
176
|
+
const imports = new Map();
|
|
177
|
+
const importStatements = root.descendantsOfType("import_statement");
|
|
178
|
+
for (const stmt of importStatements) {
|
|
179
|
+
const sourceNode = stmt.descendantsOfType("string")[0];
|
|
180
|
+
if (!sourceNode)
|
|
181
|
+
continue;
|
|
182
|
+
const source = sourceNode.text.replace(/^['"]|['"]$/g, "");
|
|
183
|
+
const importSpecifiers = stmt.descendantsOfType("import_specifier");
|
|
184
|
+
for (const spec of importSpecifiers) {
|
|
185
|
+
const names = spec.namedChildren;
|
|
186
|
+
const identifier = names.length > 1 ? names[1].text : names[0]?.text;
|
|
187
|
+
if (identifier) {
|
|
188
|
+
imports.set(identifier, { identifier, source });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const identifiers = stmt.descendantsOfType("identifier");
|
|
192
|
+
for (const id of identifiers) {
|
|
193
|
+
if (id.parent?.type === "import_clause") {
|
|
194
|
+
imports.set(id.text, { identifier: id.text, source });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return imports;
|
|
199
|
+
}
|
|
200
|
+
function findWorkflowCreateCalls(root) {
|
|
201
|
+
const calls = root.descendantsOfType("call_expression");
|
|
202
|
+
const results = [];
|
|
203
|
+
for (const call of calls) {
|
|
204
|
+
const fn = call.namedChildren[0];
|
|
205
|
+
if (!fn)
|
|
206
|
+
continue;
|
|
207
|
+
if (fn.type === "member_expression") {
|
|
208
|
+
const obj = fn.namedChildren[0];
|
|
209
|
+
const prop = fn.namedChildren[1];
|
|
210
|
+
if (obj?.text === "Workflow" && prop?.text === "create") {
|
|
211
|
+
results.push(call);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return results;
|
|
216
|
+
}
|
|
217
|
+
function extractWorkflowName(createCall) {
|
|
218
|
+
const args = createCall.descendantsOfType("arguments")[0];
|
|
219
|
+
if (!args)
|
|
220
|
+
return null;
|
|
221
|
+
const obj = args.descendantsOfType("object")[0];
|
|
222
|
+
if (!obj)
|
|
223
|
+
return null;
|
|
224
|
+
for (const prop of obj.namedChildren) {
|
|
225
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
226
|
+
const key = prop.namedChildren[0];
|
|
227
|
+
const value = prop.namedChildren[1];
|
|
228
|
+
if (key?.text === "name" && value) {
|
|
229
|
+
return value.text.replace(/^['"]|['"]$/g, "");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
function extractWorkflowVersion(createCall) {
|
|
236
|
+
const args = createCall.descendantsOfType("arguments")[0];
|
|
237
|
+
if (!args)
|
|
238
|
+
return 1;
|
|
239
|
+
const obj = args.descendantsOfType("object")[0];
|
|
240
|
+
if (!obj)
|
|
241
|
+
return 1;
|
|
242
|
+
for (const prop of obj.namedChildren) {
|
|
243
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
244
|
+
const key = prop.namedChildren[0];
|
|
245
|
+
const value = prop.namedChildren[1];
|
|
246
|
+
if (key?.text === "version" && value) {
|
|
247
|
+
const n = parseInt(value.text, 10);
|
|
248
|
+
return isNaN(n) ? 1 : n;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Extract field names from a z.object({...}) schema variable.
|
|
256
|
+
* Given the schema variable name (e.g. "UrlSummarizerInputSchema"),
|
|
257
|
+
* find its declaration and extract the top-level keys from z.object({...}).
|
|
258
|
+
*/
|
|
259
|
+
function extractSchemaFields(root, schemaVarName) {
|
|
260
|
+
// Find variable declaration: const <name> = z.object({...})
|
|
261
|
+
const declarations = root.descendantsOfType("variable_declarator");
|
|
262
|
+
for (const decl of declarations) {
|
|
263
|
+
const nameNode = decl.namedChildren[0];
|
|
264
|
+
if (nameNode?.text !== schemaVarName)
|
|
265
|
+
continue;
|
|
266
|
+
// Find the z.object call in the initializer
|
|
267
|
+
const calls = decl.descendantsOfType("call_expression");
|
|
268
|
+
for (const call of calls) {
|
|
269
|
+
const fn = call.namedChildren[0];
|
|
270
|
+
if (!fn || fn.type !== "member_expression")
|
|
271
|
+
continue;
|
|
272
|
+
if (fn.namedChildren[0]?.text !== "z" || fn.namedChildren[1]?.text !== "object")
|
|
273
|
+
continue;
|
|
274
|
+
// Found z.object(...) — extract keys from the object argument
|
|
275
|
+
const args = call.descendantsOfType("arguments")[0];
|
|
276
|
+
if (!args)
|
|
277
|
+
continue;
|
|
278
|
+
const obj = args.descendantsOfType("object")[0];
|
|
279
|
+
if (!obj)
|
|
280
|
+
continue;
|
|
281
|
+
const fields = [];
|
|
282
|
+
for (const prop of obj.namedChildren) {
|
|
283
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
284
|
+
const key = prop.namedChildren[0];
|
|
285
|
+
if (key)
|
|
286
|
+
fields.push(key.text);
|
|
287
|
+
}
|
|
288
|
+
if (prop.type === "shorthand_property_identifier" || prop.type === "shorthand_property_identifier_pattern") {
|
|
289
|
+
fields.push(prop.text);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return fields;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get the schema variable name from the Workflow.create() config object.
|
|
299
|
+
* Looks for inputSchema/outputSchema property values.
|
|
300
|
+
*/
|
|
301
|
+
function getSchemaVarName(createCall, propName) {
|
|
302
|
+
const args = createCall.descendantsOfType("arguments")[0];
|
|
303
|
+
if (!args)
|
|
304
|
+
return null;
|
|
305
|
+
const obj = args.descendantsOfType("object")[0];
|
|
306
|
+
if (!obj)
|
|
307
|
+
return null;
|
|
308
|
+
for (const prop of obj.namedChildren) {
|
|
309
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
310
|
+
const key = prop.namedChildren[0];
|
|
311
|
+
const value = prop.namedChildren[1];
|
|
312
|
+
if (key?.text === propName && value) {
|
|
313
|
+
return value.text;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function findRunMethod(createCall) {
|
|
320
|
+
const args = createCall.descendantsOfType("arguments")[0];
|
|
321
|
+
if (!args)
|
|
322
|
+
return null;
|
|
323
|
+
const obj = args.descendantsOfType("object")[0];
|
|
324
|
+
if (!obj)
|
|
325
|
+
return null;
|
|
326
|
+
for (const prop of obj.namedChildren) {
|
|
327
|
+
if (prop.type === "method_definition") {
|
|
328
|
+
const nameNode = prop.childForFieldName("name");
|
|
329
|
+
if (nameNode?.text === "run")
|
|
330
|
+
return prop;
|
|
331
|
+
}
|
|
332
|
+
if (prop.type === "pair" || prop.type === "property_assignment") {
|
|
333
|
+
const key = prop.namedChildren[0];
|
|
334
|
+
if (key?.text === "run") {
|
|
335
|
+
return prop;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
function getContextParamName(runMethod) {
|
|
342
|
+
const params = runMethod.descendantsOfType("formal_parameters")[0];
|
|
343
|
+
if (!params)
|
|
344
|
+
return "ctx";
|
|
345
|
+
const firstParam = params.namedChildren[0];
|
|
346
|
+
if (!firstParam)
|
|
347
|
+
return "ctx";
|
|
348
|
+
if (firstParam.type === "required_parameter" || firstParam.type === "optional_parameter") {
|
|
349
|
+
const pattern = firstParam.childForFieldName("pattern");
|
|
350
|
+
return pattern?.text ?? firstParam.namedChildren[0]?.text ?? "ctx";
|
|
351
|
+
}
|
|
352
|
+
return firstParam.text ?? "ctx";
|
|
353
|
+
}
|
|
354
|
+
function extractCtxRunCalls(body, ctxName) {
|
|
355
|
+
const calls = [];
|
|
356
|
+
const allCalls = body.descendantsOfType("call_expression");
|
|
357
|
+
for (const call of allCalls) {
|
|
358
|
+
const fn = call.namedChildren[0];
|
|
359
|
+
if (!fn || fn.type !== "member_expression")
|
|
360
|
+
continue;
|
|
361
|
+
const obj = fn.namedChildren[0];
|
|
362
|
+
const prop = fn.namedChildren[1];
|
|
363
|
+
if (obj?.text !== ctxName || prop?.text !== "run")
|
|
364
|
+
continue;
|
|
365
|
+
const args = call.descendantsOfType("arguments")[0];
|
|
366
|
+
if (!args)
|
|
367
|
+
continue;
|
|
368
|
+
const firstArg = args.namedChildren[0];
|
|
369
|
+
if (!firstArg)
|
|
370
|
+
continue;
|
|
371
|
+
calls.push({
|
|
372
|
+
executableIdentifier: firstArg.text,
|
|
373
|
+
lineNumber: call.startPosition.row + 1,
|
|
374
|
+
node: call,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return calls;
|
|
378
|
+
}
|
|
379
|
+
const LOOP_TYPES = new Set([
|
|
380
|
+
"for_statement",
|
|
381
|
+
"for_in_statement",
|
|
382
|
+
"while_statement",
|
|
383
|
+
"do_statement",
|
|
384
|
+
]);
|
|
385
|
+
function findEnclosingLoop(callNode, runMethodBody) {
|
|
386
|
+
let current = callNode;
|
|
387
|
+
while (current && current !== runMethodBody) {
|
|
388
|
+
if (LOOP_TYPES.has(current.type))
|
|
389
|
+
return current;
|
|
390
|
+
current = current.parent;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
function extractLoopLabel(loopNode) {
|
|
395
|
+
if (loopNode.type === "for_in_statement") {
|
|
396
|
+
// Covers for...of and for...in
|
|
397
|
+
// Structure: for (const <var> of/in <iterable>) { ... }
|
|
398
|
+
const left = loopNode.childForFieldName("left");
|
|
399
|
+
const right = loopNode.childForFieldName("right");
|
|
400
|
+
// Determine if it's "of" or "in" by checking the text between left and right
|
|
401
|
+
const keyword = loopNode.text.includes(" of ") ? "of" : "in";
|
|
402
|
+
const varName = left?.text.replace(/^(const|let|var)\s+/, "") ?? "item";
|
|
403
|
+
const iterableName = right?.text ?? "items";
|
|
404
|
+
return `for each ${varName} ${keyword} ${iterableName}`;
|
|
405
|
+
}
|
|
406
|
+
if (loopNode.type === "while_statement") {
|
|
407
|
+
const condition = loopNode.childForFieldName("condition");
|
|
408
|
+
return `while ${condition?.text ?? "(...)"}`;
|
|
409
|
+
}
|
|
410
|
+
// for_statement, do_statement
|
|
411
|
+
return "for loop";
|
|
412
|
+
}
|
|
413
|
+
function getEnclosingIfCondition(callNode, runMethodBody) {
|
|
414
|
+
let current = callNode;
|
|
415
|
+
while (current && current !== runMethodBody) {
|
|
416
|
+
if (current.type === "if_statement") {
|
|
417
|
+
const condition = current.childForFieldName("condition");
|
|
418
|
+
const consequence = current.childForFieldName("consequence");
|
|
419
|
+
const alternative = current.childForFieldName("alternative");
|
|
420
|
+
const isInConsequence = consequence && isDescendantOf(callNode, consequence);
|
|
421
|
+
const isInAlternative = alternative && isDescendantOf(callNode, alternative);
|
|
422
|
+
return {
|
|
423
|
+
condition: condition?.text ?? "?",
|
|
424
|
+
isElseBranch: !isInConsequence && !!isInAlternative,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
current = current.parent;
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
function isDescendantOf(node, ancestor) {
|
|
432
|
+
let current = node;
|
|
433
|
+
while (current) {
|
|
434
|
+
if (current === ancestor)
|
|
435
|
+
return true;
|
|
436
|
+
current = current.parent;
|
|
437
|
+
}
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
function isFollowedByReturn(callNode, runMethodBody) {
|
|
441
|
+
let stmt = callNode;
|
|
442
|
+
while (stmt && stmt.type !== "expression_statement" && stmt !== runMethodBody) {
|
|
443
|
+
stmt = stmt.parent;
|
|
444
|
+
}
|
|
445
|
+
if (!stmt || stmt === runMethodBody)
|
|
446
|
+
return false;
|
|
447
|
+
const parent = stmt.parent;
|
|
448
|
+
if (!parent)
|
|
449
|
+
return false;
|
|
450
|
+
const children = parent.namedChildren;
|
|
451
|
+
const idx = children.indexOf(stmt);
|
|
452
|
+
if (idx < 0 || idx >= children.length - 1)
|
|
453
|
+
return false;
|
|
454
|
+
const next = children[idx + 1];
|
|
455
|
+
return next?.type === "return_statement";
|
|
456
|
+
}
|
|
457
|
+
function findGuardClausesBetween(runBody, afterLine, beforeLine) {
|
|
458
|
+
const guards = [];
|
|
459
|
+
for (const child of runBody.namedChildren) {
|
|
460
|
+
const line = child.startPosition.row + 1;
|
|
461
|
+
if (line <= afterLine || line >= beforeLine)
|
|
462
|
+
continue;
|
|
463
|
+
if (child.type === "if_statement") {
|
|
464
|
+
const consequence = child.childForFieldName("consequence");
|
|
465
|
+
if (!consequence)
|
|
466
|
+
continue;
|
|
467
|
+
const returns = consequence.descendantsOfType("return_statement");
|
|
468
|
+
if (returns.length > 0) {
|
|
469
|
+
const condition = child.childForFieldName("condition");
|
|
470
|
+
guards.push({
|
|
471
|
+
condition: condition?.text ?? "?",
|
|
472
|
+
lineNumber: line,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return guards;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Parse a single TypeScript file and extract workflow DAGs from it.
|
|
481
|
+
* Fault-tolerant: returns whatever it can parse, even from broken files.
|
|
482
|
+
*/
|
|
483
|
+
export async function extractDAGs(filePath, source) {
|
|
484
|
+
const parser = await getParser();
|
|
485
|
+
const tree = parser.parse(source);
|
|
486
|
+
const root = tree.rootNode;
|
|
487
|
+
const imports = extractImports(root);
|
|
488
|
+
const workflowCalls = findWorkflowCreateCalls(root);
|
|
489
|
+
const dags = [];
|
|
490
|
+
// Fallback for broken files: if no complete Workflow.create() call found,
|
|
491
|
+
// look for a Workflow.create member expression + any ctx.run() calls in the file
|
|
492
|
+
if (workflowCalls.length === 0) {
|
|
493
|
+
const memberExprs = root.descendantsOfType("member_expression");
|
|
494
|
+
const hasWorkflowCreate = memberExprs.some((m) => m.namedChildren[0]?.text === "Workflow" && m.namedChildren[1]?.text === "create");
|
|
495
|
+
if (hasWorkflowCreate) {
|
|
496
|
+
const allCalls = root.descendantsOfType("call_expression");
|
|
497
|
+
const ctxRunCalls = [];
|
|
498
|
+
for (const call of allCalls) {
|
|
499
|
+
const fn = call.namedChildren[0];
|
|
500
|
+
if (!fn || fn.type !== "member_expression")
|
|
501
|
+
continue;
|
|
502
|
+
const prop = fn.namedChildren[1];
|
|
503
|
+
if (prop?.text !== "run")
|
|
504
|
+
continue;
|
|
505
|
+
const obj = fn.namedChildren[0];
|
|
506
|
+
if (!obj || obj.text === "Workflow" || obj.text === "z")
|
|
507
|
+
continue;
|
|
508
|
+
const args = call.descendantsOfType("arguments")[0];
|
|
509
|
+
const firstArg = args?.namedChildren[0];
|
|
510
|
+
if (!firstArg)
|
|
511
|
+
continue;
|
|
512
|
+
ctxRunCalls.push({
|
|
513
|
+
executableIdentifier: firstArg.text,
|
|
514
|
+
lineNumber: call.startPosition.row + 1,
|
|
515
|
+
node: call,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
if (ctxRunCalls.length > 0) {
|
|
519
|
+
const strings = root.descendantsOfType("string");
|
|
520
|
+
let workflowName = "partial";
|
|
521
|
+
for (const s of strings) {
|
|
522
|
+
const prev = s.parent?.namedChildren[0];
|
|
523
|
+
if (prev?.text === "name") {
|
|
524
|
+
workflowName = s.text.replace(/^['"]|['"]$/g, "");
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const nodes = [{ id: "input", label: "Input", type: "input" }];
|
|
529
|
+
const edges = [];
|
|
530
|
+
let prevId = "input";
|
|
531
|
+
for (let i = 0; i < ctxRunCalls.length; i++) {
|
|
532
|
+
const call = ctxRunCalls[i];
|
|
533
|
+
const importInfo = imports.get(call.executableIdentifier);
|
|
534
|
+
const nodeId = `step-${i}`;
|
|
535
|
+
nodes.push({
|
|
536
|
+
id: nodeId,
|
|
537
|
+
label: humanize(call.executableIdentifier),
|
|
538
|
+
type: importInfo ? inferType(importInfo.source) : "node",
|
|
539
|
+
executableName: call.executableIdentifier,
|
|
540
|
+
importPath: importInfo?.source,
|
|
541
|
+
lineNumber: call.lineNumber,
|
|
542
|
+
});
|
|
543
|
+
edges.push({ id: `${prevId}->${nodeId}`, source: prevId, target: nodeId });
|
|
544
|
+
prevId = nodeId;
|
|
545
|
+
}
|
|
546
|
+
nodes.push({ id: "output", label: "Output", type: "output" });
|
|
547
|
+
edges.push({ id: `${prevId}->output`, source: prevId, target: "output" });
|
|
548
|
+
dags.push({ workflowName, version: 1, filePath, nodes, edges });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return dags;
|
|
552
|
+
}
|
|
553
|
+
for (const createCall of workflowCalls) {
|
|
554
|
+
const workflowName = extractWorkflowName(createCall) ?? "unknown";
|
|
555
|
+
const version = extractWorkflowVersion(createCall);
|
|
556
|
+
// Extract schema fields for richer input/output nodes
|
|
557
|
+
const inputSchemaVar = getSchemaVarName(createCall, "inputSchema");
|
|
558
|
+
const outputSchemaVar = getSchemaVarName(createCall, "outputSchema");
|
|
559
|
+
const inputFields = inputSchemaVar ? extractSchemaFields(root, inputSchemaVar) : [];
|
|
560
|
+
const outputFields = outputSchemaVar ? extractSchemaFields(root, outputSchemaVar) : [];
|
|
561
|
+
const runMethod = findRunMethod(createCall);
|
|
562
|
+
if (!runMethod) {
|
|
563
|
+
dags.push({
|
|
564
|
+
workflowName,
|
|
565
|
+
version,
|
|
566
|
+
filePath,
|
|
567
|
+
nodes: [
|
|
568
|
+
{ id: "input", label: "Input", type: "input", fields: inputFields.length > 0 ? inputFields : undefined },
|
|
569
|
+
{ id: "output", label: "Output", type: "output", fields: outputFields.length > 0 ? outputFields : undefined },
|
|
570
|
+
],
|
|
571
|
+
edges: [{ id: "input->output", source: "input", target: "output" }],
|
|
572
|
+
});
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const ctxName = getContextParamName(runMethod);
|
|
576
|
+
const runBody = runMethod.descendantsOfType("statement_block")[0];
|
|
577
|
+
if (!runBody)
|
|
578
|
+
continue;
|
|
579
|
+
const ctxRunCalls = extractCtxRunCalls(runBody, ctxName);
|
|
580
|
+
const nodes = [];
|
|
581
|
+
const edges = [];
|
|
582
|
+
let condCounter = 0;
|
|
583
|
+
const loopGroupMap = new Map();
|
|
584
|
+
nodes.push({ id: "input", label: "Input", type: "input", fields: inputFields.length > 0 ? inputFields : undefined });
|
|
585
|
+
let prevNodeIds = ["input"];
|
|
586
|
+
let prevLine = 0;
|
|
587
|
+
for (let i = 0; i < ctxRunCalls.length; i++) {
|
|
588
|
+
const call = ctxRunCalls[i];
|
|
589
|
+
const importInfo = imports.get(call.executableIdentifier);
|
|
590
|
+
const execType = importInfo ? inferType(importInfo.source) : "node";
|
|
591
|
+
const nodeId = `step-${i}`;
|
|
592
|
+
const ifContext = getEnclosingIfCondition(call.node, runBody);
|
|
593
|
+
// Track loop group membership
|
|
594
|
+
const enclosingLoop = findEnclosingLoop(call.node, runBody);
|
|
595
|
+
if (enclosingLoop) {
|
|
596
|
+
if (!loopGroupMap.has(enclosingLoop)) {
|
|
597
|
+
loopGroupMap.set(enclosingLoop, {
|
|
598
|
+
label: extractLoopLabel(enclosingLoop),
|
|
599
|
+
stepIds: [],
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
loopGroupMap.get(enclosingLoop).stepIds.push(nodeId);
|
|
603
|
+
}
|
|
604
|
+
const guards = findGuardClausesBetween(runBody, prevLine, call.lineNumber);
|
|
605
|
+
for (const guard of guards) {
|
|
606
|
+
const condId = `condition-${condCounter++}`;
|
|
607
|
+
nodes.push({
|
|
608
|
+
id: condId,
|
|
609
|
+
label: guard.condition,
|
|
610
|
+
type: "condition",
|
|
611
|
+
lineNumber: guard.lineNumber,
|
|
612
|
+
});
|
|
613
|
+
for (const prev of prevNodeIds) {
|
|
614
|
+
edges.push({ id: `${prev}->${condId}`, source: prev, target: condId });
|
|
615
|
+
}
|
|
616
|
+
edges.push({ id: `${condId}->output-guard`, source: condId, target: "output", label: "yes" });
|
|
617
|
+
prevNodeIds = [condId];
|
|
618
|
+
}
|
|
619
|
+
if (ifContext) {
|
|
620
|
+
const condId = `condition-${condCounter++}`;
|
|
621
|
+
const conditionExists = nodes.some((n) => n.type === "condition" && n.label === ifContext.condition);
|
|
622
|
+
if (!conditionExists) {
|
|
623
|
+
nodes.push({
|
|
624
|
+
id: condId,
|
|
625
|
+
label: ifContext.condition,
|
|
626
|
+
type: "condition",
|
|
627
|
+
lineNumber: call.lineNumber,
|
|
628
|
+
});
|
|
629
|
+
for (const prev of prevNodeIds) {
|
|
630
|
+
edges.push({ id: `${prev}->${condId}`, source: prev, target: condId });
|
|
631
|
+
}
|
|
632
|
+
prevNodeIds = [condId];
|
|
633
|
+
}
|
|
634
|
+
nodes.push({
|
|
635
|
+
id: nodeId,
|
|
636
|
+
label: humanize(call.executableIdentifier),
|
|
637
|
+
type: execType,
|
|
638
|
+
executableName: call.executableIdentifier,
|
|
639
|
+
importPath: importInfo?.source,
|
|
640
|
+
lineNumber: call.lineNumber,
|
|
641
|
+
});
|
|
642
|
+
const sourceCondId = nodes.find((n) => n.type === "condition" && n.label === ifContext.condition)?.id ?? condId;
|
|
643
|
+
edges.push({
|
|
644
|
+
id: `${sourceCondId}->${nodeId}`,
|
|
645
|
+
source: sourceCondId,
|
|
646
|
+
target: nodeId,
|
|
647
|
+
label: ifContext.isElseBranch ? "else" : "then",
|
|
648
|
+
});
|
|
649
|
+
if (isFollowedByReturn(call.node, runBody)) {
|
|
650
|
+
edges.push({ id: `${nodeId}->output`, source: nodeId, target: "output" });
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
prevNodeIds = [...prevNodeIds.filter((p) => p !== sourceCondId), nodeId];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
nodes.push({
|
|
658
|
+
id: nodeId,
|
|
659
|
+
label: humanize(call.executableIdentifier),
|
|
660
|
+
type: execType,
|
|
661
|
+
executableName: call.executableIdentifier,
|
|
662
|
+
importPath: importInfo?.source,
|
|
663
|
+
lineNumber: call.lineNumber,
|
|
664
|
+
});
|
|
665
|
+
for (const prev of prevNodeIds) {
|
|
666
|
+
edges.push({ id: `${prev}->${nodeId}`, source: prev, target: nodeId });
|
|
667
|
+
}
|
|
668
|
+
prevNodeIds = [nodeId];
|
|
669
|
+
}
|
|
670
|
+
prevLine = call.lineNumber;
|
|
671
|
+
}
|
|
672
|
+
const lastLine = ctxRunCalls.length > 0 ? ctxRunCalls[ctxRunCalls.length - 1].lineNumber : 0;
|
|
673
|
+
const trailingGuards = findGuardClausesBetween(runBody, lastLine, 999999);
|
|
674
|
+
for (const guard of trailingGuards) {
|
|
675
|
+
const condId = `condition-${condCounter++}`;
|
|
676
|
+
nodes.push({
|
|
677
|
+
id: condId,
|
|
678
|
+
label: guard.condition,
|
|
679
|
+
type: "condition",
|
|
680
|
+
lineNumber: guard.lineNumber,
|
|
681
|
+
});
|
|
682
|
+
for (const prev of prevNodeIds) {
|
|
683
|
+
edges.push({ id: `${prev}->${condId}`, source: prev, target: condId });
|
|
684
|
+
}
|
|
685
|
+
edges.push({ id: `${condId}->output-guard`, source: condId, target: "output", label: "yes" });
|
|
686
|
+
prevNodeIds = [condId];
|
|
687
|
+
}
|
|
688
|
+
nodes.push({ id: "output", label: "Output", type: "output", fields: outputFields.length > 0 ? outputFields : undefined });
|
|
689
|
+
for (const prev of prevNodeIds) {
|
|
690
|
+
const alreadyConnected = edges.some((e) => e.source === prev && e.target === "output");
|
|
691
|
+
if (!alreadyConnected) {
|
|
692
|
+
edges.push({ id: `${prev}->output`, source: prev, target: "output" });
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// Build loop groups array
|
|
696
|
+
const loopGroups = [];
|
|
697
|
+
let loopCounter = 0;
|
|
698
|
+
for (const [, group] of loopGroupMap) {
|
|
699
|
+
loopGroups.push({
|
|
700
|
+
id: `loop-${loopCounter++}`,
|
|
701
|
+
label: group.label,
|
|
702
|
+
nodeIds: group.stepIds,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
dags.push({
|
|
706
|
+
workflowName,
|
|
707
|
+
version,
|
|
708
|
+
filePath,
|
|
709
|
+
nodes,
|
|
710
|
+
edges,
|
|
711
|
+
loopGroups: loopGroups.length > 0 ? loopGroups : undefined,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return dags;
|
|
715
|
+
}
|
|
716
|
+
//# sourceMappingURL=extractor.js.map
|