@adobe/aio-cli-plugin-api-mesh 2.0.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.
@@ -73,134 +73,6 @@ describe('update command tests', () => {
73
73
  });
74
74
  });
75
75
 
76
- afterEach(() => {
77
- jest.restoreAllMocks();
78
- });
79
-
80
- test('should fail if mesh id is missing', async () => {
81
- getMeshId.mockResolvedValue(null);
82
- const runResult = UpdateCommand.run();
83
-
84
- await expect(runResult).rejects.toMatchInlineSnapshot(
85
- `[Error: Unable to update. No mesh found for Org(1234) -> Project(5678) -> Workspace(123456789). Please check the details and try again.]`,
86
- );
87
- expect(logSpy.mock.calls).toMatchInlineSnapshot(`[]`);
88
- expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
89
- [
90
- [
91
- "Unable to update. No mesh found for Org(1234) -> Project(5678) -> Workspace(123456789). Please check the details and try again.",
92
- ],
93
- ]
94
- `);
95
- });
96
-
97
- test('should fail if getMeshId api failed', async () => {
98
- getMeshId.mockRejectedValue(new Error('getMeshId api failed'));
99
- const runResult = UpdateCommand.run();
100
-
101
- await expect(runResult).rejects.toMatchInlineSnapshot(
102
- `[Error: Unable to get mesh ID. Please check the details and try again. RequestId: dummy_request_id]`,
103
- );
104
- expect(logSpy.mock.calls).toMatchInlineSnapshot(`[]`);
105
- expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
106
- [
107
- [
108
- "Unable to get mesh ID. Please check the details and try again. RequestId: dummy_request_id",
109
- ],
110
- ]
111
- `);
112
- });
113
-
114
- test('should fail if update file path is missing', async () => {
115
- parseSpy.mockResolvedValueOnce({
116
- args: { file: null },
117
- flags: {
118
- ignoreCache: mockIgnoreCacheFlag,
119
- autoConfirmAction: mockAutoApproveAction,
120
- },
121
- });
122
- const runResult = UpdateCommand.run();
123
-
124
- await expect(runResult).rejects.toEqual(
125
- new Error('Missing required args. Run aio api-mesh update --help for more info.'),
126
- );
127
- expect(logSpy.mock.calls).toMatchInlineSnapshot(`[]`);
128
- expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
129
- [
130
- [
131
- "Missing required args. Run aio api-mesh update --help for more info.",
132
- ],
133
- ]
134
- `);
135
- });
136
-
137
- test('should fail if dummy file path is provided', async () => {
138
- readFile.mockRejectedValueOnce(new Error('File not found'));
139
- const runResult = UpdateCommand.run();
140
-
141
- await expect(runResult).rejects.toEqual(
142
- new Error(
143
- 'Unable to read the mesh configuration file provided. Please check the file and try again.',
144
- ),
145
- );
146
- expect(logSpy.mock.calls).toMatchInlineSnapshot(`
147
- [
148
- [
149
- "File not found",
150
- ],
151
- ]
152
- `);
153
- expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
154
- [
155
- [
156
- "Unable to read the mesh configuration file provided. Please check the file and try again.",
157
- ],
158
- ]
159
- `);
160
- });
161
-
162
- test('should not update if user prompt returns false', async () => {
163
- promptConfirm.mockResolvedValueOnce(false);
164
-
165
- const runResult = await UpdateCommand.run();
166
-
167
- expect(runResult).toMatchInlineSnapshot(`"Update cancelled"`);
168
- expect(logSpy.mock.calls).toMatchInlineSnapshot(`
169
- [
170
- [
171
- "Update cancelled",
172
- ],
173
- ]
174
- `);
175
- expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`[]`);
176
- });
177
-
178
- test('should fail if updateMesh method failed', async () => {
179
- updateMesh.mockRejectedValueOnce(new Error('dummy_error'));
180
-
181
- const runResult = UpdateCommand.run();
182
-
183
- await expect(runResult).rejects.toEqual(
184
- new Error(
185
- 'Unable to update the mesh. Please check the mesh configuration file and try again. If the error persists please contact support. RequestId: dummy_request_id',
186
- ),
187
- );
188
- expect(logSpy.mock.calls).toMatchInlineSnapshot(`
189
- [
190
- [
191
- "dummy_error",
192
- ],
193
- ]
194
- `);
195
- expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
196
- [
197
- [
198
- "Unable to update the mesh. Please check the mesh configuration file and try again. If the error persists please contact support. RequestId: dummy_request_id",
199
- ],
200
- ]
201
- `);
202
- });
203
-
204
76
  test('should pass with valid args', async () => {
205
77
  const runResult = await UpdateCommand.run();
206
78
 
@@ -322,4 +194,132 @@ describe('update command tests', () => {
322
194
  `);
323
195
  expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`[]`);
