@pikku/inspector 0.11.0 → 0.11.2
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/CHANGELOG.md +32 -2
- package/dist/add/add-channel.js +11 -10
- package/dist/add/add-file-with-factory.js +10 -10
- package/dist/add/add-forge-credential.d.ts +8 -0
- package/dist/add/add-forge-credential.js +77 -0
- package/dist/add/add-forge-node.d.ts +7 -0
- package/dist/add/add-forge-node.js +77 -0
- package/dist/add/add-functions.js +158 -51
- package/dist/add/add-http-route.js +28 -4
- package/dist/add/add-mcp-prompt.js +6 -5
- package/dist/add/add-mcp-resource.js +6 -5
- package/dist/add/add-mcp-tool.js +6 -5
- package/dist/add/add-middleware.js +1 -1
- package/dist/add/add-permission.js +1 -1
- package/dist/add/add-queue-worker.js +6 -5
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +51 -25
- package/dist/add/add-schedule.js +5 -4
- package/dist/add/add-workflow-graph.d.ts +6 -0
- package/dist/add/add-workflow-graph.js +659 -0
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/add/add-workflow.js +191 -69
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/inspector.js +29 -9
- package/dist/types.d.ts +47 -8
- package/dist/utils/extract-function-name.js +7 -7
- package/dist/utils/extract-function-node.d.ts +10 -0
- package/dist/utils/extract-function-node.js +38 -0
- package/dist/utils/extract-node-value.d.ts +8 -0
- package/dist/utils/extract-node-value.js +24 -0
- package/dist/utils/extract-service-metadata.d.ts +19 -0
- package/dist/utils/extract-service-metadata.js +244 -0
- package/dist/utils/get-files-and-methods.d.ts +3 -3
- package/dist/utils/get-files-and-methods.js +3 -3
- package/dist/utils/get-property-value.d.ts +14 -6
- package/dist/utils/get-property-value.js +55 -43
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +30 -3
- package/dist/utils/serialize-inspector-state.d.ts +42 -6
- package/dist/utils/serialize-inspector-state.js +36 -10
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +17 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +1284 -0
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/utils/workflow/dsl/patterns.d.ts +60 -0
- package/dist/utils/workflow/dsl/patterns.js +218 -0
- package/dist/utils/workflow/dsl/validation.d.ts +30 -0
- package/dist/utils/workflow/dsl/validation.js +142 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
- package/dist/utils/workflow/graph/index.d.ts +6 -0
- package/dist/utils/workflow/graph/index.js +6 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/utils/write-service-metadata.d.ts +13 -0
- package/dist/utils/write-service-metadata.js +37 -0
- package/dist/visit.js +8 -2
- package/package.json +16 -4
- package/src/add/add-channel.ts +37 -17
- package/src/add/add-file-with-factory.ts +10 -10
- package/src/add/add-forge-credential.ts +119 -0
- package/src/add/add-forge-node.ts +132 -0
- package/src/add/add-functions.ts +199 -69
- package/src/add/add-http-route.ts +34 -5
- package/src/add/add-mcp-prompt.ts +11 -7
- package/src/add/add-mcp-resource.ts +11 -7
- package/src/add/add-mcp-tool.ts +11 -7
- package/src/add/add-middleware.ts +1 -1
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-queue-worker.ts +11 -12
- package/src/add/add-rpc-invocations.ts +61 -31
- package/src/add/add-schedule.ts +10 -5
- package/src/add/add-workflow-graph.ts +864 -0
- package/src/add/add-workflow.ts +212 -116
- package/src/error-codes.ts +3 -0
- package/src/index.ts +12 -0
- package/src/inspector.ts +36 -10
- package/src/types.ts +43 -9
- package/src/utils/extract-function-name.ts +7 -7
- package/src/utils/extract-function-node.ts +58 -0
- package/src/utils/extract-node-value.ts +31 -0
- package/src/utils/extract-service-metadata.ts +353 -0
- package/src/utils/filter-inspector-state.test.ts +3 -3
- package/src/utils/filter-utils.test.ts +45 -51
- package/src/utils/get-files-and-methods.ts +11 -11
- package/src/utils/get-property-value.ts +67 -53
- package/src/utils/permissions.test.ts +3 -3
- package/src/utils/post-process.ts +56 -3
- package/src/utils/serialize-inspector-state.ts +67 -19
- package/src/utils/test-data/inspector-state.json +9 -9
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +1608 -0
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/utils/workflow/dsl/patterns.ts +279 -0
- package/src/utils/workflow/dsl/validation.ts +180 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
- package/src/utils/workflow/graph/index.ts +6 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
- package/src/utils/write-service-metadata.ts +51 -0
- package/src/visit.ts +9 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { extractStringLiteral } from '../utils/extract-node-value.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extract wire configuration from object literal
|
|
6
|
+
*/
|
|
7
|
+
function extractWiresConfig(wiresNode, checker) {
|
|
8
|
+
const wires = {};
|
|
9
|
+
for (const prop of wiresNode.properties) {
|
|
10
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
11
|
+
continue;
|
|
12
|
+
const propName = prop.name.text;
|
|
13
|
+
if (propName === 'http' && ts.isArrayLiteralExpression(prop.initializer)) {
|
|
14
|
+
wires.http = [];
|
|
15
|
+
for (const elem of prop.initializer.elements) {
|
|
16
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
17
|
+
const httpWire = {};
|
|
18
|
+
for (const httpProp of elem.properties) {
|
|
19
|
+
if (!ts.isPropertyAssignment(httpProp) ||
|
|
20
|
+
!ts.isIdentifier(httpProp.name))
|
|
21
|
+
continue;
|
|
22
|
+
const httpPropName = httpProp.name.text;
|
|
23
|
+
if (httpPropName === 'route') {
|
|
24
|
+
httpWire.route = extractStringLiteral(httpProp.initializer, checker);
|
|
25
|
+
}
|
|
26
|
+
else if (httpPropName === 'method') {
|
|
27
|
+
httpWire.method = extractStringLiteral(httpProp.initializer, checker);
|
|
28
|
+
}
|
|
29
|
+
else if (httpPropName === 'startNode') {
|
|
30
|
+
httpWire.startNode = extractStringLiteral(httpProp.initializer, checker);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (httpWire.route && httpWire.method && httpWire.startNode) {
|
|
34
|
+
wires.http.push(httpWire);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (propName === 'channel' &&
|
|
40
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
41
|
+
wires.channel = [];
|
|
42
|
+
for (const elem of prop.initializer.elements) {
|
|
43
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
44
|
+
const channelWire = {};
|
|
45
|
+
for (const channelProp of elem.properties) {
|
|
46
|
+
if (!ts.isPropertyAssignment(channelProp) ||
|
|
47
|
+
!ts.isIdentifier(channelProp.name))
|
|
48
|
+
continue;
|
|
49
|
+
const channelPropName = channelProp.name.text;
|
|
50
|
+
if (channelPropName === 'name') {
|
|
51
|
+
channelWire.name = extractStringLiteral(channelProp.initializer, checker);
|
|
52
|
+
}
|
|
53
|
+
else if (channelPropName === 'onConnect') {
|
|
54
|
+
channelWire.onConnect = extractStringLiteral(channelProp.initializer, checker);
|
|
55
|
+
}
|
|
56
|
+
else if (channelPropName === 'onDisconnect') {
|
|
57
|
+
channelWire.onDisconnect = extractStringLiteral(channelProp.initializer, checker);
|
|
58
|
+
}
|
|
59
|
+
else if (channelPropName === 'onMessage') {
|
|
60
|
+
channelWire.onMessage = extractStringLiteral(channelProp.initializer, checker);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (channelWire.name) {
|
|
64
|
+
wires.channel.push(channelWire);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (propName === 'queue' &&
|
|
70
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
71
|
+
wires.queue = [];
|
|
72
|
+
for (const elem of prop.initializer.elements) {
|
|
73
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
74
|
+
const queueWire = {};
|
|
75
|
+
for (const queueProp of elem.properties) {
|
|
76
|
+
if (!ts.isPropertyAssignment(queueProp) ||
|
|
77
|
+
!ts.isIdentifier(queueProp.name))
|
|
78
|
+
continue;
|
|
79
|
+
const queuePropName = queueProp.name.text;
|
|
80
|
+
if (queuePropName === 'name') {
|
|
81
|
+
queueWire.name = extractStringLiteral(queueProp.initializer, checker);
|
|
82
|
+
}
|
|
83
|
+
else if (queuePropName === 'startNode') {
|
|
84
|
+
queueWire.startNode = extractStringLiteral(queueProp.initializer, checker);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (queueWire.name && queueWire.startNode) {
|
|
88
|
+
wires.queue.push(queueWire);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (propName === 'cli' &&
|
|
94
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
95
|
+
wires.cli = [];
|
|
96
|
+
for (const elem of prop.initializer.elements) {
|
|
97
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
98
|
+
const cliWire = {};
|
|
99
|
+
for (const cliProp of elem.properties) {
|
|
100
|
+
if (!ts.isPropertyAssignment(cliProp) ||
|
|
101
|
+
!ts.isIdentifier(cliProp.name))
|
|
102
|
+
continue;
|
|
103
|
+
const cliPropName = cliProp.name.text;
|
|
104
|
+
if (cliPropName === 'command') {
|
|
105
|
+
cliWire.command = extractStringLiteral(cliProp.initializer, checker);
|
|
106
|
+
}
|
|
107
|
+
else if (cliPropName === 'startNode') {
|
|
108
|
+
cliWire.startNode = extractStringLiteral(cliProp.initializer, checker);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (cliWire.command && cliWire.startNode) {
|
|
112
|
+
wires.cli.push(cliWire);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (propName === 'mcp' &&
|
|
118
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
119
|
+
const mcpWires = {};
|
|
120
|
+
for (const mcpProp of prop.initializer.properties) {
|
|
121
|
+
if (!ts.isPropertyAssignment(mcpProp) || !ts.isIdentifier(mcpProp.name))
|
|
122
|
+
continue;
|
|
123
|
+
const mcpPropName = mcpProp.name.text;
|
|
124
|
+
if (mcpPropName === 'tool' &&
|
|
125
|
+
ts.isArrayLiteralExpression(mcpProp.initializer)) {
|
|
126
|
+
mcpWires.tool = extractMcpToolWireArray(mcpProp.initializer, checker);
|
|
127
|
+
}
|
|
128
|
+
else if (mcpPropName === 'prompt' &&
|
|
129
|
+
ts.isArrayLiteralExpression(mcpProp.initializer)) {
|
|
130
|
+
mcpWires.prompt = extractMcpToolWireArray(mcpProp.initializer, checker);
|
|
131
|
+
}
|
|
132
|
+
else if (mcpPropName === 'resource' &&
|
|
133
|
+
ts.isArrayLiteralExpression(mcpProp.initializer)) {
|
|
134
|
+
mcpWires.resource = extractMcpResourceWireArray(mcpProp.initializer, checker);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (mcpWires.tool || mcpWires.prompt || mcpWires.resource) {
|
|
138
|
+
wires.mcp = mcpWires;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (propName === 'schedule' &&
|
|
142
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
143
|
+
wires.schedule = [];
|
|
144
|
+
for (const elem of prop.initializer.elements) {
|
|
145
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
146
|
+
const scheduleWire = {};
|
|
147
|
+
for (const scheduleProp of elem.properties) {
|
|
148
|
+
if (!ts.isPropertyAssignment(scheduleProp) ||
|
|
149
|
+
!ts.isIdentifier(scheduleProp.name))
|
|
150
|
+
continue;
|
|
151
|
+
const schedulePropName = scheduleProp.name.text;
|
|
152
|
+
if (schedulePropName === 'cron') {
|
|
153
|
+
scheduleWire.cron = extractStringLiteral(scheduleProp.initializer, checker);
|
|
154
|
+
}
|
|
155
|
+
else if (schedulePropName === 'interval') {
|
|
156
|
+
scheduleWire.interval = extractStringLiteral(scheduleProp.initializer, checker);
|
|
157
|
+
}
|
|
158
|
+
else if (schedulePropName === 'startNode') {
|
|
159
|
+
scheduleWire.startNode = extractStringLiteral(scheduleProp.initializer, checker);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if ((scheduleWire.cron || scheduleWire.interval) &&
|
|
163
|
+
scheduleWire.startNode) {
|
|
164
|
+
wires.schedule.push(scheduleWire);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (propName === 'trigger' &&
|
|
170
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
171
|
+
wires.trigger = [];
|
|
172
|
+
for (const elem of prop.initializer.elements) {
|
|
173
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
174
|
+
const triggerWire = {};
|
|
175
|
+
for (const triggerProp of elem.properties) {
|
|
176
|
+
if (!ts.isPropertyAssignment(triggerProp) ||
|
|
177
|
+
!ts.isIdentifier(triggerProp.name))
|
|
178
|
+
continue;
|
|
179
|
+
const triggerPropName = triggerProp.name.text;
|
|
180
|
+
if (triggerPropName === 'name') {
|
|
181
|
+
triggerWire.name = extractStringLiteral(triggerProp.initializer, checker);
|
|
182
|
+
}
|
|
183
|
+
else if (triggerPropName === 'startNode') {
|
|
184
|
+
triggerWire.startNode = extractStringLiteral(triggerProp.initializer, checker);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (triggerWire.name && triggerWire.startNode) {
|
|
188
|
+
wires.trigger.push(triggerWire);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return wires;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Helper to extract MCP wire arrays for tool/prompt (name field)
|
|
198
|
+
*/
|
|
199
|
+
function extractMcpToolWireArray(arrayNode, checker) {
|
|
200
|
+
const result = [];
|
|
201
|
+
for (const elem of arrayNode.elements) {
|
|
202
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
203
|
+
let name;
|
|
204
|
+
let startNode;
|
|
205
|
+
for (const prop of elem.properties) {
|
|
206
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
207
|
+
continue;
|
|
208
|
+
const propName = prop.name.text;
|
|
209
|
+
if (propName === 'name') {
|
|
210
|
+
name = extractStringLiteral(prop.initializer, checker);
|
|
211
|
+
}
|
|
212
|
+
else if (propName === 'startNode') {
|
|
213
|
+
startNode = extractStringLiteral(prop.initializer, checker);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (name && startNode) {
|
|
217
|
+
result.push({ name, startNode });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Helper to extract MCP wire arrays for resource (uri field)
|
|
225
|
+
*/
|
|
226
|
+
function extractMcpResourceWireArray(arrayNode, checker) {
|
|
227
|
+
const result = [];
|
|
228
|
+
for (const elem of arrayNode.elements) {
|
|
229
|
+
if (ts.isObjectLiteralExpression(elem)) {
|
|
230
|
+
let uri;
|
|
231
|
+
let startNode;
|
|
232
|
+
for (const prop of elem.properties) {
|
|
233
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
234
|
+
continue;
|
|
235
|
+
const propName = prop.name.text;
|
|
236
|
+
if (propName === 'uri') {
|
|
237
|
+
uri = extractStringLiteral(prop.initializer, checker);
|
|
238
|
+
}
|
|
239
|
+
else if (propName === 'startNode') {
|
|
240
|
+
startNode = extractStringLiteral(prop.initializer, checker);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (uri && startNode) {
|
|
244
|
+
result.push({ uri, startNode });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Extract input mapping from an arrow function
|
|
252
|
+
* Parses: (ref) => ({ key: ref('nodeId', 'path'), key2: 'literal' })
|
|
253
|
+
*/
|
|
254
|
+
function extractInputMapping(node, _checker) {
|
|
255
|
+
if (!ts.isArrowFunction(node)) {
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
let bodyObj;
|
|
259
|
+
if (ts.isObjectLiteralExpression(node.body)) {
|
|
260
|
+
bodyObj = node.body;
|
|
261
|
+
}
|
|
262
|
+
else if (ts.isParenthesizedExpression(node.body)) {
|
|
263
|
+
if (ts.isObjectLiteralExpression(node.body.expression)) {
|
|
264
|
+
bodyObj = node.body.expression;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else if (ts.isBlock(node.body)) {
|
|
268
|
+
for (const stmt of node.body.statements) {
|
|
269
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
270
|
+
if (ts.isObjectLiteralExpression(stmt.expression)) {
|
|
271
|
+
bodyObj = stmt.expression;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!bodyObj) {
|
|
277
|
+
return {};
|
|
278
|
+
}
|
|
279
|
+
const refParamName = node.parameters.length > 0 && ts.isIdentifier(node.parameters[0].name)
|
|
280
|
+
? node.parameters[0].name.text
|
|
281
|
+
: 'ref';
|
|
282
|
+
const input = {};
|
|
283
|
+
for (const prop of bodyObj.properties) {
|
|
284
|
+
if (!ts.isPropertyAssignment(prop))
|
|
285
|
+
continue;
|
|
286
|
+
const key = ts.isIdentifier(prop.name)
|
|
287
|
+
? prop.name.text
|
|
288
|
+
: ts.isStringLiteral(prop.name)
|
|
289
|
+
? prop.name.text
|
|
290
|
+
: null;
|
|
291
|
+
if (!key)
|
|
292
|
+
continue;
|
|
293
|
+
if (ts.isCallExpression(prop.initializer)) {
|
|
294
|
+
const callExpr = prop.initializer.expression;
|
|
295
|
+
if (ts.isIdentifier(callExpr) && callExpr.text === refParamName) {
|
|
296
|
+
const args = prop.initializer.arguments;
|
|
297
|
+
const nodeIdArg = args[0];
|
|
298
|
+
const pathArg = args[1];
|
|
299
|
+
const nodeId = nodeIdArg && ts.isStringLiteral(nodeIdArg)
|
|
300
|
+
? nodeIdArg.text
|
|
301
|
+
: 'unknown';
|
|
302
|
+
const path = pathArg && ts.isStringLiteral(pathArg) ? pathArg.text : undefined;
|
|
303
|
+
input[key] = { $ref: nodeId, path };
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (ts.isStringLiteral(prop.initializer)) {
|
|
308
|
+
input[key] = prop.initializer.text;
|
|
309
|
+
}
|
|
310
|
+
else if (ts.isNumericLiteral(prop.initializer)) {
|
|
311
|
+
input[key] = Number(prop.initializer.text);
|
|
312
|
+
}
|
|
313
|
+
else if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
314
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
|
|
315
|
+
input[key] = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
|
|
316
|
+
}
|
|
317
|
+
else if (prop.initializer.kind === ts.SyntaxKind.NullKeyword) {
|
|
318
|
+
input[key] = null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return input;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Extract next config (string, array, or record)
|
|
325
|
+
*/
|
|
326
|
+
function extractNextConfig(node, _checker) {
|
|
327
|
+
if (ts.isStringLiteral(node)) {
|
|
328
|
+
return node.text;
|
|
329
|
+
}
|
|
330
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
331
|
+
return node.elements
|
|
332
|
+
.filter(ts.isStringLiteral)
|
|
333
|
+
.map((el) => el.text);
|
|
334
|
+
}
|
|
335
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
336
|
+
const result = {};
|
|
337
|
+
for (const prop of node.properties) {
|
|
338
|
+
if (!ts.isPropertyAssignment(prop))
|
|
339
|
+
continue;
|
|
340
|
+
const key = ts.isIdentifier(prop.name)
|
|
341
|
+
? prop.name.text
|
|
342
|
+
: ts.isStringLiteral(prop.name)
|
|
343
|
+
? prop.name.text
|
|
344
|
+
: null;
|
|
345
|
+
if (!key)
|
|
346
|
+
continue;
|
|
347
|
+
if (ts.isStringLiteral(prop.initializer)) {
|
|
348
|
+
result[key] = prop.initializer.text;
|
|
349
|
+
}
|
|
350
|
+
else if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
351
|
+
result[key] = prop.initializer.elements
|
|
352
|
+
.filter(ts.isStringLiteral)
|
|
353
|
+
.map((el) => el.text);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Extract definition object from wireWorkflow call
|
|
362
|
+
*/
|
|
363
|
+
function extractDefinitionObject(firstArg) {
|
|
364
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
365
|
+
return firstArg;
|
|
366
|
+
}
|
|
367
|
+
if (ts.isArrowFunction(firstArg)) {
|
|
368
|
+
const body = firstArg.body;
|
|
369
|
+
if (ts.isObjectLiteralExpression(body)) {
|
|
370
|
+
return body;
|
|
371
|
+
}
|
|
372
|
+
if (ts.isParenthesizedExpression(body)) {
|
|
373
|
+
if (ts.isObjectLiteralExpression(body.expression)) {
|
|
374
|
+
return body.expression;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (ts.isBlock(body)) {
|
|
378
|
+
for (const stmt of body.statements) {
|
|
379
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
380
|
+
if (ts.isObjectLiteralExpression(stmt.expression)) {
|
|
381
|
+
return stmt.expression;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Compute entry node IDs from graph nodes
|
|
391
|
+
*/
|
|
392
|
+
function computeEntryNodeIds(graphNodes) {
|
|
393
|
+
const hasIncomingEdge = new Set();
|
|
394
|
+
for (const node of Object.values(graphNodes)) {
|
|
395
|
+
const next = node.next;
|
|
396
|
+
if (!next)
|
|
397
|
+
continue;
|
|
398
|
+
if (typeof next === 'string') {
|
|
399
|
+
hasIncomingEdge.add(next);
|
|
400
|
+
}
|
|
401
|
+
else if (Array.isArray(next)) {
|
|
402
|
+
next.forEach((n) => hasIncomingEdge.add(n));
|
|
403
|
+
}
|
|
404
|
+
else if (typeof next === 'object') {
|
|
405
|
+
for (const targets of Object.values(next)) {
|
|
406
|
+
if (typeof targets === 'string') {
|
|
407
|
+
hasIncomingEdge.add(targets);
|
|
408
|
+
}
|
|
409
|
+
else if (Array.isArray(targets)) {
|
|
410
|
+
;
|
|
411
|
+
targets.forEach((n) => hasIncomingEdge.add(n));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return Object.keys(graphNodes).filter((nodeId) => !hasIncomingEdge.has(nodeId));
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Extract pikkuWorkflowGraph config from a variable reference or call expression
|
|
420
|
+
* New format: pikkuWorkflowGraph({ nodes: {...}, wires: {...}, config: {...} })
|
|
421
|
+
*/
|
|
422
|
+
function extractPikkuWorkflowGraphConfig(node, checker) {
|
|
423
|
+
// If it's an identifier, resolve to the declaration
|
|
424
|
+
if (ts.isIdentifier(node)) {
|
|
425
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
426
|
+
if (symbol) {
|
|
427
|
+
const declarations = symbol.getDeclarations();
|
|
428
|
+
if (declarations && declarations.length > 0) {
|
|
429
|
+
const decl = declarations[0];
|
|
430
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
431
|
+
const result = extractPikkuWorkflowGraphConfig(decl.initializer, checker);
|
|
432
|
+
if (result) {
|
|
433
|
+
// Use the variable name as exportedName
|
|
434
|
+
result.exportedName = ts.isIdentifier(decl.name)
|
|
435
|
+
? decl.name.text
|
|
436
|
+
: undefined;
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
// If it's a call expression to pikkuWorkflowGraph
|
|
445
|
+
if (ts.isCallExpression(node)) {
|
|
446
|
+
const expr = node.expression;
|
|
447
|
+
if (ts.isIdentifier(expr) && expr.text === 'pikkuWorkflowGraph') {
|
|
448
|
+
const configArg = node.arguments[0];
|
|
449
|
+
if (configArg && ts.isObjectLiteralExpression(configArg)) {
|
|
450
|
+
let name;
|
|
451
|
+
let description;
|
|
452
|
+
let tags;
|
|
453
|
+
let wires;
|
|
454
|
+
let nodesNode;
|
|
455
|
+
let configNode;
|
|
456
|
+
for (const prop of configArg.properties) {
|
|
457
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
458
|
+
continue;
|
|
459
|
+
const propName = prop.name.text;
|
|
460
|
+
if (propName === 'name') {
|
|
461
|
+
name = extractStringLiteral(prop.initializer, checker);
|
|
462
|
+
}
|
|
463
|
+
else if (propName === 'description') {
|
|
464
|
+
description = extractStringLiteral(prop.initializer, checker);
|
|
465
|
+
}
|
|
466
|
+
else if (propName === 'tags' &&
|
|
467
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
468
|
+
tags = prop.initializer.elements
|
|
469
|
+
.filter(ts.isStringLiteral)
|
|
470
|
+
.map((el) => el.text);
|
|
471
|
+
}
|
|
472
|
+
else if (propName === 'wires' &&
|
|
473
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
474
|
+
wires = extractWiresConfig(prop.initializer, checker);
|
|
475
|
+
}
|
|
476
|
+
else if (propName === 'nodes' &&
|
|
477
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
478
|
+
nodesNode = prop.initializer;
|
|
479
|
+
}
|
|
480
|
+
else if (propName === 'config' &&
|
|
481
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
482
|
+
configNode = prop.initializer;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return { name, description, tags, wires, nodesNode, configNode };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Extract graph nodes from the new pikkuWorkflowGraph format
|
|
493
|
+
* New format: { nodes: { entry: 'rpcName', ... }, config: { entry: { next: 'sendWelcome', ... }, ... } }
|
|
494
|
+
*/
|
|
495
|
+
function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
496
|
+
const nodes = {};
|
|
497
|
+
if (!nodesNode) {
|
|
498
|
+
return nodes;
|
|
499
|
+
}
|
|
500
|
+
// Extract node ID to RPC name mapping from 'nodes' property
|
|
501
|
+
const nodeRpcMap = {};
|
|
502
|
+
for (const prop of nodesNode.properties) {
|
|
503
|
+
if (!ts.isPropertyAssignment(prop))
|
|
504
|
+
continue;
|
|
505
|
+
const nodeId = ts.isIdentifier(prop.name)
|
|
506
|
+
? prop.name.text
|
|
507
|
+
: ts.isStringLiteral(prop.name)
|
|
508
|
+
? prop.name.text
|
|
509
|
+
: null;
|
|
510
|
+
if (!nodeId)
|
|
511
|
+
continue;
|
|
512
|
+
const rpcName = extractStringLiteral(prop.initializer, checker);
|
|
513
|
+
if (rpcName) {
|
|
514
|
+
nodeRpcMap[nodeId] = rpcName;
|
|
515
|
+
state.rpc.invokedFunctions.add(rpcName);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// Initialize nodes with their RPC names
|
|
519
|
+
for (const [nodeId, rpcName] of Object.entries(nodeRpcMap)) {
|
|
520
|
+
nodes[nodeId] = {
|
|
521
|
+
nodeId,
|
|
522
|
+
rpcName,
|
|
523
|
+
input: {},
|
|
524
|
+
next: undefined,
|
|
525
|
+
onError: undefined,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
// Extract config for each node from 'config' property
|
|
529
|
+
if (configNode) {
|
|
530
|
+
for (const prop of configNode.properties) {
|
|
531
|
+
if (!ts.isPropertyAssignment(prop))
|
|
532
|
+
continue;
|
|
533
|
+
const nodeId = ts.isIdentifier(prop.name)
|
|
534
|
+
? prop.name.text
|
|
535
|
+
: ts.isStringLiteral(prop.name)
|
|
536
|
+
? prop.name.text
|
|
537
|
+
: null;
|
|
538
|
+
if (!nodeId || !nodes[nodeId])
|
|
539
|
+
continue;
|
|
540
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
541
|
+
const nodeConfig = extractNodeConfigFromObject(prop.initializer, checker);
|
|
542
|
+
if (nodeConfig) {
|
|
543
|
+
nodes[nodeId].next = nodeConfig.next;
|
|
544
|
+
nodes[nodeId].onError = nodeConfig.onError;
|
|
545
|
+
nodes[nodeId].input = nodeConfig.input;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return nodes;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Extract node config (next, onError, input) from object literal
|
|
554
|
+
*/
|
|
555
|
+
function extractNodeConfigFromObject(obj, checker) {
|
|
556
|
+
let next = undefined;
|
|
557
|
+
let onError = undefined;
|
|
558
|
+
let input = {};
|
|
559
|
+
for (const prop of obj.properties) {
|
|
560
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
561
|
+
continue;
|
|
562
|
+
const propName = prop.name.text;
|
|
563
|
+
if (propName === 'next') {
|
|
564
|
+
next = extractNextConfig(prop.initializer, checker);
|
|
565
|
+
}
|
|
566
|
+
else if (propName === 'onError') {
|
|
567
|
+
onError = extractNextConfig(prop.initializer, checker);
|
|
568
|
+
}
|
|
569
|
+
else if (propName === 'input') {
|
|
570
|
+
input = extractInputMapping(prop.initializer, checker);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return { next, onError, input };
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Inspector for wireWorkflow() calls with graph definitions
|
|
577
|
+
* Detects: wireWorkflow({ wires: {...}, graph: pikkuWorkflowGraphResult })
|
|
578
|
+
*/
|
|
579
|
+
export const addWorkflowGraph = (logger, node, checker, state) => {
|
|
580
|
+
if (!ts.isCallExpression(node)) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const expression = node.expression;
|
|
584
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireWorkflow') {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const args = node.arguments;
|
|
588
|
+
const firstArg = args[0];
|
|
589
|
+
if (!firstArg) {
|
|
590
|
+
logger.critical(ErrorCode.MISSING_FUNC, 'wireWorkflow requires an argument');
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const definitionObj = extractDefinitionObject(firstArg);
|
|
594
|
+
if (!definitionObj) {
|
|
595
|
+
logger.critical(ErrorCode.MISSING_FUNC, 'wireWorkflow requires an object argument');
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
// Check if this is a graph workflow (has 'graph' property)
|
|
599
|
+
let graphPropNode;
|
|
600
|
+
let enabled = true; // Default to true
|
|
601
|
+
for (const prop of definitionObj.properties) {
|
|
602
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
603
|
+
continue;
|
|
604
|
+
const propName = prop.name.text;
|
|
605
|
+
if (propName === 'graph') {
|
|
606
|
+
graphPropNode = prop.initializer;
|
|
607
|
+
}
|
|
608
|
+
else if (propName === 'enabled') {
|
|
609
|
+
// Check if enabled is explicitly set to false
|
|
610
|
+
if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
|
|
611
|
+
enabled = false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Note: We no longer extract wires from wireWorkflow - they come from pikkuWorkflowGraph
|
|
615
|
+
}
|
|
616
|
+
// If no graph property, this is a DSL workflow - skip (handled by add-workflow.ts)
|
|
617
|
+
if (!graphPropNode) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
// Extract config from the pikkuWorkflowGraph result
|
|
621
|
+
const graphConfig = extractPikkuWorkflowGraphConfig(graphPropNode, checker);
|
|
622
|
+
if (!graphConfig) {
|
|
623
|
+
logger.critical(ErrorCode.MISSING_NAME, 'wireWorkflow with graph requires a pikkuWorkflowGraph');
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
// Use explicit name or fall back to exported variable name
|
|
627
|
+
const workflowName = graphConfig.name || graphConfig.exportedName;
|
|
628
|
+
if (!workflowName) {
|
|
629
|
+
logger.critical(ErrorCode.MISSING_NAME, 'wireWorkflow with graph requires a pikkuWorkflowGraph with a name property or exported variable name');
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
// Extract graph nodes from the new format (nodes + config properties)
|
|
633
|
+
let graphNodes = {};
|
|
634
|
+
if (graphConfig.nodesNode) {
|
|
635
|
+
graphNodes = extractGraphFromNewFormat(graphConfig.nodesNode, graphConfig.configNode, checker, state);
|
|
636
|
+
}
|
|
637
|
+
const entryNodeIds = computeEntryNodeIds(graphNodes);
|
|
638
|
+
// Use wires from pikkuWorkflowGraph (not from wireWorkflow)
|
|
639
|
+
const wires = graphConfig.wires || {};
|
|
640
|
+
const serialized = {
|
|
641
|
+
name: workflowName,
|
|
642
|
+
pikkuFuncName: workflowName,
|
|
643
|
+
source: 'graph',
|
|
644
|
+
description: graphConfig.description,
|
|
645
|
+
tags: graphConfig.tags,
|
|
646
|
+
wires,
|
|
647
|
+
nodes: graphNodes,
|
|
648
|
+
entryNodeIds,
|
|
649
|
+
};
|
|
650
|
+
// Only add if enabled (or not explicitly disabled)
|
|
651
|
+
if (enabled) {
|
|
652
|
+
state.workflows.graphMeta[workflowName] = serialized;
|
|
653
|
+
// Store file path and exported name for import generation
|
|
654
|
+
state.workflows.graphFiles.set(workflowName, {
|
|
655
|
+
path: node.getSourceFile().fileName,
|
|
656
|
+
exportedName: graphConfig.exportedName || workflowName,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
};
|