@adobe/aio-cli-plugin-api-mesh 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":"2.1.0","commands":{"PLUGINNAME":{"id":"PLUGINNAME","description":"Your description here","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio PLUGINNAME:some_command"],"flags":{"someflag":{"name":"someflag","type":"option","char":"f","description":"this is some flag"}},"args":[]},"api-mesh:create":{"id":"api-mesh:create","description":"Create a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:delete":{"id":"api-mesh:delete","description":"Delete the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[]},"api-mesh:describe":{"id":"api-mesh:describe","description":"Get details of a mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:get":{"id":"api-mesh:get","description":"Get the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:status":{"id":"api-mesh:status","description":"Get a mesh status with a given meshid.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:update":{"id":"api-mesh:update","description":"Update a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:source:discover":{"id":"api-mesh:source:discover","description":"Return the list of avaliable sources","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm install action prompt. CLI will not check ask user to install source.","allowNo":false}},"args":[]},"api-mesh:source:get":{"id":"api-mesh:source:get","description":"Command returns the content of a specific source.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:get -s=<version>@<source_name>","$ aio api-mesh:source:get -s<source_name>","$ aio api-mesh:source:get -m"],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm print action prompt. CLI will not check ask user to print source.","allowNo":false},"source":{"name":"source","type":"option","char":"s","description":"Source name"},"multiple":{"name":"multiple","type":"boolean","char":"m","description":"Select multiple sources","allowNo":false}},"args":[]},"api-mesh:source:install":{"id":"api-mesh:source:install","description":"Command to install the source to your API mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:install <version>@<source_name>","$ aio api-mesh:source:install <source_name> -v <variable_name>=<variable_value>","$ aio api-mesh:source:install <source_name> -f <path_to_variables_file>"],"flags":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm override action prompt. CLI will not check ask user to override source.","allowNo":false},"variable":{"name":"variable","type":"option","char":"v","description":"Variables required for the source"},"variable-file":{"name":"variable-file","type":"option","char":"f","description":"Variables file path"},"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"source"}]}}}
1
+ {"version":"2.2.0","commands":{"PLUGINNAME":{"id":"PLUGINNAME","description":"Your description here","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio PLUGINNAME:some_command"],"flags":{"someflag":{"name":"someflag","type":"option","char":"f","description":"this is some flag"}},"args":[]},"api-mesh:create":{"id":"api-mesh:create","description":"Create a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file"}},"args":[{"name":"file"}]},"api-mesh:delete":{"id":"api-mesh:delete","description":"Delete the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[]},"api-mesh:describe":{"id":"api-mesh:describe","description":"Get details of a mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:get":{"id":"api-mesh:get","description":"Get the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:status":{"id":"api-mesh:status","description":"Get a mesh status with a given meshid.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:update":{"id":"api-mesh:update","description":"Update a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file"}},"args":[{"name":"file"}]},"api-mesh:source:discover":{"id":"api-mesh:source:discover","description":"Return the list of avaliable sources","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm install action prompt. CLI will not check ask user to install source.","allowNo":false}},"args":[]},"api-mesh:source:get":{"id":"api-mesh:source:get","description":"Command returns the content of a specific source.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:get -s=<version>@<source_name>","$ aio api-mesh:source:get -s<source_name>","$ aio api-mesh:source:get -m"],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm print action prompt. CLI will not check ask user to print source.","allowNo":false},"source":{"name":"source","type":"option","char":"s","description":"Source name"},"multiple":{"name":"multiple","type":"boolean","char":"m","description":"Select multiple sources","allowNo":false}},"args":[]},"api-mesh:source:install":{"id":"api-mesh:source:install","description":"Command to install the source to your API mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:install <version>@<source_name>","$ aio api-mesh:source:install <source_name> -v <variable_name>=<variable_value>","$ aio api-mesh:source:install <source_name> -f <path_to_variables_file>"],"flags":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm override action prompt. CLI will not check ask user to override source.","allowNo":false},"variable":{"name":"variable","type":"option","char":"v","description":"Variables required for the source"},"variable-file":{"name":"variable-file","type":"option","char":"f","description":"Variables file path"},"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"source"}]}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/aio-cli-plugin-api-mesh",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -16,13 +16,14 @@
16
16
  "@oclif/errors": "^1.1.2",
17
17
  "axios": "^1.2.0",
18
18
  "chalk": "^4.1.0",
