@kumologica/sdk 3.6.0 → 3.6.2-beta1

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.
@@ -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,5 +1,7 @@
1
1
  const createProjectIteratively = require('./create-project-iteratively');
2
+ const createOpenApiProject = require('./openapi');
2
3
 
3
4
  module.exports = {
4
- createProjectIteratively
5
+ createProjectIteratively,
6
+ createOpenApiProject
5
7
  }
@@ -1,42 +1,65 @@
1
- const pathlib = require('path');
2
- const { prompt, Select } = require('enquirer');
3
- const { logNotice, logError } = require('../../utils/logger');
4
- const { downloadTemplateFromRepo, listAvailableTemplates } = require('../../utils/download-template-from-repo');
5
-
6
- async function createProjectIteratively(){
7
- const availableTemplates = await listAvailableTemplates();
8
- // Project name
9
- const projectNameResponse = await prompt({
10
- type: 'input',
11
- name: 'projectName',
12
- message: 'Enter the name for the project'
13
- });
14
- let aProjectName = projectNameResponse.projectName;
15
-
16
- // Target Path
17
- const directoryResponse = await prompt({
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
- module.exports = createProjectIteratively;
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;
@@ -0,0 +1,425 @@
1
+ const {
2
+ CognitoIdentityProviderClient,
3
+ SignUpCommand,
4
+ ConfirmSignUpCommand,
5
+ InitiateAuthCommand,
6
+ RefreshTokenCommand,
7
+ } = require('@aws-sdk/client-cognito-identity-provider');
8
+ const fs = require('fs').promises;
9
+ const path = require('path');
10
+ const axios = require('axios');
11
+ const { prompt } = require('enquirer'); // Import enquirer for prompting
12
+
13
+ // Simulated DNS-based config fetch
14
+ async function fetchCognitoConfig(dnsName = 'config.myapp.com') {
15
+ try {
16
+ const response = await axios.get(`https://${dnsName}/cognito-config`);
17
+ return response.data;
18
+ } catch (err) {
19
+ console.error('Config fetch error:', err.message);
20
+ return {
21
+ region: 'YOUR_AWS_REGION', // e.g., 'us-east-1'
22
+ userPoolId: 'YOUR_USER_POOL_ID', // e.g., 'us-east-1_abc123'
23
+ clientId: 'YOUR_CLIENT_ID' // e.g., '1a2b3c4d5e6f7g8h9i0j'
24
+ };
25
+ }
26
+ }
27
+
28
+ // Directory and file for storing tokens
29
+ const tokensDir = path.join(__dirname, 'tokens');
30
+ const tokensFile = path.join(tokensDir, 'tokens.json');
31
+
32
+ // --- Initialize Cognito Client with Dynamic Config ---
33
+ async function getCognitoClient() {
34
+ const config = await fetchCognitoConfig();
35
+ return {
36
+ client: new CognitoIdentityProviderClient({ region: config.region }),
37
+ userPoolId: config.userPoolId,
38
+ clientId: config.clientId,
39
+ };
40
+ }
41
+
42
+ // --- Signup Function with Email Verification ---
43
+ async function signUp(email, password, client, clientId) {
44
+ const params = {
45
+ ClientId: clientId,
46
+ Username: email,
47
+ Password: password,
48
+ UserAttributes: [{ Name: 'email', Value: email }],
49
+ };
50
+
51
+ const command = new SignUpCommand(params);
52
+ const result = await client.send(command);
53
+ console.log('Signup successful! Please check your email for a verification code.', result.UserSub);
54
+ return result;
55
+ }
56
+
57
+ // --- Email Verification Function ---
58
+ async function verifyEmail(email, verificationCode, client, clientId) {
59
+ const params = {
60
+ ClientId: clientId,
61
+ Username: email,
62
+ ConfirmationCode: verificationCode,
63
+ };
64
+
65
+ const command = new ConfirmSignUpCommand(params);
66
+ const result = await client.send(command);
67
+ console.log('Email verified successfully!');
68
+ return result;
69
+ }
70
+
71
+ // --- Login Function ---
72
+ async function login(email, password, client, clientId) {
73
+ const params = {
74
+ AuthFlow: 'USER_PASSWORD_AUTH',
75
+ ClientId: clientId,
76
+ AuthParameters: { USERNAME: email, PASSWORD: password },
77
+ };
78
+
79
+ const command = new InitiateAuthCommand(params);
80
+ const result = await client.send(command);
81
+ const tokens = {
82
+ accessToken: result.AuthenticationResult.AccessToken,
83
+ idToken: result.AuthenticationResult.IdToken,
84
+ refreshToken: result.AuthenticationResult.RefreshToken,
85
+ };
86
+ console.log('Login successful!', tokens);
87
+
88
+ await fs.mkdir(tokensDir, { recursive: true });
89
+ await fs.writeFile(tokensFile, JSON.stringify(tokens, null, 2));
90
+ console.log('Tokens stored in', tokensFile);
91
+
92
+ return tokens;
93
+ }
94
+
95
+ // --- Token Validation Function ---
96
+ function validateToken(token) {
97
+ try {
98
+ const decoded = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
99
+ const currentTime = Math.floor(Date.now() / 1000);
100
+ if (decoded.exp < currentTime) {
101
+ console.log('Token expired');
102
+ return false;
103
+ }
104
+ console.log('Token is valid');
105
+ return true;
106
+ } catch (err) {
107
+ console.error('Token validation error:', err.message);
108
+ return false;
109
+ }
110
+ }
111
+
112
+ // --- Refresh Token Function ---
113
+ async function refreshToken(refreshToken, client, clientId) {
114
+ const params = {
115
+ AuthFlow: 'REFRESH_TOKEN_AUTH',
116
+ ClientId: clientId,
117
+ AuthParameters: { REFRESH_TOKEN: refreshToken },
118
+ };
119
+
120
+ const command = new RefreshTokenCommand(params);
121
+ const result = await client.send(command);
122
+ const newTokens = {
123
+ accessToken: result.AuthenticationResult.AccessToken,
124
+ idToken: result.AuthenticationResult.IdToken,
125
+ refreshToken: refreshToken,
126
+ };
127
+ console.log('Token refreshed successfully!', newTokens);
128
+
129
+ await fs.writeFile(tokensFile, JSON.stringify(newTokens, null, 2));
130
+ console.log('Refreshed tokens updated in', tokensFile);
131
+
132
+ return newTokens;
133
+ }
134
+
135
+ // --- Check for Existing Tokens ---
136
+ async function getStoredTokens() {
137
+ try {
138
+ const data = await fs.readFile(tokensFile, 'utf8');
139
+ return JSON.parse(data);
140
+ } catch (err) {
141
+ if (err.code === 'ENOENT') {
142
+ console.log('No stored tokens found.');
143
+ return null;
144
+ }
145
+ throw err;
146
+ }
147
+ }
148
+
149
+ // --- Updated Main Function with Enquirer Prompt ---
150
+ async function main() {
151
+ const email = 'user@example.com'; // You could prompt for this too
152
+ const password = 'SecurePass123!'; // You could prompt for this too
153
+
154
+ try {
155
+ const { client, clientId } = await getCognitoClient();
156
+
157
+ const storedTokens = await getStoredTokens();
158
+
159
+ if (storedTokens) {
160
+ const isValid = validateToken(storedTokens.accessToken);
161
+ if (isValid) {
162
+ console.log('Using existing valid tokens:', storedTokens);
163
+ return storedTokens;
164
+ } else {
165
+ const refreshedTokens = await refreshToken(storedTokens.refreshToken, client, clientId);
166
+ console.log('Using refreshed tokens:', refreshedTokens);
167
+ return refreshedTokens;
168
+ }
169
+ } else {
170
+ // No tokens, proceed with signup
171
+ await signUp(email, password, client, clientId);
172
+
173
+ // Prompt user for verification code
174
+ const { verificationCode } = await prompt({
175
+ type: 'input',
176
+ name: 'verificationCode',
177
+ message: 'Enter the verification code sent to your email:',
178
+ validate: (value) => value.length > 0 || 'Please enter a valid code',
179
+ });
180
+
181
+ await verifyEmail(email, verificationCode, client, clientId);
182
+ const tokens = await login(email, password, client, clientId);
183
+ console.log('New tokens generated and stored:', tokens);
184
+ return tokens;
185
+ }
186
+ } catch (error) {
187
+ console.error('Error in process:', error.message);
188
+ throw error;
189
+ }
190
+ }
191
+
192
+ // Run the script
193
+ main()
194
+ .then((tokens) => console.log('Final tokens:', tokens))
195
+ .catch((err) => console.error('Main execution failed:', err.message));
196
+
197
+
198
+
199
+ -----
200
+
201
+ v2
202
+
203
+ const {
204
+ CognitoIdentityProviderClient,
205
+ SignUpCommand,
206
+ ConfirmSignUpCommand,
207
+ InitiateAuthCommand,
208
+ RefreshTokenCommand,
209
+ } = require('@aws-sdk/client-cognito-identity-provider');
210
+ const fs = require('fs').promises;
211
+ const path = require('path');
212
+ const axios = require('axios');
213
+ const { prompt } = require('enquirer');
214
+ const jwt = require('jsonwebtoken');
215
+ const jwksClient = require('jwks-rsa');
216
+
217
+ // Simulated DNS-based config fetch
218
+ async function fetchCognitoConfig(dnsName = 'config.myapp.com') {
219
+ try {
220
+ const response = await axios.get(`https://${dnsName}/cognito-config`);
221
+ return response.data;
222
+ } catch (err) {
223
+ console.error('Config fetch error:', err.message);
224
+ return {
225
+ region: 'YOUR_AWS_REGION', // e.g., 'us-east-1'
226
+ userPoolId: 'YOUR_USER_POOL_ID', // e.g., 'us-east-1_abc123'
227
+ clientId: 'YOUR_CLIENT_ID' // e.g., '1a2b3c4d5e6f7g8h9i0j'
228
+ };
229
+ }
230
+ }
231
+
232
+ // Directory and file for storing tokens
233
+ const tokensDir = path.join(__dirname, 'tokens');
234
+ const tokensFile = path.join(tokensDir, 'tokens.json');
235
+
236
+ // --- Initialize Cognito Client with Dynamic Config ---
237
+ async function getCognitoClient() {
238
+ const config = await fetchCognitoConfig();
239
+ return {
240
+ client: new CognitoIdentityProviderClient({ region: config.region }),
241
+ userPoolId: config.userPoolId,
242
+ clientId: config.clientId,
243
+ };
244
+ }
245
+
246
+ // --- Signup Function with Email Verification ---
247
+ async function signUp(email, password, client, clientId) {
248
+ const params = {
249
+ ClientId: clientId,
250
+ Username: email,
251
+ Password: password,
252
+ UserAttributes: [{ Name: 'email', Value: email }],
253
+ };
254
+
255
+ const command = new SignUpCommand(params);
256
+ const result = await client.send(command);
257
+ console.log('Signup successful! Please check your email for a verification code.', result.UserSub);
258
+ return result;
259
+ }
260
+
261
+ // --- Email Verification Function ---
262
+ async function verifyEmail(email, verificationCode, client, clientId) {
263
+ const params = {
264
+ ClientId: clientId,
265
+ Username: email,
266
+ ConfirmationCode: verificationCode,
267
+ };
268
+
269
+ const command = new ConfirmSignUpCommand(params);
270
+ const result = await client.send(command);
271
+ console.log('Email verified successfully!');
272
+ return result;
273
+ }
274
+
275
+ // --- Login Function ---
276
+ async function login(email, password, client, clientId) {
277
+ const params = {
278
+ AuthFlow: 'USER_PASSWORD_AUTH',
279
+ ClientId: clientId,
280
+ AuthParameters: { USERNAME: email, PASSWORD: password },
281
+ };
282
+
283
+ const command = new InitiateAuthCommand(params);
284
+ const result = await client.send(command);
285
+ const tokens = {
286
+ accessToken: result.AuthenticationResult.AccessToken,
287
+ idToken: result.AuthenticationResult.IdToken,
288
+ refreshToken: result.AuthenticationResult.RefreshToken,
289
+ };
290
+ console.log('Login successful!', tokens);
291
+
292
+ await fs.mkdir(tokensDir, { recursive: true });
293
+ await fs.writeFile(tokensFile, JSON.stringify(tokens, null, 2));
294
+ console.log('Tokens stored in', tokensFile);
295
+
296
+ return tokens;
297
+ }
298
+
299
+ // --- Full Token Validation Function for Production ---
300
+ async function validateToken(token, userPoolId, clientId) {
301
+ const jwksUri = `https://cognito-idp.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
302
+ const client = jwksClient({
303
+ jwksUri,
304
+ cache: true,
305
+ rateLimit: true,
306
+ jwksRequestsPerMinute: 5,
307
+ });
308
+
309
+ function getKey(header, callback) {
310
+ client.getSigningKey(header.kid, (err, key) => {
311
+ if (err) {
312
+ callback(err);
313
+ } else {
314
+ const signingKey = key.getPublicKey();
315
+ callback(null, signingKey);
316
+ }
317
+ });
318
+ }
319
+
320
+ return new Promise((resolve, reject) => {
321
+ jwt.verify(
322
+ token,
323
+ getKey,
324
+ {
325
+ issuer: `https://cognito-idp.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com/${userPoolId}`,
326
+ audience: clientId, // For access token; idToken uses 'aud' differently, adjust if validating idToken
327
+ algorithms: ['RS256'],
328
+ },
329
+ (err, decoded) => {
330
+ if (err) {
331
+ console.error('Token validation failed:', err.message);
332
+ resolve(false);
333
+ } else {
334
+ console.log('Token is valid:', decoded);
335
+ resolve(true);
336
+ }
337
+ }
338
+ );
339
+ });
340
+ }
341
+
342
+ // --- Refresh Token Function ---
343
+ async function refreshToken(refreshToken, client, clientId) {
344
+ const params = {
345
+ AuthFlow: 'REFRESH_TOKEN_AUTH',
346
+ ClientId: clientId,
347
+ AuthParameters: { REFRESH_TOKEN: refreshToken },
348
+ };
349
+
350
+ const command = new RefreshTokenCommand(params);
351
+ const result = await client.send(command);
352
+ const newTokens = {
353
+ accessToken: result.AuthenticationResult.AccessToken,
354
+ idToken: result.AuthenticationResult.IdToken,
355
+ refreshToken: refreshToken,
356
+ };
357
+ console.log('Token refreshed successfully!', newTokens);
358
+
359
+ await fs.writeFile(tokensFile, JSON.stringify(newTokens, null, 2));
360
+ console.log('Refreshed tokens updated in', tokensFile);
361
+
362
+ return newTokens;
363
+ }
364
+
365
+ // --- Check for Existing Tokens ---
366
+ async function getStoredTokens() {
367
+ try {
368
+ const data = await fs.readFile(tokensFile, 'utf8');
369
+ return JSON.parse(data);
370
+ } catch (err) {
371
+ if (err.code === 'ENOENT') {
372
+ console.log('No stored tokens found.');
373
+ return null;
374
+ }
375
+ throw err;
376
+ }
377
+ }
378
+
379
+ // --- Updated Main Function ---
380
+ async function main() {
381
+ const email = 'user@example.com';
382
+ const password = 'SecurePass123!';
383
+
384
+ try {
385
+ const { client, userPoolId, clientId } = await getCognitoClient();
386
+
387
+ const storedTokens = await getStoredTokens();
388
+
389
+ if (storedTokens) {
390
+ const isValid = await validateToken(storedTokens.accessToken, userPoolId, clientId);
391
+ if (isValid) {
392
+ console.log('Using existing valid tokens:', storedTokens);
393
+ return storedTokens;
394
+ } else {
395
+ const refreshedTokens = await refreshToken(storedTokens.refreshToken, client, clientId);
396
+ console.log('Using refreshed tokens:', refreshedTokens);
397
+ return refreshedTokens;
398
+ }
399
+ } else {
400
+ await signUp(email, password, client, clientId);
401
+
402
+ const { verificationCode } = await prompt({
403
+ type: 'input',
404
+ name: 'verificationCode',
405
+ message: 'Enter the verification code sent to your email:',
406
+ validate: (value) => value.length > 0 || 'Please enter a valid code',
407
+ });
408
+
409
+ await verifyEmail(email, verificationCode, client, clientId);
410
+ const tokens = await login(email, password, client, clientId);
411
+ console.log('New tokens generated and stored:', tokens);
412
+ return tokens;
413
+ }
414
+ } catch (error) {
415
+ console.error('Error in process:', error.message);
416
+ throw error;
417
+ }
418
+ }
419
+
420
+ // Run the script
421
+ main()
422
+ .then((tokens) => console.log('Final tokens:', tokens))
423
+ .catch((err) => console.error('Main execution failed:', err.message));
424
+
425
+
@@ -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 } = require('./create-commands');
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
 
@@ -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.0",
3
+ "version": "3.6.2-beta1",
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.0",
86
- "@kumologica/devkit": "3.6.0",
87
- "@kumologica/runtime": "3.6.0",
86
+ "@kumologica/builder": "3.6.2-beta1",
87
+ "@kumologica/devkit": "3.6.2-beta1",
88
+ "@kumologica/runtime": "3.6.2-beta1",
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": "7.5.9",
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