324
196
  });
197
+
198
+ test('should fail if mesh id is missing', async () => {
199
+ getMeshId.mockResolvedValue(null);
200
+ const runResult = UpdateCommand.run();
201
+
202
+ await expect(runResult).rejects.toMatchInlineSnapshot(
203
+ `[Error: Unable to update. No mesh found for Org(1234) -> Project(5678) -> Workspace(123456789). Please check the details and try again.]`,
204
+ );
205
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`[]`);
206
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
207
+ [
208
+ [
209
+ "Unable to update. No mesh found for Org(1234) -> Project(5678) -> Workspace(123456789). Please check the details and try again.",
210
+ ],
211
+ ]
212
+ `);
213
+ });
214
+
215
+ test('should fail if getMeshId api failed', async () => {
216
+ getMeshId.mockRejectedValue(new Error('getMeshId api failed'));
217
+ const runResult = UpdateCommand.run();
218
+
219
+ await expect(runResult).rejects.toMatchInlineSnapshot(
220
+ `[Error: Unable to get mesh ID. Please check the details and try again. RequestId: dummy_request_id]`,
221
+ );
222
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`[]`);
223
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
224
+ [
225
+ [
226
+ "Unable to get mesh ID. Please check the details and try again. RequestId: dummy_request_id",
227
+ ],
228
+ ]
229
+ `);
230
+ });
231
+
232
+ test('should fail if updateMesh method failed', async () => {
233
+ updateMesh.mockRejectedValueOnce(new Error('dummy_error'));
234
+
235
+ const runResult = UpdateCommand.run();
236
+
237
+ // await expect(runResult).rejects.toEqual(
238
+ // new Error(
239
+ // 'Unable to update the mesh. Please check the mesh configuration file and try again. If the error persists please contact support. RequestId: dummy_request_id',
240
+ // ),
241
+ // );
242
+
243
+ await expect(runResult).rejects.toMatchInlineSnapshot(
244
+ `[Error: Unable to update the mesh. Please check the mesh configuration file and try again. If the error persists please contact support. RequestId: dummy_request_id]`,
245
+ );
246
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
247
+ [
248
+ [
249
+ "dummy_error",
250
+ ],
251
+ ]
252
+ `);
253
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
254
+ [
255
+ [
256
+ "Unable to update the mesh. Please check the mesh configuration file and try again. If the error persists please contact support. RequestId: dummy_request_id",
257
+ ],
258
+ ]
259
+ `);
260
+ });
261
+
262
+ test('should fail if update file path is missing', async () => {
263
+ parseSpy.mockResolvedValueOnce({
264
+ args: { file: null },
265
+ flags: {
266
+ ignoreCache: mockIgnoreCacheFlag,
267
+ autoConfirmAction: mockAutoApproveAction,
268
+ },
269
+ });
270
+ const runResult = UpdateCommand.run();
271
+
272
+ await expect(runResult).rejects.toEqual(
273
+ new Error('Missing required args. Run aio api-mesh update --help for more info.'),
274
+ );
275
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`[]`);
276
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
277
+ [
278
+ [
279
+ "Missing required args. Run aio api-mesh update --help for more info.",
280
+ ],
281
+ ]
282
+ `);
283
+ });
284
+
285
+ test('should fail if dummy file path is provided', async () => {
286
+ readFile.mockRejectedValueOnce(new Error('File not found'));
287
+ const runResult = UpdateCommand.run();
288
+
289
+ await expect(runResult).rejects.toEqual(
290
+ new Error(
291
+ 'Unable to read the mesh configuration file provided. Please check the file and try again.',
292
+ ),
293
+ );
294
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
295
+ [
296
+ [
297
+ "File not found",
298
+ ],
299
+ ]
300
+ `);
301
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
302
+ [
303
+ [
304
+ "Unable to read the mesh configuration file provided. Please check the file and try again.",
305
+ ],
306
+ ]
307
+ `);
308
+ });
309
+
310
+ test('should not update if user prompt returns false', async () => {
311
+ promptConfirm.mockResolvedValueOnce(false);
312
+
313
+ const runResult = await UpdateCommand.run();
314
+
315
+ expect(runResult).toMatchInlineSnapshot(`"Update cancelled"`);
316
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
317
+ [
318
+ [
319
+ "Update cancelled",
320
+ ],
321
+ ]
322
+ `);
323
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`[]`);
324
+ });
325
325
  });
@@ -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
  };