19
- "dotenv": "^16.0.1",
19
+ "dotenv": "^16.0.3",
20
20
  "fs-extra": "^11.1.0",
21
21
  "inquirer": "^8.2.4",
22
22
  "json-interpolate": "^1.0.3",
23
23
  "node-clipboardy": "^1.0.3",
24
24
  "pino": "^7.9.2",
25
25
  "pino-pretty": "^7.6.0",
26
+ "pupa": "^3.1.0",
26
27
  "source-registry-storage-adapter": "github:devx-services/source-registry-storage-adapter#main",
27
28
  "uuid": "^8.3.2"
28
29
  },
@@ -0,0 +1,8 @@
1
+ #Duplicate Keys
2
+ key1='value1'
3
+ key1='value2'
4
+ #Incorrect format
5
+ key2=='value3'
6
+ key3
7
+ key4='value4
8
+ key5='value5'
@@ -0,0 +1,3 @@
1
+ magentoAPIName='MagentoAPI'
2
+ magentoURL=''
3
+ includeHTTPDetailsValue=true
@@ -0,0 +1,17 @@
1
+ {
2
+ "meshConfig": {
3
+ "sources": [
4
+ {
5
+ "name": "{env.magentoAPIName}",
6
+ "handler": {
7
+ "graphql": {
8
+ "endpoint": "{env.magentoURL}"
9
+ }
10
+ }
11
+ }
12
+ ],
13
+ "responseConfig":{
14
+ "includeHTTPDetails":{env.includeHTTPDetailsValue}
15
+ }
16
+ }
17
+ }
@@ -21,6 +21,8 @@ const {
21
21
  subscribeCredentialToMeshService,
22
22
  } = require('../../../lib/devConsole');
23
23
 
24
+ const meshInterpolation = require('../../../meshInterpolation');
25
+
24
26
  const selectedOrg = { id: '1234', code: 'CODE1234@AdobeOrg', name: 'ORG01', type: 'entp' };
25
27
 
26
28
  const selectedProject = { id: '5678', title: 'Project01' };
@@ -40,6 +42,7 @@ jest.mock('../../../helpers', () => ({
40
42
  initSdk: jest.fn().mockResolvedValue({}),
41
43
  initRequestId: jest.fn().mockResolvedValue({}),
42
44
  promptConfirm: jest.fn().mockResolvedValue(true),
45
+ getname: jest.fn().mockResolvedValue({}),
43
46
  }));
44
47
  jest.mock('../../../lib/devConsole');
45
48
 
@@ -111,6 +114,7 @@ describe('create command tests', () => {
111
114
  },
112
115
  ]
113
116
  `);
117
+
114
118
  expect(CreateCommand.flags).toMatchInlineSnapshot(`
