@kumologica/sdk 3.6.1 → 3.6.2-beta2
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/cli/commands/create-commands/OpenApiBuilder.js +608 -0
- package/cli/commands/create-commands/index.js +3 -1
- package/cli/commands/create-commands/openapi.js +61 -38
- package/cli/commands/create.js +7 -1
- package/cli/commands/run.js +14 -13
- package/package.json +6 -5
- package/src/app/main-process/modal-home.js +1 -0
- package/src/app/main-process/modal-welcome.js +2 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
class OpenApiBuilder {
|
|
2
|
+
constructor(api) {
|
|
3
|
+
this.api = api;
|
|
4
|
+
this.shiftx = 100;
|
|
5
|
+
this.shifty = 80;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
S4() {
|
|
9
|
+
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
getId() {
|
|
13
|
+
return this.S4() + "." + this.S4();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getEventListenerEnd(id, tabId, x, y, status, response, notes, headers) {
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
"id": id,
|
|
20
|
+
"type": "EventListener-End",
|
|
21
|
+
"z": tabId,
|
|
22
|
+
"name": status,
|
|
23
|
+
"statusCode": status === "default" ? "200" : status,
|
|
24
|
+
"responseType": "http",
|
|
25
|
+
"headers": headers,
|
|
26
|
+
"payload": response || "msg.payload",
|
|
27
|
+
"eventPayload": "",
|
|
28
|
+
"eventType": "success",
|
|
29
|
+
"x": x,
|
|
30
|
+
"y": y,
|
|
31
|
+
"wires": [],
|
|
32
|
+
"caname": "eventlistenerend",
|
|
33
|
+
"category": "general",
|
|
34
|
+
"info": notes || ""
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getLog(id, nextId, tabId, x, y, logName, logMessage, logType) {
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
"id": id,
|
|
42
|
+
"type": "Logger",
|
|
43
|
+
"z": tabId,
|
|
44
|
+
"name": logName || "Log",
|
|
45
|
+
"level": logType || "INFO",
|
|
46
|
+
"message": logMessage,
|
|
47
|
+
"x": x,
|
|
48
|
+
"y": y,
|
|
49
|
+
"wires": [[nextId]],
|
|
50
|
+
"caname": "logger",
|
|
51
|
+
"category": "logging"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getSchema(id, nextId, tabId, x, y, schema, notes) {
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"id": id,
|
|
59
|
+
"type": "json-schema-validator",
|
|
60
|
+
"z": tabId,
|
|
61
|
+
"name": "Validate Request",
|
|
62
|
+
"Property": "msg.payload",
|
|
63
|
+
"query": schema,
|
|
64
|
+
"x": x,
|
|
65
|
+
"y": y,
|
|
66
|
+
"wires": [
|
|
67
|
+
[nextId]
|
|
68
|
+
],
|
|
69
|
+
"caname": "JSONSchemaVal",
|
|
70
|
+
"category": "validation",
|
|
71
|
+
"info": notes || ""
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getError(id, nextId, tabId, x, y, errorCode, notes) {
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"id": id,
|
|
79
|
+
"type": "catch",
|
|
80
|
+
"z": tabId,
|
|
81
|
+
"name": "Catch " + (errorCode || "Error"),
|
|
82
|
+
"scope": [],
|
|
83
|
+
"uncaught": false,
|
|
84
|
+
"x": x,
|
|
85
|
+
"y": y,
|
|
86
|
+
"wires": [[nextId]],
|
|
87
|
+
"caname": "catch",
|
|
88
|
+
"category": "exception",
|
|
89
|
+
"info": notes || ""
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getLogicCall(id, nextId, tabId, x, y, logicInId) {
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"id": id,
|
|
98
|
+
"type": "Subflow",
|
|
99
|
+
"z": tabId,
|
|
100
|
+
"untilproptype": "num",
|
|
101
|
+
"proptype": "msg",
|
|
102
|
+
"name": "Business Logic Call",
|
|
103
|
+
"prop": "loop",
|
|
104
|
+
"untilprop": 0,
|
|
105
|
+
"until": "gt",
|
|
106
|
+
"loop": "none",
|
|
107
|
+
"scope": "global",
|
|
108
|
+
"perf": false,
|
|
109
|
+
"seq": false,
|
|
110
|
+
"selectedTargetNode": logicInId,
|
|
111
|
+
"targetNode": logicInId,
|
|
112
|
+
"initialLoad": "false",
|
|
113
|
+
"x": x,
|
|
114
|
+
"y": y,
|
|
115
|
+
"wires": [[nextId]],
|
|
116
|
+
"caname": "subflow",
|
|
117
|
+
"category": "routing"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getLogicSubflow(id, tabId, y) {
|
|
122
|
+
const subflowOutId = this.getId();
|
|
123
|
+
const logId = this.getId();
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
"id": id,
|
|
127
|
+
"type": "Subflow_in",
|
|
128
|
+
"z": tabId,
|
|
129
|
+
"name": "Business Logic In",
|
|
130
|
+
"priority": "50",
|
|
131
|
+
"links": [],
|
|
132
|
+
"scope": "global",
|
|
133
|
+
"x": 50,
|
|
134
|
+
"y": y,
|
|
135
|
+
"wires": [[ logId]],
|
|
136
|
+
"caname": "subflow",
|
|
137
|
+
"category": "routing"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": subflowOutId,
|
|
141
|
+
"type": "Subflow_out",
|
|
142
|
+
"z": tabId,
|
|
143
|
+
"name": "Business Logic Out",
|
|
144
|
+
"links":[],
|
|
145
|
+
"x": 50 + 2 * this.shiftx,
|
|
146
|
+
"y": y,
|
|
147
|
+
"wires":[],
|
|
148
|
+
"caname": "subflow",
|
|
149
|
+
"category": "routing"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"id": logId,
|
|
153
|
+
"type":"Logger",
|
|
154
|
+
"z": tabId,
|
|
155
|
+
"name": "Replace with logic",
|
|
156
|
+
"level": "INFO",
|
|
157
|
+
"message": "replace this node with business logic implementation",
|
|
158
|
+
"format": "string",
|
|
159
|
+
"headers": {},
|
|
160
|
+
"x": 50 + this.shiftx,
|
|
161
|
+
"y": y,
|
|
162
|
+
"wires":[[ subflowOutId ]],
|
|
163
|
+
"caname":"logger",
|
|
164
|
+
"category":"logging"
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getTab(tabName) {
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
|
|
173
|
+
"id": this.getId(),
|
|
174
|
+
|
|
175
|
+
"type": "tab",
|
|
176
|
+
|
|
177
|
+
"label": tabName,
|
|
178
|
+
|
|
179
|
+
"disabled": false,
|
|
180
|
+
|
|
181
|
+
"info": ""
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getEventListener(id, nextId, tabId, x, y, provider, verb, resource, notes) {
|
|
188
|
+
return {
|
|
189
|
+
"id": id,
|
|
190
|
+
"type": "EventListener",
|
|
191
|
+
"z": tabId,
|
|
192
|
+
"name": verb.toUpperCase() + " " + resource,
|
|
193
|
+
"provider": provider,
|
|
194
|
+
"eventSource": "api",
|
|
195
|
+
"dynamodbOperation": "",
|
|
196
|
+
"apiUrl": provider === "aws" ? resource : "",
|
|
197
|
+
"apiMethod": provider === "aws" ? verb : "",
|
|
198
|
+
"albMethod": "any",
|
|
199
|
+
"albUrl": "",
|
|
200
|
+
"bucketName": "",
|
|
201
|
+
"event": "",
|
|
202
|
+
"kapiUrl": "",
|
|
203
|
+
"kcronexpression": "",
|
|
204
|
+
"zapiUrl": provider === "azure" ? resource : "",
|
|
205
|
+
"zapiMethod": provider === "azure" ? verb : "",
|
|
206
|
+
"gapiUrl": provider === "gcp" ? resource : "",
|
|
207
|
+
"gapiMethod": provider === "gcp" ? verb : "",
|
|
208
|
+
"napiUrl": provider === "nodejs" ? resource : "",
|
|
209
|
+
"napiMethod": provider === "nodejs" ? verb : "",
|
|
210
|
+
"x": x,
|
|
211
|
+
"y": y,
|
|
212
|
+
"wires": [
|
|
213
|
+
[nextId]
|
|
214
|
+
],
|
|
215
|
+
"caname": "event-handler",
|
|
216
|
+
"category": "general",
|
|
217
|
+
"info": notes || ""
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getTest(tabId, x, y, verb, resource, statusCode) {
|
|
222
|
+
|
|
223
|
+
const idEnd = this.getId();
|
|
224
|
+
const idStart = this.getId();
|
|
225
|
+
const idAssert = this.getId();
|
|
226
|
+
|
|
227
|
+
return [{
|
|
228
|
+
|
|
229
|
+
"id": idAssert,
|
|
230
|
+
"type": "Assertion",
|
|
231
|
+
"z": tabId,
|
|
232
|
+
"name": "check status",
|
|
233
|
+
"selector": "statusCode",
|
|
234
|
+
"property": "hello",
|
|
235
|
+
"comparison": "equals",
|
|
236
|
+
"value": statusCode,
|
|
237
|
+
"valueType": "str",
|
|
238
|
+
"x": x + this.shiftx * 2,
|
|
239
|
+
"y": y,
|
|
240
|
+
"wires": [[idEnd]],
|
|
241
|
+
"caname": "test-assertion",
|
|
242
|
+
"category": "testing"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": idEnd,
|
|
246
|
+
"type": "TestCaseEnd",
|
|
247
|
+
"z": tabId,
|
|
248
|
+
"name": "TestCaseEnd",
|
|
249
|
+
"x": x + this.shiftx * 3,
|
|
250
|
+
"y": y,
|
|
251
|
+
"wires": [],
|
|
252
|
+
"caname": "test-case-end",
|
|
253
|
+
"category": "testing"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"id": idStart,
|
|
257
|
+
"type": "HTTPTestCase",
|
|
258
|
+
"z": tabId,
|
|
259
|
+
"name": "HTTP Test " + verb + " " + resource,
|
|
260
|
+
"method": verb.toUpperCase(),
|
|
261
|
+
"path": resource,
|
|
262
|
+
"headers": {
|
|
263
|
+
"Accept": "application/json"
|
|
264
|
+
},
|
|
265
|
+
"authtype": "none",
|
|
266
|
+
"secUser": "",
|
|
267
|
+
"secPassword": "",
|
|
268
|
+
"secToken": "",
|
|
269
|
+
"payload": "",
|
|
270
|
+
"x": x + this.shiftx,
|
|
271
|
+
"y": y,
|
|
272
|
+
"wires": [[idAssert]],
|
|
273
|
+
"caname": "http-test-case",
|
|
274
|
+
"category": "testing"
|
|
275
|
+
}];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
getXML(id, nextId, tabId, x, y) {
|
|
279
|
+
return {
|
|
280
|
+
"id": id,
|
|
281
|
+
"type":"XML",
|
|
282
|
+
"z": tabId,
|
|
283
|
+
"name":"XML",
|
|
284
|
+
"property":"msg.payload",
|
|
285
|
+
"attr":"",
|
|
286
|
+
"chr":"",
|
|
287
|
+
"x":x,
|
|
288
|
+
"y":y,
|
|
289
|
+
"wires":[ [nextId] ],
|
|
290
|
+
"caname":"XML",
|
|
291
|
+
"category":"transformation"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
getForm(id, nextId, tabId, x, y) {
|
|
296
|
+
return {
|
|
297
|
+
"id":id,
|
|
298
|
+
"type":"Form Data",
|
|
299
|
+
"z":tabId,
|
|
300
|
+
"name":"Form Data",
|
|
301
|
+
"attr":"",
|
|
302
|
+
"chr":"",
|
|
303
|
+
"x":x,
|
|
304
|
+
"y":y,
|
|
305
|
+
"wires":[[nextId]],
|
|
306
|
+
"caname":"Form2Json",
|
|
307
|
+
"category":"transformation"}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getSetProperty(id, nextId, tabId, x, y) {
|
|
311
|
+
return {
|
|
312
|
+
"id":id,
|
|
313
|
+
"type":"Set-Property",
|
|
314
|
+
"z":tabId,
|
|
315
|
+
"name":"Set Content Type",
|
|
316
|
+
"rules":[
|
|
317
|
+
{
|
|
318
|
+
"t":"set",
|
|
319
|
+
"p":"contentType",
|
|
320
|
+
"pt":"camsg",
|
|
321
|
+
"to":"$exists(msg.header.event.Records[0].headers.'Content-Type') ? msg.header.event.Records[0].headers.'Content-Type' : msg.header.event.Records[0].headers.'content-type'",
|
|
322
|
+
"tot":"jsonata"
|
|
323
|
+
}
|
|
324
|
+
],
|
|
325
|
+
"action":"",
|
|
326
|
+
"property":"",
|
|
327
|
+
"from":"",
|
|
328
|
+
"to":"",
|
|
329
|
+
"reg":false,
|
|
330
|
+
"x":x,
|
|
331
|
+
"y":y,
|
|
332
|
+
"wires":[[ nextId ]],
|
|
333
|
+
"caname":"setproperty",
|
|
334
|
+
"category":"transformation"
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
getSwitch(id, tabId, x, y, label) {
|
|
339
|
+
|
|
340
|
+
let s = {
|
|
341
|
+
"id":id,
|
|
342
|
+
"type":"Switch",
|
|
343
|
+
"z":tabId,
|
|
344
|
+
"name": label,
|
|
345
|
+
"property":"msg.contentType",
|
|
346
|
+
"propertyType":"",
|
|
347
|
+
"rules":[],
|
|
348
|
+
"repair":false,
|
|
349
|
+
"outputs":0,
|
|
350
|
+
"x":x,
|
|
351
|
+
"y":y,
|
|
352
|
+
"wires":[],
|
|
353
|
+
"caname":"switch",
|
|
354
|
+
"category":"routing"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return s;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
handleApi(projectName, provider = "aws") {
|
|
361
|
+
|
|
362
|
+
const flowFileName = projectName + "-flow.json";
|
|
363
|
+
|
|
364
|
+
const { codegen } = require('@kumologica/builder');
|
|
365
|
+
const packageInfo = require('../../../package.json');
|
|
366
|
+
|
|
367
|
+
let pc = codegen.genPackageJson(
|
|
368
|
+
projectName,
|
|
369
|
+
"lambda.js",
|
|
370
|
+
flowFileName,
|
|
371
|
+
packageInfo.version,
|
|
372
|
+
this.api?.info?.title,
|
|
373
|
+
this.api?.info?.version,
|
|
374
|
+
this.api?.info?.description,
|
|
375
|
+
this.api?.info?.contact ? this.api?.info?.contact?.email: "",
|
|
376
|
+
this.api?.info?.license ? this.api?.info?.license?.name: "MIT");
|
|
377
|
+
|
|
378
|
+
const paths = this.api.paths;
|
|
379
|
+
|
|
380
|
+
let flow = [];
|
|
381
|
+
|
|
382
|
+
Object.keys(paths).forEach(path => {
|
|
383
|
+
|
|
384
|
+
const svc = paths[path];
|
|
385
|
+
flow = flow.concat(this.handleResource(path, svc, provider));
|
|
386
|
+
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
return { pck: pc, flow: flow };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
generateTabName(path, v) {
|
|
393
|
+
|
|
394
|
+
return v.toUpperCase() + ":" + path;
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
updateIdsAndPosition(id, nextId, x) {
|
|
399
|
+
id = nextId;
|
|
400
|
+
nextId = this.getId();
|
|
401
|
+
x += this.shiftx;
|
|
402
|
+
return { id, nextId, x };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
handleVerb(path, v, verb, provider) {
|
|
406
|
+
|
|
407
|
+
const resource = path.replace(/{/g, ":").replace(/}/g, "");
|
|
408
|
+
const tabName = this.generateTabName(resource, v);
|
|
409
|
+
|
|
410
|
+
console.log(` ${v}: ${path}`);
|
|
411
|
+
|
|
412
|
+
let x = 50;
|
|
413
|
+
let y = 50;
|
|
414
|
+
let listenerEndY = 50; // to align listeners end
|
|
415
|
+
let listenerEndX = 50; // to align listeners end
|
|
416
|
+
let flow = [];
|
|
417
|
+
|
|
418
|
+
// create tab for verb - resource
|
|
419
|
+
const tab = this.getTab(tabName);
|
|
420
|
+
flow.push(tab);
|
|
421
|
+
|
|
422
|
+
let id = this.getId();
|
|
423
|
+
let nextId = this.getId();
|
|
424
|
+
let logicId = this.getId();
|
|
425
|
+
|
|
426
|
+
flow.push(this.getEventListener(id, nextId, tab.id, x, y, provider, v, resource, verb.summary + "\n" + verb.description));
|
|
427
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
428
|
+
|
|
429
|
+
flow.push(this.getLog(id, nextId, tab.id, x, y, "Log Request", "received: " + v + " " + resource, "INFO"));
|
|
430
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
// Handle requestBody content
|
|
434
|
+
if (verb.requestBody && verb.requestBody.content) {
|
|
435
|
+
|
|
436
|
+
const requestBodyContentTypes = Object.keys(verb.requestBody.content);
|
|
437
|
+
|
|
438
|
+
let switchNode;
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
if (requestBodyContentTypes.length > 1) {
|
|
442
|
+
// set content type
|
|
443
|
+
flow.push(this.getSetProperty(id, nextId, tab.id, x, y));
|
|
444
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
445
|
+
|
|
446
|
+
// switch
|
|
447
|
+
switchNode = this.getSwitch(id, tab.id, x, y, "Request Content Type"); // add switch node with no wires (no nextId)
|
|
448
|
+
flow.push(switchNode);
|
|
449
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const requestX = x;
|
|
453
|
+
requestBodyContentTypes.forEach((contentType, i) => {
|
|
454
|
+
const content = verb.requestBody.content[contentType];
|
|
455
|
+
|
|
456
|
+
let schemaId = content.schema? id: logicId;
|
|
457
|
+
|
|
458
|
+
let currId = null;
|
|
459
|
+
|
|
460
|
+
// for application/json there is no need for conversion, just pass through to validation
|
|
461
|
+
if (contentType === 'application/xml') {
|
|
462
|
+
currId = this.getId();
|
|
463
|
+
flow.push(this.getXML(currId, schemaId, tab.id, requestX, y + (i * this.shifty)));
|
|
464
|
+
|
|
465
|
+
} else if (contentType === 'application/x-www-form-urlencoded') {
|
|
466
|
+
currId = this.getId();
|
|
467
|
+
flow.push(this.getForm(currId, schemaId, tab.id, requestX, y + (i * this.shifty)));
|
|
468
|
+
|
|
469
|
+
} else {
|
|
470
|
+
// link switch to schema or logic node
|
|
471
|
+
// currId = logicId;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// attach schema to current content type and point to the logic node,
|
|
475
|
+
if (content.schema) {
|
|
476
|
+
const schema = this.getSchema(schemaId, logicId, tab.id, requestX + this.shiftx, y + (i * this.shifty), JSON.stringify(content.schema, null, 2));
|
|
477
|
+
x = requestX + this.shiftx;
|
|
478
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
479
|
+
|
|
480
|
+
flow.push(schema);
|
|
481
|
+
// logicNode.x = schema.x + this.shiftx; // adjust logic node x position
|
|
482
|
+
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (switchNode) {
|
|
486
|
+
if (!currId) {
|
|
487
|
+
currId = schemaId;
|
|
488
|
+
}
|
|
489
|
+
switchNode.rules.push({t: "eq", v: contentType, vt: "str"});
|
|
490
|
+
switchNode.outputs++;
|
|
491
|
+
switchNode.wires.push([ currId ]);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
logicId = id;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// set logic node, x will be updated
|
|
500
|
+
const logicInId = this.getId(); // this is for business logic subflow in
|
|
501
|
+
let logicNode = this.getLogicCall(logicId, nextId, tab.id, x, y, logicInId);
|
|
502
|
+
|
|
503
|
+
//let logicNode = this.getLog(logicId, nextId, tab.id, x , y, "Log Logic", "logic for: " + v + " " + resource, "INFO");
|
|
504
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
505
|
+
|
|
506
|
+
flow.push(logicNode);
|
|
507
|
+
x = logicNode.x + this.shiftx;
|
|
508
|
+
listenerEndX = x;
|
|
509
|
+
|
|
510
|
+
//sort by code
|
|
511
|
+
const sortedResponses = Object.keys(verb.responses).sort((a, b) => a.localeCompare(b));
|
|
512
|
+
|
|
513
|
+
let currId = null;
|
|
514
|
+
// 200, 201, 400, 500, ...
|
|
515
|
+
sortedResponses.forEach((r, i) => {
|
|
516
|
+
const res = verb.responses[r];
|
|
517
|
+
// Transform headers into key-value pair with empty string values
|
|
518
|
+
const transformedHeaders = Object.keys(res.headers || {}).reduce((acc, headerKey) => {
|
|
519
|
+
acc[headerKey] = "";
|
|
520
|
+
return acc;
|
|
521
|
+
}, {});
|
|
522
|
+
|
|
523
|
+
//console.log(" processing " + r);
|
|
524
|
+
|
|
525
|
+
// for each content type
|
|
526
|
+
let responseSwitchNode = null;
|
|
527
|
+
|
|
528
|
+
//console.log((JSON.stringify(verb.responses[r])));
|
|
529
|
+
if ( res.content) {
|
|
530
|
+
|
|
531
|
+
if (Object.keys(res.content).length > 1) {
|
|
532
|
+
|
|
533
|
+
responseSwitchNode = this.getSwitch(id, tab.id, x, y, "Response Content Type"); // add switch node with no wires (no nextId)
|
|
534
|
+
flow.push(responseSwitchNode);
|
|
535
|
+
listenerEndX += this.shiftx;
|
|
536
|
+
|
|
537
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
538
|
+
logicNode.wires.push([ responseSwitchNode.id ]);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
Object.keys(res.content).sort((a, b) => b.localeCompare(a)).forEach((c) => {
|
|
542
|
+
|
|
543
|
+
currId = null;
|
|
544
|
+
if (c === 'application/xml') {
|
|
545
|
+
currId = id;
|
|
546
|
+
flow.push(this.getXML(currId, nextId, tab.id, x, y + (i * this.shifty)));
|
|
547
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
548
|
+
listenerEndX += this.shiftx;
|
|
549
|
+
}
|
|
550
|
+
const endId = id;
|
|
551
|
+
flow.push(this.getLog(id, nextId, tab.id, listenerEndX, listenerEndY, "Log Response " + r, `msg.payload`, "INFO"));
|
|
552
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
553
|
+
|
|
554
|
+
flow.push(this.getEventListenerEnd(id, tab.id, listenerEndX+this.shiftx, listenerEndY, r, null, res.description, { ...transformedHeaders, "Content-Type": c }));
|
|
555
|
+
listenerEndY += this.shifty;
|
|
556
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
557
|
+
|
|
558
|
+
if (responseSwitchNode) {
|
|
559
|
+
responseSwitchNode.rules.push({t: "eq", v: c, vt: "str"});
|
|
560
|
+
responseSwitchNode.outputs++;
|
|
561
|
+
responseSwitchNode.wires.push([ currId? currId: endId ]);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
});
|
|
565
|
+
} else {
|
|
566
|
+
|
|
567
|
+
// just end or exception
|
|
568
|
+
if (i == 0) {
|
|
569
|
+
flow.push(this.getLog(id, nextId, tab.id, listenerEndX, listenerEndY, "Log Response " + r, "msg.payload", "INFO"));
|
|
570
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
571
|
+
|
|
572
|
+
} else if ((r.startsWith("4") || r.startsWith("5")) ) {
|
|
573
|
+
flow.push(this.getError(this.getId(), nextId, tab.id, listenerEndX-this.shiftx, listenerEndY, r, res.description));;
|
|
574
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
575
|
+
|
|
576
|
+
flow.push(this.getLog(id, nextId, tab.id, listenerEndX, listenerEndY, "Log Error " + r, `msg.error = null? "Error: ${r}": msg.error`, "ERROR"));
|
|
577
|
+
({ id, nextId, x } = this.updateIdsAndPosition(id, nextId, x));
|
|
578
|
+
|
|
579
|
+
}
|
|
580
|
+
flow.push(this.getEventListenerEnd(id, tab.id, listenerEndX+this.shiftx, listenerEndY, r, null, res.description, { ...transformedHeaders, "Content-Type": "application/json" }));
|
|
581
|
+
|
|
582
|
+
//flow = [...flow, ...this.getTest(tab.id, listenerEndX + this.shiftx, listenerEndY, v, resource, r)];
|
|
583
|
+
|
|
584
|
+
listenerEndY += this.shifty;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
flow = [...flow, ...this.getLogicSubflow(logicInId, tab.id, listenerEndY)];
|
|
589
|
+
|
|
590
|
+
return flow;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
handleResource(path, svc, provider) {
|
|
594
|
+
|
|
595
|
+
let flow = [];
|
|
596
|
+
|
|
597
|
+
Object.keys(svc).forEach(v => {
|
|
598
|
+
const verb = svc[v];
|
|
599
|
+
flow = flow.concat(this.handleVerb(path, v, verb, provider));
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
return flow;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
module.exports = {
|
|
607
|
+
OpenApiBuilder
|
|
608
|
+
}
|
|
@@ -1,42 +1,65 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type: 'input',
|
|
19
|
-
name: 'directory',
|
|
20
|
-
message: 'Enter the path where the project will be created '
|
|
21
|
-
});
|
|
22
|
-
let aProjectPath = directoryResponse.directory;
|
|
23
|
-
|
|
24
|
-
// Template name
|
|
25
|
-
const promptTemplate = new Select({
|
|
26
|
-
name: 'template',
|
|
27
|
-
message: 'Pick one of the available templates',
|
|
28
|
-
choices: availableTemplates
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
let aTemplateName = await promptTemplate.run();
|
|
32
|
-
|
|
33
|
-
try{
|
|
34
|
-
await downloadTemplateFromRepo(undefined, aTemplateName, aProjectPath, aProjectName);
|
|
35
|
-
logNotice(`Successfully created project: ${pathlib.join(aProjectPath, aProjectName)}`)
|
|
36
|
-
} catch (err){
|
|
37
|
-
logError(`Error found while creating project due to: ${err.message}`)
|
|
1
|
+
const { logNotice, logError, logInfo } = require('../../utils/logger');
|
|
2
|
+
const SwaggerParser = require('@apidevtools/swagger-parser');
|
|
3
|
+
const { OpenApiBuilder } = require('./OpenApiBuilder');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const { URL } = require('url');
|
|
7
|
+
|
|
8
|
+
function getFileName(fullPath) {
|
|
9
|
+
let fileName;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Check if the path is a URL
|
|
13
|
+
const url = new URL(fullPath);
|
|
14
|
+
fileName = path.basename(url.pathname);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
// If it's not a valid URL, treat it as a filesystem path
|
|
17
|
+
fileName = path.basename(fullPath);
|
|
38
18
|
}
|
|
19
|
+
|
|
20
|
+
// Remove the file extension
|
|
21
|
+
return fileName.replace(path.extname(fileName), '');
|
|
39
22
|
}
|
|
23
|
+
|
|
24
|
+
function createProject(p, projectName, pck, flow) {
|
|
25
|
+
|
|
26
|
+
// create project directory
|
|
27
|
+
const rootDir = path.join(p || process.cwd(), projectName);
|
|
28
|
+
|
|
29
|
+
// logInfo(rootDir);
|
|
30
|
+
fs.ensureDir(rootDir);
|
|
40
31
|
|
|
32
|
+
fs.outputFileSync(path.join(rootDir, projectName + "-flow.json"), JSON.stringify(flow, null, 2), 'utf-8');
|
|
33
|
+
fs.outputFileSync(path.join(rootDir, "package.json"), pck, 'utf-8');
|
|
34
|
+
|
|
35
|
+
return rootDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function createOpenApiProject(p, apiSpec) {
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
try {
|
|
41
|
+
const api = await SwaggerParser.validate(apiSpec);
|
|
42
|
+
|
|
43
|
+
// Print some basic API info.
|
|
44
|
+
logInfo(`Successfully parsed API: ${api?.info?.title}`);
|
|
45
|
+
logInfo(`Version: ${api?.info?.version}`);
|
|
46
|
+
const projectName = getFileName(apiSpec);
|
|
47
|
+
|
|
48
|
+
const builder = new OpenApiBuilder(api);
|
|
49
|
+
const response = builder.handleApi(projectName);
|
|
50
|
+
|
|
51
|
+
const rootDir = createProject(p, projectName, response.pck, response.flow);
|
|
52
|
+
|
|
53
|
+
logInfo(" ");
|
|
54
|
+
logInfo("Project created successfully.");
|
|
55
|
+
logInfo("To open project in kumologica designer run the following command:");
|
|
56
|
+
logInfo("kl open " + rootDir);
|
|
57
|
+
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logError("Error parsing OpenAPI specification:", error.message);
|
|
60
|
+
logError(error);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = createOpenApiProject;
|
package/cli/commands/create.js
CHANGED
|
@@ -3,7 +3,8 @@ const { codegen } = require('@kumologica/builder');
|
|
|
3
3
|
|
|
4
4
|
const { logNotice, logError } = require('../utils/logger');
|
|
5
5
|
const { downloadTemplateFromRepo, isPlainGitURL } = require('../utils/download-template-from-repo');
|
|
6
|
-
const { createProjectIteratively
|
|
6
|
+
const { createProjectIteratively,
|
|
7
|
+
createOpenApiProject } = require('./create-commands');
|
|
7
8
|
|
|
8
9
|
exports.command = 'create [options]'
|
|
9
10
|
exports.builder = (yargs) => {
|
|
@@ -33,6 +34,11 @@ exports.desc = 'Create a kumologica project. Do not pass any options to create i
|
|
|
33
34
|
exports.handler = async ({ path, template, api }) => {
|
|
34
35
|
const { version } = require('../../package.json');
|
|
35
36
|
|
|
37
|
+
if (api) {
|
|
38
|
+
createOpenApiProject(path, api);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
if (!path && !template && !api) {
|
|
37
43
|
await createProjectIteratively();
|
|
38
44
|
|
package/cli/commands/run.js
CHANGED
|
@@ -100,23 +100,24 @@ exports.handler = async ({ project_directory, loglevel, port, taskName, args })
|
|
|
100
100
|
},
|
|
101
101
|
body: JSON.stringify({args: args})
|
|
102
102
|
};
|
|
103
|
-
const url = `http://127.0.0.1:${port||1880}/__task__/${tName}`;
|
|
104
|
-
|
|
105
|
-
const response = await fetch(url, req);
|
|
106
|
-
const data = await response.text();
|
|
107
|
-
|
|
108
|
-
logInfo("Status: " + response.status);
|
|
109
|
-
logInfo("Message: " + data);
|
|
110
|
-
|
|
111
|
-
await server.stop();
|
|
112
|
-
|
|
113
|
-
exitCode = response.status >= 200 && response.status < 300 ? 0 : 1;
|
|
114
103
|
|
|
104
|
+
if (tName) {
|
|
105
|
+
const url = `http://127.0.0.1:${port||1880}/__task__/${tName}`;
|
|
106
|
+
|
|
107
|
+
const response = await fetch(url, req);
|
|
108
|
+
const data = await response.text();
|
|
109
|
+
|
|
110
|
+
logInfo("Status: " + response.status);
|
|
111
|
+
logInfo("Message: " + data);
|
|
112
|
+
|
|
113
|
+
await server.stop();
|
|
114
|
+
|
|
115
|
+
exitCode = response.status >= 200 && response.status < 300 ? 0 : 1;
|
|
116
|
+
process.exit(exitCode); // this is task, terminate after completion
|
|
117
|
+
}
|
|
115
118
|
} catch (e) {
|
|
116
119
|
logFatal(e.message);
|
|
117
120
|
}
|
|
118
|
-
|
|
119
|
-
process.exit(exitCode);
|
|
120
121
|
};
|
|
121
122
|
|
|
122
123
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kumologica/sdk",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.2-beta2",
|
|
4
4
|
"productName": "Kumologica Designer",
|
|
5
5
|
"copyright": "Copyright 2020 Kumologica Pty Ltd, All Rights Reserved.",
|
|
6
6
|
"author": "Kumologica Pty Ltd <contact@kumologica.com>",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"license": "Proprietary",
|
|
65
65
|
"dependencies": {
|
|
66
|
+
"@apidevtools/swagger-parser": "^10.1.1",
|
|
66
67
|
"@aws-sdk/client-api-gateway": "^3.556.0",
|
|
67
68
|
"@aws-sdk/client-cloudformation": "^3.556.0",
|
|
68
69
|
"@aws-sdk/client-cloudwatch-events": "^3.556.0",
|
|
@@ -82,9 +83,9 @@
|
|
|
82
83
|
"@aws-sdk/credential-providers": "^3.556.0",
|
|
83
84
|
"@aws-sdk/lib-dynamodb": "^3.549.0",
|
|
84
85
|
"@electron/remote": "^2.0.8",
|
|
85
|
-
"@kumologica/builder": "3.6.
|
|
86
|
-
"@kumologica/devkit": "3.6.
|
|
87
|
-
"@kumologica/runtime": "3.6.
|
|
86
|
+
"@kumologica/builder": "3.6.2-beta2",
|
|
87
|
+
"@kumologica/devkit": "3.6.2-beta2",
|
|
88
|
+
"@kumologica/runtime": "3.6.2-beta2",
|
|
88
89
|
"adm-zip": "0.4.13",
|
|
89
90
|
"ajv": "8.10.0",
|
|
90
91
|
"archive-type": "^4.0.0",
|
|
@@ -148,7 +149,7 @@
|
|
|
148
149
|
"when": "3.7.8",
|
|
149
150
|
"wide-align": "^1.1.5",
|
|
150
151
|
"wildcard-match": "^5.1.2",
|
|
151
|
-
"ws": "
|
|
152
|
+
"ws": "8.18.1",
|
|
152
153
|
"xterm": "4.1.0",
|
|
153
154
|
"xterm-addon-fit": "0.2.1",
|
|
154
155
|
"yargs": "17.3.1"
|
|
@@ -39,6 +39,7 @@ class ModalHome {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
this.window = new BrowserWindow(windowOptions);
|
|
42
|
+
require('@electron/remote/main').enable(this.window.webContents);
|
|
42
43
|
|
|
43
44
|
this.window.on('focus', () => this.window.webContents.send('focus'));
|
|
44
45
|
this.window.on('blur', () => this.window.webContents.send('blur'));
|
|
@@ -38,6 +38,8 @@ class ModalWelcome {
|
|
|
38
38
|
|
|
39
39
|
this.window = new BrowserWindow(windowOptions);
|
|
40
40
|
|
|
41
|
+
require('@electron/remote/main').enable(this.window.webContents);
|
|
42
|
+
|
|
41
43
|
this.window.on('focus', () => this.window.webContents.send('focus'));
|
|
42
44
|
this.window.on('blur', () => this.window.webContents.send('blur'));
|
|
43
45
|
|