115
119
  {
116
120
  "autoConfirmAction": {
@@ -121,6 +125,14 @@ describe('create command tests', () => {
121
125
  "parse": [Function],
122
126
  "type": "boolean",
123
127
  },
128
+ "env": {
129
+ "char": "e",
130
+ "description": "Path to env file",
131
+ "input": [],
132
+ "multiple": false,
133
+ "parse": [Function],
134
+ "type": "option",
135
+ },
124
136
  "ignoreCache": {
125
137
  "allowNo": false,
126
138
  "char": "i",
@@ -481,4 +493,219 @@ describe('create command tests', () => {
481
493
  `);
482
494
  expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`[]`);
483
495
  });
496
+
497
+ test('must return proper object structure used by adobe/generator-app-api-mesh', async () => {
498
+ parseSpy.mockResolvedValueOnce({
499
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
500
+ flags: {
501
+ json: Promise.resolve(true),
502
+ },
503
+ });
504
+ const output = await CreateCommand.run();
505
+ expect(output).toHaveProperty('mesh');
506
+ expect(output).toHaveProperty('adobeIdIntegrationsForWorkspace');
507
+ expect(output.mesh).toEqual(expect.objectContaining({ meshId: 'dummy_mesh_id' }));
508
+ expect(output.adobeIdIntegrationsForWorkspace).toEqual(
509
+ expect.objectContaining({ apiKey: 'dummy_api_key' }),
510
+ );
511
+ });
512
+
513
+ test('should return error if the env file provided using --env flag is not found', async () => {
514
+ parseSpy.mockResolvedValueOnce({
515
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
516
+ flags: {
517
+ ignoreCache: mockIgnoreCacheFlag,
518
+ autoConfirmAction: Promise.resolve(true),
519
+ env: 'src/commands/__fixtures__/.env_nonExisting',
520
+ },
521
+ });
522
+ const runResult = CreateCommand.run();
523
+
524
+ await expect(runResult).rejects.toEqual(
525
+ new Error('Unable to read the env file provided. Please check the file and try again.'),
526
+ );
527
+
528
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
529
+ [
530
+ [
531
+ "ENOENT: no such file or directory, open 'src/commands/__fixtures__/.env_nonExisting'",
532
+ ],
533
+ ]
534
+ `);
535
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
536
+ [
537
+ [
538
+ "Unable to read the env file provided. Please check the file and try again.",
539
+ ],
540
+ ]
541
+ `);
542
+ });
543
+
544
+ test('should return error if the provided env file is invalid', async () => {
545
+ parseSpy.mockResolvedValueOnce({
546
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
547
+ flags: {
548
+ ignoreCache: mockIgnoreCacheFlag,
549
+ autoConfirmAction: Promise.resolve(true),
550
+ env: 'src/commands/__fixtures__/env_invalid',
551
+ },
552
+ });
553
+
554
+ const runResult = CreateCommand.run();
555
+
556
+ await expect(runResult).rejects.toEqual(
557
+ new Error(
558
+ "Issue in src/commands/__fixtures__/env_invalid file - Duplicate key << key1 >> on line 3,Invalid format for key/value << key2=='value3' >> on line 5,Invalid format << key3 >> on line 6,Invalid format for key/value << key4='value4 >> on line 7",
559
+ ),
560
+ );
561
+
562
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
563
+ [
564
+ [
565
+ "Issue in src/commands/__fixtures__/env_invalid file - Duplicate key << key1 >> on line 3,Invalid format for key/value << key2=='value3' >> on line 5,Invalid format << key3 >> on line 6,Invalid format for key/value << key4='value4 >> on line 7",
566
+ ],
567
+ ]
568
+ `);
569
+ });
570
+
571
+ test('should return error if the provided env file is valid but there are missing keys found in mesh interpolation', async () => {
572
+ parseSpy.mockResolvedValueOnce({
573
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
574
+ flags: {
575
+ ignoreCache: mockIgnoreCacheFlag,
576
+ autoConfirmAction: Promise.resolve(true),
577
+ env: 'src/commands/__fixtures__/env_valid',
578
+ },
579
+ });
580
+
581
+ const interpolateMeshFunc = jest.spyOn(meshInterpolation, 'interpolateMesh');
582
+ interpolateMeshFunc.mockResolvedValueOnce({
583
+ interpolationStatus: 'failed',
584
+ missingKeys: ['newKey1', 'newKey2', 'newKey1'],
585
+ interpolatedMesh: '',
586
+ });
587
+
588
+ const runResult = CreateCommand.run();
589
+ await expect(runResult).rejects.toEqual(
590
+ new Error('The mesh file cannot be interpolated due to missing keys : newKey1,newKey2'),
591
+ );
592
+
593
+ await expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
594
+ [
595
+ [
596
+ "The mesh file cannot be interpolated due to missing keys : newKey1,newKey2",
597
+ ],
598
+ ]
599
+ `);
600
+ });
601
+
602
+ test('should return error if the provided env file is valid and mesh interpolation is successful but interpolated mesh is not a valid JSON', async () => {
603
+ parseSpy.mockResolvedValueOnce({
604
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
605
+ flags: {
606
+ ignoreCache: mockIgnoreCacheFlag,
607
+ autoConfirmAction: Promise.resolve(true),
608
+ env: 'src/commands/__fixtures__/env_valid',
609
+ },
610
+ });
611
+
612
+ //sampleInterpolated mesh where value of responseConfig.includeHTTPDetails is invalid i.e. non-boolean
613
+ const sampleInterpolatedMesh =
614
+ '{"meshConfig":{"sources":[{"name":"<api-name>","handler":{"graphql":{"endpoint":"<api-url>"}}}],"responseConfig":{"includeHTTPDetails":sample}}}';
615
+
616
+ const interpolateMeshFunc = jest.spyOn(meshInterpolation, 'interpolateMesh');
617
+ interpolateMeshFunc.mockResolvedValueOnce({
618
+ interpolationStatus: 'success',
619
+ missingKeys: [],
620
+ interpolatedMeshData: sampleInterpolatedMesh,
621
+ });
622
+
623
+ const runResult = CreateCommand.run();
624
+ await expect(runResult).rejects.toEqual(
625
+ new Error('Interpolated mesh is not a valid JSON. Please check the generated json file.'),
626
+ );
627
+
628
+ await expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
629
+ [
630
+ [
631
+ "Interpolated mesh is not a valid JSON. Please check the generated json file.",
632
+ ],
633
+ ]
634
+ `);
635
+ });
636
+
637
+ test('should successfully create a mesh if provided env file is valid, mesh interpolation is successful and interpolated mesh is a valid JSON', async () => {
638
+ parseSpy.mockResolvedValue({
639
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
640
+ flags: {
641
+ ignoreCache: mockIgnoreCacheFlag,
642
+ autoConfirmAction: mockAutoApproveAction,
643
+ env: 'src/commands/__fixtures__/env_valid',
644
+ },
645
+ });
646
+
647
+ //sampleInterpolated mesh where the mesh string is a valid JSON
648
+ const sampleInterpolatedMesh =
649
+ '{"meshConfig":{"sources":[{"name":"<api-name>","handler":{"graphql":{"endpoint":"<api-url>"}}}],"responseConfig":{"includeHTTPDetails":true}}}';
650
+
651
+ const interpolateMeshFunc = jest.spyOn(meshInterpolation, 'interpolateMesh');
652
+ interpolateMeshFunc.mockResolvedValueOnce({
653
+ interpolationStatus: 'success',
654
+ missingKeys: [],
655
+ interpolatedMeshData: sampleInterpolatedMesh,
656
+ });
657
+
658
+ const runResult = await CreateCommand.run();
659
+
660
+ expect(promptConfirm).toHaveBeenCalledWith('Are you sure you want to create a mesh?');
661
+ expect(runResult).toMatchInlineSnapshot(`
662
+ {
663
+ "adobeIdIntegrationsForWorkspace": {
664
+ "apiKey": "dummy_api_key",
665
+ "id": "dummy_id",
666
+ },
667
+ "mesh": {
668
+ "meshConfig": {
669
+ "sources": [
670
+ {
671
+ "handler": {
672
+ "graphql": {
673
+ "endpoint": "<gql_endpoint>",
674
+ },
675
+ },
676
+ "name": "<api_name>",
677
+ },
678
+ ],
679
+ },
680
+ "meshId": "dummy_mesh_id",
681
+ },
682
+ "sdkList": [
683
+ "dummy_service",
684
+ ],
685
+ }
686
+ `);
687
+ });
688
+
689
+ test('should return error if inputMesh is not a valid JSON', async () => {
690
+ parseSpy.mockResolvedValue({
691
+ args: { file: 'src/commands/__fixtures__/sample_mesh_with_placeholder' },
692
+ flags: {
693
+ ignoreCache: mockIgnoreCacheFlag,
694
+ autoConfirmAction: mockAutoApproveAction,
695
+ },
696
+ });
697
+
698
+ const runResult = CreateCommand.run();
699
+ await expect(runResult).rejects.toEqual(
700
+ new Error('Input mesh file is not a valid JSON. Please check the file provided.'),
701
+ );
702
+
703
+ await expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
704
+ [
705
+ [
706
+ "Input mesh file is not a valid JSON. Please check the file provided.",
707
+ ],
708
+ ]
709
+ `);
710
+ });
484
711
  });
@@ -15,14 +15,16 @@ const { readFile } = require('fs/promises');
15
15
  const { initSdk, initRequestId, promptConfirm } = require('../../helpers');
16
16
  const logger = require('../../classes/logger');
17
17
  const CONSTANTS = require('../../constants');
18
- const { ignoreCacheFlag, autoConfirmActionFlag, jsonFlag } = require('../../utils');
18
+ const { ignoreCacheFlag, autoConfirmActionFlag, jsonFlag, envFileFlag } = require('../../utils');
19
19
  const {
20
20
  createMesh,
21
21
  createAPIMeshCredentials,
22
22
  subscribeCredentialToMeshService,
23
23
  } = require('../../lib/devConsole');
24
24
 
25
- require('dotenv').config();
25
+ const meshInterpolation = require('../../meshInterpolation');
26
+
27
+ const dotenv = require('dotenv');
26
28
 
27
29
  const { MULTITENANT_GRAPHQL_SERVER_BASE_URL } = CONSTANTS;
28
30
 
@@ -32,6 +34,7 @@ class CreateCommand extends Command {
32
34
  ignoreCache: ignoreCacheFlag,
33
35
  autoConfirmAction: autoConfirmActionFlag,
34
36
  json: jsonFlag,
37
+ env: envFileFlag,
35
38
  };
36
39
 
37
40
  static enableJsonFlag = true;
@@ -51,15 +54,16 @@ class CreateCommand extends Command {
51
54
 
52
55
  const ignoreCache = await flags.ignoreCache;
53
56
  const autoConfirmAction = await flags.autoConfirmAction;
54
-
57
+ const envFilePath=await flags.env;
55
58
  const { imsOrgId, projectId, workspaceId } = await initSdk({
56
59
  ignoreCache,
57
60
  });
58
61
 
59
- let data;
62
+ let inputMeshData;
60
63
 
64
+ //Input the mesh data from the input file
61
65
  try {
62
- data = JSON.parse(await readFile(args.file, 'utf8'));
66
+ inputMeshData = await readFile(args.file, 'utf8');
63
67
  } catch (error) {
64
68
  logger.error(error);
65
69
 
@@ -69,6 +73,66 @@ class CreateCommand extends Command {
69
73
  );
70
74
  }
71
75
 
76
+ let data;
77
+
78
+ if (envFilePath) {
79
+ let envFileContent;
80
+
81
+ //Read the environment file
82
+ try {
83
+ envFileContent = await readFile(envFilePath, 'utf8');
84
+ } catch (error) {
85
+ this.log(error.message);
86
+ this.error('Unable to read the env file provided. Please check the file and try again.');
87
+ }
88
+
89
+ //Validate the env file
90
+ const envFileValidity = meshInterpolation.validateEnvFileFormat(envFileContent);
91
+ if (envFileValidity.valid) {
92
+ //load env file into the process.env object
93
+ meshInterpolation.clearEnv();
94
+
95
+ //Added env at start of each environment variable
96
+ const envObj = { env: dotenv.config({ path: envFilePath }).parsed };
97
+
98
+ let {
99
+ interpolationStatus,
100
+ missingKeys,
101
+ interpolatedMeshData,
102
+ } = await meshInterpolation.interpolateMesh(inputMeshData, envObj);
103
+
104
+ //De-duplicate the missing keys array
105
+ missingKeys = missingKeys.filter(function (item, index, inputArray) {
106
+ return inputArray.indexOf(item) == index;
107
+ });
108
+
109
+ if (interpolationStatus == 'failed') {
110
+ this.error(
111
+ 'The mesh file cannot be interpolated due to missing keys : ' + missingKeys.toString(),
112
+ );
113
+ }
114
+
115
+ try {
116
+ data = JSON.parse(interpolatedMeshData);
117
+ } catch (err) {
118
+ this.log(err.message);
119
+ this.log(interpolatedMeshData);
120
+ this.error(
121
+ 'Interpolated mesh is not a valid JSON. Please check the generated json file.',
122
+ );
123
+ }
124
+ } else {
125
+ this.error(`Issue in ${envFilePath} file - ` + envFileValidity.error);
126
+ }
127
+ } else {
128
+ try {
129
+ data = JSON.parse(inputMeshData);
130
+ } catch (err) {
131
+ this.log(err.message);
132
+ this.error('Input mesh file is not a valid JSON. Please check the file provided.');
133
+ }
134
+ }
135
+
72
136
  let shouldContinue = true;
73
137
 
74
138
  if (!autoConfirmAction) {
@@ -14,16 +14,18 @@ const { readFile } = require('fs/promises');
14
14
 
15
15
  const logger = require('../../classes/logger');
16
16
  const { initSdk, initRequestId, promptConfirm } = require('../../helpers');
17
- const { ignoreCacheFlag, autoConfirmActionFlag } = require('../../utils');
17
+ const { ignoreCacheFlag, autoConfirmActionFlag, envFileFlag } = require('../../utils');
18
18
  const { getMeshId, updateMesh } = require('../../lib/devConsole');
19
+ const meshInterpolation = require('../../meshInterpolation');
19
20
 
20
- require('dotenv').config();
21
+ const dotenv = require('dotenv');
21
22
 
22
23
  class UpdateCommand extends Command {
23
24
  static args = [{ name: 'file' }];
24
25
  static flags = {
25
26
  ignoreCache: ignoreCacheFlag,
26
27
  autoConfirmAction: autoConfirmActionFlag,
28
+ env: envFileFlag,
27
29
  };
28
30
 
29
31
  async run() {
@@ -41,15 +43,17 @@ class UpdateCommand extends Command {
41
43
 
42
44
  const ignoreCache = await flags.ignoreCache;
43
45
  const autoConfirmAction = await flags.autoConfirmAction;
46
+ const envFilePath=await flags.env;
44
47
 
45
48
  const { imsOrgId, projectId, workspaceId } = await initSdk({
46
49
  ignoreCache,
47
50
  });
48
51
 
49
- let data;
52
+ let inputMeshData;
50
53
 
54
+ //Input the mesh data from the input file
51
55
  try {
52
- data = JSON.parse(await readFile(args.file, 'utf8'));
56
+ inputMeshData = await readFile(args.file, 'utf8');
53
57
  } catch (error) {
54
58
  logger.error(error);
55
59
 
@@ -59,7 +63,7 @@ class UpdateCommand extends Command {
59
63
  );
60
64
  }
61
65
 
62
- let meshId = null;
66
+ let meshId;
63
67
 
64
68
  try {
65
69
  meshId = await getMeshId(imsOrgId, projectId, workspaceId);
@@ -69,6 +73,66 @@ class UpdateCommand extends Command {
69
73
  );
70
74
  }
71
75
 
76
+ let data;
77
+
78
+ if (envFilePath) {
79
+ let envFileContent;
80
+
81
+ //Read the environment file
82
+ try {
83
+ envFileContent = await readFile(envFilePath, 'utf8');
84
+ } catch (error) {
85
+ this.log(error.message);
86
+ this.error('Unable to read the env file provided. Please check the file and try again.');
87
+ }
88
+
89
+ //Validate the env file
90
+ const envFileValidity = meshInterpolation.validateEnvFileFormat(envFileContent);
91
+ if (envFileValidity.valid) {
92
+ //load env file into the process.env object
93
+ meshInterpolation.clearEnv();
94
+
95
+ //Added env at start of each environment variable
96
+ const envObj = { env: dotenv.config({ path: envFilePath }).parsed };
97
+
98
+ let {
99
+ interpolationStatus,
100
+ missingKeys,
101
+ interpolatedMeshData,
102
+ } = await meshInterpolation.interpolateMesh(inputMeshData, envObj);
103
+
104
+ //De-duplicate the missing keys array
105
+ missingKeys = missingKeys.filter(function (item, index, inputArray) {
106
+ return inputArray.indexOf(item) == index;
107
+ });
108
+
109
+ if (interpolationStatus == 'failed') {
110
+ this.error(
111
+ 'The mesh file cannot be interpolated due to missing keys : ' + missingKeys.toString(),
112
+ );
113
+ }
114
+
115
+ try {
116
+ data = JSON.parse(interpolatedMeshData);
117
+ } catch (err) {
118
+ this.log(err.message);
119
+ this.log(interpolatedMeshData);
120
+ this.error(
121
+ 'Interpolated mesh is not a valid JSON. Please check the generated json file.',
122
+ );
123
+ }
124
+ } else {
125
+ this.error(`Issue in ${envFilePath} file - ` + envFileValidity.error);
126
+ }
127
+ } else {
128
+ try {
129
+ data = JSON.parse(inputMeshData);
130
+ } catch (err) {
131
+ this.log(err.message);
132
+ this.error('Input mesh file is not a valid JSON. Please check the input file provided.');
133
+ }
134
+ }
135
+
72
136
  if (meshId) {
73
137
  let shouldContinue = true;
74
138
 
package/src/helpers.js CHANGED
@@ -459,6 +459,112 @@ async function promptInput(message) {
459
459
  return selected.item;
460
460
  }
461
461
 
462
+ function clearEnv() {
463
+ for (const key in process.env) {
464
+ delete process.env[key];
465
+ }
466
+ }
467
+
468
+ function lintEnvFileContent(envContent) {
469
+ //Key should start with a underscore or an alphabet followed by underscore/alphanumeric characters
470
+ const envKeyRegex = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
471
+
472
+ const envValueRegex = /^(?:"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'|[^'"\s])+$/;
473
+
474
+ /*
475
+ The above regex matches one or more of below :
476
+ (?:"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'|[^'"\s])
477
+ which is
478
+ 1. ?:"(?:\\.|[^\\"])*" : Non capturing group starts and ends with '"'
479
+ */
480
+ const envDict = {};
481
+ const lines = envContent.split(/\r?\n/);
482
+ const errors = [];
483
+
484
+ for (let index = 0; index < lines.length; index++) {
485
+ const line = lines[index];
486
+ const trimmedLine = line.trim();
487
+ if (trimmedLine.startsWith('#') || trimmedLine === '') {
488
+ // ignore comment or empty lines
489
+ continue;
490
+ }
491
+
492
+ if (!trimmedLine.includes('=')) {
493
+ errors.push(`Invalid format << ${trimmedLine} >> on line ${index + 1}`);
494
+ } else {
495
+ const [key, value] = trimmedLine.split('=', 2);
496
+ if (!envKeyRegex.test(key) || !envValueRegex.test(value)) {
497
+ // invalid format: key or value does not match regex
498
+ errors.push(`Invalid format for key/value << ${trimmedLine} >> on line ${index + 1}`);
499
+ }
500
+ if (key in envDict) {
501
+ // duplicate key found
502
+ errors.push(`Duplicate key << ${key} >> on line ${index + 1}`);
503
+ }
504
+ envDict[key] = value;
505
+ }
506
+ }
507
+ if (errors.length) {
508
+ return {
509
+ valid: false,
510
+ error: errors.toString(),
511
+ };
512
+ }
513
+ return {
514
+ valid: true,
515
+ };
516
+ }
517
+
518
+ async function loadPupa() {
519
+ try {
520
+ const pupa = (await import('pupa')).default;
521
+ return pupa;
522
+ } catch {
523
+ console.log('Error while loading pupa module');
524
+ }
525
+ }
526
+
527
+ async function interpolateMesh(data, obj) {
528
+ let missingKeys = [];
529
+ let interpolatedMesh;
530
+ await loadPupa().then(pupa => {
531
+ interpolatedMesh = pupa(data, obj, {
532
+ ignoreMissing: true,
533
+ transform: ({ value, key }) => {
534
+ if (key.startsWith('env.')) {
535
+ if (value) {
536
+ return value;
537
+ } else {
538
+ // missing value, add to list
539
+ missingKeys.push(key.split('.')[1]);
540
+ }
541
+ } else {
542
+ //ignore
543
+ return undefined;
544
+ }
545
+ return value;
546
+ },
547
+ });
548
+ });
549
+
550
+ if (missingKeys.length) {
551
+ return {
552
+ interpolationStatus: 'failed',
553
+ missingKeys: missingKeys,
554
+ interpolatedMesh: '',
555
+ };
556
+ }
557
+ return {
558
+ interpolationStatus: 'success',
559
+ missingKeys: [],
560
+ interpolatedMesh: interpolatedMesh,
561
+ };
562
+ }
563
+
564
+ async function getname(name, length) {
565
+ return { name: name, length: length };
566
+ }
567
+
462
568
  module.exports = {
463
569
  promptInput,
464
570
  promptConfirm,
@@ -468,4 +574,8 @@ module.exports = {
468
574
  initRequestId,
469
575
  promptSelect,
470
576
  promptMultiselect,
577
+ clearEnv,
578
+ lintEnvFileContent,
579
+ interpolateMesh,
580
+ getname,
471
581
  };
@@ -0,0 +1,120 @@
1
+ /**
2
+ *clears the process.env object
3
+ */
4
+
5
+ function clearEnv() {
6
+ for (const key in process.env) {
7
+ delete process.env[key];
8
+ }
9
+ }
10
+
11
+ /**
12
+ *validates the environment file content
13
+ * @param {envContent}
14
+ * @returns {object} containing the status of validation . If validation is failed then the error property including the formatting errors is returned.
15
+ */
16
+
17
+ function validateEnvFileFormat(envContent) {
18
+ //Key should start with a underscore or an alphabet followed by underscore/alphanumeric characters
19
+ const envKeyRegex = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
20
+
21
+ const envValueRegex = /^(?:"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'|[^'"\s])+$/;
22
+
23
+ /*
24
+ The above regex matches one or more of below :
25
+ (?:"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'|[^'"\s])
26
+ which is
27
+ 1. ?:"(?:\\.|[^\\"])*" : Non capturing group starts and ends with '"'
28
+ */
29
+ const envDict = {};
30
+ const lines = envContent.split(/\r?\n/);
31
+ const errors = [];
32
+
33
+ for (let index = 0; index < lines.length; index++) {
34
+ const line = lines[index];
35
+ const trimmedLine = line.trim();
36
+ if (trimmedLine.startsWith('#') || trimmedLine === '') {
37
+ // ignore comment or empty lines
38
+ continue;
39
+ }
40
+
41
+ if (!trimmedLine.includes('=')) {
42
+ errors.push(`Invalid format << ${trimmedLine} >> on line ${index + 1}`);
43
+ } else {
44
+ const [key, value] = trimmedLine.split('=', 2);
45
+ if (!envKeyRegex.test(key) || !envValueRegex.test(value)) {
46
+ // invalid format: key or value does not match regex
47
+ errors.push(`Invalid format for key/value << ${trimmedLine} >> on line ${index + 1}`);
48
+ }
49
+ if (key in envDict) {
50
+ // duplicate key found
51
+ errors.push(`Duplicate key << ${key} >> on line ${index + 1}`);
52
+ }
53
+ envDict[key] = value;
54
+ }
55
+ }
56
+ if (errors.length) {
57
+ return {
58
+ valid: false,
59
+ error: errors.toString(),
60
+ };
61
+ }
62
+ return {
63
+ valid: true,
64
+ };
65
+ }
66
+
67
+ /**
68
+ *loads the pupa module dynamically and then interpolates the raw data from mesh file with object data
69
+ * @param {data}
70
+ * @param {obj}
71
+ * @returns {object} having interpolationStatus, missingKeys and interpolatedMesh
72
+ */
73
+
74
+ async function interpolateMesh(data, obj) {
75
+ let missingKeys = [];
76
+ let interpolatedMesh;
77
+ let pupa;
78
+ try {
79
+ pupa = (await import('pupa')).default;
80
+ } catch {
81
+ this.error('Error while loading pupa module');
82
+ }
83
+
84
+ interpolatedMesh = pupa(data, obj, {
85
+ ignoreMissing: true,
86
+ transform: ({ value, key }) => {
87
+ if (key.startsWith('env.')) {
88
+ if (value) {
89
+ return value;
90
+ } else {
91
+ // missing value, add to list
92
+ missingKeys.push(key.split('.')[1]);
93
+ }
94
+ } else {
95
+ //ignore
96
+ return undefined;
97
+ }
98
+ return value;
99
+ },
100
+ });
101
+
102
+ if (missingKeys.length) {
103
+ return {
104
+ interpolationStatus: 'failed',
105
+ missingKeys: missingKeys,
106
+ interpolatedMesh: '',
107
+ };
108
+ }
109
+ return {
110
+ interpolationStatus: 'success',
111
+ missingKeys: [],
112
+ interpolatedMeshData: interpolatedMesh,
113
+ };
114
+ }
115
+
116
+ module.exports = {
117
+ interpolateMesh,
118
+ clearEnv,
119
+ validateEnvFileFormat,
120
+ };
package/src/utils.js CHANGED
@@ -1,3 +1,5 @@
1
+ const { Flags } = require('@oclif/core');
2
+
1
3
  /**
2
4
  * Returns the string representation of the object's path.
3
5
  * If the path evaluates to false, the default string is returned.
@@ -33,8 +35,6 @@ function objToString(obj, path = [], defaultString = '') {
33
35
  }
34
36
  }
35
37
 
36
- const { Flags } = require('@oclif/core');
37
-
38
38
  const ignoreCacheFlag = Flags.boolean({
39
39
  char: 'i',
40
40
  description: 'Ignore cache and force manual org -> project -> workspace selection',
@@ -53,9 +53,15 @@ const jsonFlag = Flags.boolean({
53
53
  default: false,
54
54
  });
55
55
 
56
+ const envFileFlag = Flags.string({
57
+ char: 'e',
58
+ description: 'Path to env file',
59
+ });
60
+
56
61
  module.exports = {
57
62
  objToString,
58
63
  ignoreCacheFlag,
59
64
  autoConfirmActionFlag,
60
65
  jsonFlag,
66
+ envFileFlag,
61
67
  };