@adobe/aio-cli-plugin-api-mesh 2.2.0 → 2.3.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.
Files changed (33) hide show
  1. package/oclif.manifest.json +1 -1
  2. package/package.json +2 -1
  3. package/src/commands/__fixtures__/files/requestParams.json +3 -0
  4. package/src/commands/__fixtures__/openapi-schema.json +4 -0
  5. package/src/commands/__fixtures__/requestParams.json +3 -0
  6. package/src/commands/__fixtures__/sample_fully_qualified_mesh.json +29 -0
  7. package/src/commands/__fixtures__/sample_invalid_mesh.txt +17 -0
  8. package/src/commands/__fixtures__/sample_mesh_files.json +23 -0
  9. package/src/commands/__fixtures__/sample_mesh_invalid_file_content.json +14 -0
  10. package/src/commands/__fixtures__/sample_mesh_invalid_file_name.json +27 -0
  11. package/src/commands/__fixtures__/sample_mesh_invalid_paths.json +23 -0
  12. package/src/commands/__fixtures__/sample_mesh_invalid_type.json +27 -0
  13. package/src/commands/__fixtures__/sample_mesh_mismatching_path.json +29 -0
  14. package/src/commands/__fixtures__/sample_mesh_outside_workspace_dir.json +23 -0
  15. package/src/commands/__fixtures__/sample_mesh_path_from_home.json +14 -0
  16. package/src/commands/__fixtures__/sample_mesh_subdirectory.json +23 -0
  17. package/src/commands/__fixtures__/sample_mesh_with_files_array.json +29 -0
  18. package/src/commands/api-mesh/__tests__/create.test.js +1000 -31
  19. package/src/commands/api-mesh/__tests__/get.test.js +8 -0
  20. package/src/commands/api-mesh/__tests__/init.test.js +390 -0
  21. package/src/commands/api-mesh/__tests__/update.test.js +419 -4
  22. package/src/commands/api-mesh/create.js +37 -68
  23. package/src/commands/api-mesh/get.js +7 -2
  24. package/src/commands/api-mesh/init.js +168 -0
  25. package/src/commands/api-mesh/source/__tests__/get.test.js +1 -1
  26. package/src/commands/api-mesh/source/__tests__/install.test.js +2 -2
  27. package/src/commands/api-mesh/update.js +35 -67
  28. package/src/helpers.js +249 -91
  29. package/src/lib/devConsole.js +1 -2
  30. package/src/templates/gitignore +1 -0
  31. package/src/templates/package.json +38 -0
  32. package/src/utils.js +329 -31
  33. package/src/meshInterpolation.js +0 -120
package/src/helpers.js CHANGED
@@ -12,7 +12,6 @@ governing permissions and limitations under the License.
12
12
 
13
13
  const fs = require('fs');
14
14
  const inquirer = require('inquirer');
15
-
16
15
  const Config = require('@adobe/aio-lib-core-config');
17
16
  const { getToken, context } = require('@adobe/aio-lib-ims');
18
17
  const { CLI } = require('@adobe/aio-lib-ims/src/context');
@@ -22,10 +21,48 @@ const { getCliEnv } = require('@adobe/aio-lib-env');
22
21
  const logger = require('../src/classes/logger');
23
22
  const { UUID } = require('./classes/UUID');
24
23
  const CONSTANTS = require('./constants');
25
- const { objToString } = require('./utils');
24
+ const path = require('path');
25
+ const { exec } = require('child_process');
26
+ const { stdout, stderr } = require('process');
27
+ const jsmin = require('jsmin').jsmin;
26
28
 
27
29
  const { DEV_CONSOLE_BASE_URL, DEV_CONSOLE_API_KEY, AIO_CLI_API_KEY } = CONSTANTS;
28
30
 
31
+ /**
32
+ * Returns the string representation of the object's path.
33
+ * If the path evaluates to false, the default string is returned.
34
+ *
35
+ * @param {object} obj
36
+ * @param {Array<string>} path
37
+ * @param {string} defaultString
38
+ * @returns {string}
39
+ */
40
+ function objToString(obj, path = [], defaultString = '') {
41
+ try {
42
+ // Cache the current object
43
+ let current = obj;
44
+
45
+ // For each item in the path, dig into the object
46
+ for (let i = 0; i < path.length; i++) {
47
+ // If the item isn't found, return the default (or null)
48
+ if (!current[path[i]]) return defaultString;
49
+
50
+ // Otherwise, update the current value
51
+ current = current[path[i]];
52
+ }
53
+
54
+ if (typeof current === 'string') {
55
+ return current;
56
+ } else if (typeof current === 'object') {
57
+ return JSON.stringify(current, null, 2);
58
+ } else {
59
+ return defaultString;
60
+ }
61
+ } catch (error) {
62
+ return defaultString;
63
+ }
64
+ }
65
+
29
66
  /**
30
67
  * @param configFilePath
31
68
  */
@@ -134,9 +171,10 @@ async function getDevConsoleConfig() {
134
171
  }
135
172
 
136
173
  /**
174
+ * @param options
137
175
  * @returns {string} Returns organizations the user belongs to
138
176
  */
139
- async function getAuthorizedOrganization() {
177
+ async function getAuthorizedOrganization(options = { verbose: true }) {
140
178
  logger.info(`Initializing organization selection for`);
141
179
 
142
180
  const { consoleCLI } = await getLibConsoleCLI();
@@ -166,7 +204,9 @@ async function getAuthorizedOrganization() {
166
204
  }
167
205
  } else {
168
206
  logger.debug(`Selected organization config ${objToString(consoleConfigOrg)}`);
169
- console.log(`Selected organization: ${consoleConfigOrg.name}`);
207
+ if (options.verbose) {
208
+ console.log(`Selected organization: ${consoleConfigOrg.name}`);
209
+ }
170
210
 
171
211
  return Object.assign({}, consoleConfigOrg);
172
212
  }
@@ -175,8 +215,9 @@ async function getAuthorizedOrganization() {
175
215
  /**
176
216
  * @param imsOrgId
177
217
  * @param imsOrgTitle
218
+ * @param options
178
219
  */
179
- async function getProject(imsOrgId, imsOrgTitle) {
220
+ async function getProject(imsOrgId, imsOrgTitle, options = { verbose: true }) {
180
221
  logger.info(`Initializing project selection for ${imsOrgId}`);
181
222
 
182
223
  const { consoleCLI } = await getLibConsoleCLI();
@@ -210,7 +251,9 @@ async function getProject(imsOrgId, imsOrgTitle) {
210
251
  }
211
252
  } else {
212
253
  logger.debug(`Selected project config ${objToString(consoleConfigProject)}`);
213
- console.log(`Selected project: ${consoleConfigProject.title}`);
254
+ if (options.verbose) {
255
+ console.log(`Selected project: ${consoleConfigProject.title}`);
256
+ }
214
257
 
215
258
  return consoleConfigProject;
216
259
  }
@@ -221,8 +264,15 @@ async function getProject(imsOrgId, imsOrgTitle) {
221
264
  * @param projectId
222
265
  * @param imsOrgTitle
223
266
  * @param projectTitle
267
+ * @param options
224
268
  */
225
- async function getWorkspace(orgId, projectId, imsOrgTitle, projectTitle) {
269
+ async function getWorkspace(
270
+ orgId,
271
+ projectId,
272
+ imsOrgTitle,
273
+ projectTitle,
274
+ options = { verbose: true },
275
+ ) {
226
276
  logger.info(`Initializing workspace selection for ${orgId} -> ${projectId}`);
227
277
 
228
278
  const { consoleCLI } = await getLibConsoleCLI();
@@ -255,7 +305,9 @@ async function getWorkspace(orgId, projectId, imsOrgTitle, projectTitle) {
255
305
  }
256
306
  } else {
257
307
  logger.debug(`Selected workspace config ${objToString(consoleConfigWorkspace)}`);
258
- console.log(`Select workspace: ${consoleConfigWorkspace.name}`);
308
+ if (options.verbose) {
309
+ console.log(`Select workspace: ${consoleConfigWorkspace.name}`);
310
+ }
259
311
 
260
312
  return {
261
313
  id: consoleConfigWorkspace.id,
@@ -311,7 +363,7 @@ const selectWorkspace = async (orgId, projectId, imsOrgTitle, projectTitle) => {
311
363
  throw new Error('No workspace selected');
312
364
  }
313
365
  } else {
314
- this.error(
366
+ throw new Error(
315
367
  'No workspaces found for the selected organization: ' +
316
368
  imsOrgTitle +
317
369
  ' and project: ' +
@@ -344,16 +396,18 @@ async function getLibConsoleCLI() {
344
396
  * @returns {any} Returns an object with properties ready for consumption
345
397
  */
346
398
  async function initSdk(options) {
347
- const { ignoreCache = false } = options;
399
+ const { ignoreCache = false, verbose = true } = options;
348
400
 
349
401
  let org;
350
402
  let project;
351
403
  let workspace;
352
404
 
353
405
  if (!ignoreCache) {
354
- org = await getAuthorizedOrganization();
355
- project = await getProject(org.id, org.name);
356
- workspace = await getWorkspace(org.id, project.id, org.name, project.title);
406
+ org = await getAuthorizedOrganization({ verbose: verbose });
407
+ project = await getProject(org.id, org.name, { verbose: verbose });
408
+ workspace = await getWorkspace(org.id, project.id, org.name, project.title, {
409
+ verbose: verbose,
410
+ });
357
411
  } else {
358
412
  org = await selectAuthorizedOrganization();
359
413
  project = await selectProject(org.id, org.name);
@@ -459,113 +513,217 @@ async function promptInput(message) {
459
513
  return selected.item;
460
514
  }
461
515
 
462
- function clearEnv() {
463
- for (const key in process.env) {
464
- delete process.env[key];
465
- }
466
- }
516
+ /**
517
+ * Import the files in the files array in meshConfig
518
+ *
519
+ * @param data MeshConfig
520
+ * @param filesList List of files in meshConfig
521
+ * @param meshConfigName MeshConfigName
522
+ * @param autoConfirmActionFlag The user won't be prompted any questions, if this flag is set
523
+ */
524
+ async function importFiles(data, filesListArray, meshConfigName, autoConfirmActionFlag) {
525
+ //if autoConfirmActionFlag is passed in the command, it should override by default
526
+ let shouldOverride = true;
527
+ let filesNotFound = [];
528
+ let filesPathMap = new Map();
529
+ let filesListMap = new Map(
530
+ filesListArray.map(ele => {
531
+ return [ele];
532
+ }),
533
+ );
467
534
 
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;
535
+ //copy the meshConfig data
536
+ let resultData = data;
537
+
538
+ //array of {file to be overridden, overrideIndex}
539
+ let overrideArr = [];
540
+
541
+ if (data.meshConfig.files) {
542
+ for (let i = 0; i < data.meshConfig.files?.length; i++) {
543
+ filesPathMap.set(data.meshConfig.files[i].path, i);
490
544
  }
545
+ }
491
546
 
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}`);
547
+ for (let file of filesListMap.keys()) {
548
+ //if file exists in files array
549
+ if (filesPathMap.has(file)) {
550
+ //if file exists in files array, then override
551
+ if (fs.existsSync(path.resolve(path.dirname(meshConfigName), file))) {
552
+ if (!autoConfirmActionFlag) {
553
+ let index = filesPathMap.get(file);
554
+ overrideArr.push({ fileName: file, index: index });
555
+ }
499
556
  }
500
- if (key in envDict) {
501
- // duplicate key found
502
- errors.push(`Duplicate key << ${key} >> on line ${index + 1}`);
557
+ } else {
558
+ //if file does not exist in files array, but exists in filesystem, we append
559
+ if (fs.existsSync(path.resolve(path.dirname(meshConfigName), file))) {
560
+ resultData = updateFilesArray(resultData, file, meshConfigName, -1);
561
+ } else {
562
+ filesNotFound.push(file);
503
563
  }
504
- envDict[key] = value;
505
564
  }
506
565
  }
507
- if (errors.length) {
508
- return {
509
- valid: false,
510
- error: errors.toString(),
511
- };
566
+
567
+ if (filesNotFound.length) {
568
+ for (let i = 0; i < filesNotFound.length; i++) {
569
+ filesNotFound[i] = path.basename(filesNotFound[i]);
570
+ }
571
+
572
+ throw new Error(
573
+ `Please make sure the file(s): ${filesNotFound.join(', ')} and ${path.basename(
574
+ meshConfigName,
575
+ )} are in the same directory/subdirectory`,
576
+ );
512
577
  }
513
- return {
514
- valid: true,
515
- };
516
- }
517
578
 
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');
579
+ for (let i = 0; i < overrideArr.length; i++) {
580
+ shouldOverride = await promptConfirm(
581
+ `Do you want to override the ${path.basename(overrideArr[i].fileName)} file?`,
582
+ );
583
+
584
+ if (shouldOverride) {
585
+ resultData = updateFilesArray(
586
+ resultData,
587
+ overrideArr[i].fileName,
588
+ meshConfigName,
589
+ overrideArr[i].index,
590
+ );
591
+ }
524
592
  }
593
+
594
+ return resultData;
525
595
  }
526
596
 
597
+ /**loads the pupa module dynamically and then interpolates the raw data from mesh file with object data
598
+ * @param {data}
599
+ * @param {obj}
600
+ * @returns {object} having interpolationStatus, missingKeys and interpolatedMesh
601
+ */
602
+
527
603
  async function interpolateMesh(data, obj) {
528
- let missingKeys = [];
604
+ let missingKeys = new Set();
529
605
  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
- }
606
+ let pupa;
607
+ try {
608
+ pupa = (await import('pupa')).default;
609
+ } catch {
610
+ throw new Error('Error while loading pupa module');
611
+ }
612
+
613
+ interpolatedMesh = pupa(data, obj, {
614
+ ignoreMissing: true,
615
+ transform: ({ value, key }) => {
616
+ if (key.startsWith('env.')) {
617
+ if (value) {
618
+ return value;
541
619
  } else {
542
- //ignore
543
- return undefined;
620
+ // missing value, add to list
621
+ missingKeys.add(key.split('.')[1]);
544
622
  }
545
- return value;
546
- },
547
- });
623
+ } else {
624
+ //ignore
625
+ return undefined;
626
+ }
627
+ return value;
628
+ },
548
629
  });
549
630
 
550
- if (missingKeys.length) {
631
+ if (missingKeys.size) {
551
632
  return {
552
633
  interpolationStatus: 'failed',
553
- missingKeys: missingKeys,
634
+ missingKeys: Array.from(missingKeys),
554
635
  interpolatedMesh: '',
555
636
  };
556
637
  }
557
638
  return {
558
639
  interpolationStatus: 'success',
559
640
  missingKeys: [],
560
- interpolatedMesh: interpolatedMesh,
641
+ interpolatedMeshData: interpolatedMesh,
561
642
  };
562
643
  }
563
644
 
564
- async function getname(name, length) {
565
- return { name: name, length: length };
645
+ /** Function to run cli command
646
+ *
647
+ * @param command Ocliff/Command
648
+ * @param workingDirectory string
649
+ *
650
+ * @returns Promise<void>
651
+ */
652
+ function runCliCommand(command, workingDirectory = '.') {
653
+ return new Promise((resolve, reject) => {
654
+ const childProcess = exec(command, { cwd: workingDirectory });
655
+ childProcess.stdout.pipe(stdout);
656
+ childProcess.stdin.pipe(stderr);
657
+ childProcess.on('exit', code => {
658
+ if (code === 0) {
659
+ resolve();
660
+ } else {
661
+ reject(new Error(`${command} exection failed`));
662
+ }
663
+ });
664
+ });
665
+ }
666
+
667
+ /**
668
+ * Append/override files to the files array in meshConfig
669
+ *
670
+ * @param data MeshConfig
671
+ * @param file File to append or override
672
+ * @param meshConfigName MeshConfig name
673
+ * @param index Append operation if index is -1, else override, it is the index where the override takes place
674
+ */
675
+ function updateFilesArray(data, file, meshConfigName, index) {
676
+ try {
677
+ let readFileData = fs.readFileSync(
678
+ path.resolve(path.dirname(meshConfigName), file),
679
+ { encoding: 'utf-8' },
680
+ err => {
681
+ if (err) {
682
+ throw new Error(err);
683
+ }
684
+ },
685
+ );
686
+
687
+ try {
688
+ //validate JSON file
689
+ if (path.extname(file) === '.json') {
690
+ readFileData = JSON.stringify(JSON.parse(readFileData));
691
+ }
692
+ } catch (err) {
693
+ logger.error(err.message);
694
+ throw new Error(`Invalid JSON content in ${path.basename(file)}`);
695
+ }
696
+
697
+ //data to be overridden or appended
698
+ const dataInFilesArray = jsmin(readFileData);
699
+
700
+ if (index >= 0) {
701
+ data.meshConfig.files[index] = {
702
+ path: file,
703
+ content: dataInFilesArray,
704
+ };
705
+ } else {
706
+ //if the files array does not exist
707
+ if (!data.meshConfig.files) {
708
+ data.meshConfig.files = [];
709
+ }
710
+
711
+ //if the files arrray exists, we append the file path and content in meshConfig
712
+ data.meshConfig.files.push({
713
+ path: file,
714
+ content: dataInFilesArray,
715
+ });
716
+ }
717
+
718
+ return data;
719
+ } catch (err) {
720
+ logger.error(err.message);
721
+ throw new Error(err.message);
722
+ }
566
723
  }
567
724
 
568
725
  module.exports = {
726
+ objToString,
569
727
  promptInput,
570
728
  promptConfirm,
571
729
  getLibConsoleCLI,
@@ -574,8 +732,8 @@ module.exports = {
574
732
  initRequestId,
575
733
  promptSelect,
576
734
  promptMultiselect,
577
- clearEnv,
578
- lintEnvFileContent,
735
+ importFiles,
579
736
  interpolateMesh,
580
- getname,
737
+ runCliCommand,
738
+ updateFilesArray,
581
739
  };
@@ -4,12 +4,11 @@
4
4
  const axios = require('axios');
5
5
 
6
6
  const logger = require('../classes/logger');
7
- const { objToString } = require('../utils');
8
7
  const CONSTANTS = require('../constants');
9
8
 
10
9
  const { DEV_CONSOLE_TRANSPORTER_API_KEY } = CONSTANTS;
11
10
 
12
- const { getDevConsoleConfig } = require('../helpers');
11
+ const { objToString, getDevConsoleConfig } = require('../helpers');
13
12
 
14
13
  const getApiKeyCredential = async (organizationId, projectId, workspaceId) => {
15
14
  const { baseUrl: devConsoleUrl, accessToken } = await getDevConsoleConfig();
@@ -0,0 +1 @@
1
+ node_modules
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "",
3
+ "version": "0.0.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "dependencies": {
8
+ "@graphql-mesh/cli": "^0.78.33",
9
+ "@graphql-mesh/graphql": "^0.32.4",
10
+ "@graphql-mesh/json-schema": "^0.35.28",
11
+ "@graphql-mesh/openapi": "^0.33.39",
12
+ "@graphql-mesh/plugin-http-details-extensions": "^0.0.12",
13
+ "@graphql-mesh/runtime": "^0.44.37",
14
+ "@graphql-mesh/transform-encapsulate": "^0.3.114",
15
+ "@graphql-mesh/transform-federation": "^0.9.60",
16
+ "@graphql-mesh/transform-filter-schema": "^0.14.113",
17
+ "@graphql-mesh/transform-hoist-field": "^0.1.78",
18
+ "@graphql-mesh/transform-naming-convention": "^0.12.3",
19
+ "@graphql-mesh/transform-prefix": "^0.11.104",
20
+ "@graphql-mesh/transform-prune": "^0.0.88",
21
+ "@graphql-mesh/transform-rename": "^0.13.2",
22
+ "@graphql-mesh/transform-replace-field": "^0.3.112",
23
+ "@graphql-mesh/transform-resolvers-composition": "^0.12.111",
24
+ "@graphql-mesh/transform-type-merging": "^0.4.56",
25
+ "@graphql-mesh/types": "^0.87.1",
26
+ "graphql": "^16.6.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "keywords": [
32
+ "api-mesh"
33
+ ],
34
+ "license": "Apache-2.0",
35
+ "scripts": {},
36
+ "description": "API mesh starter template",
37
+ "author": "Adobe Inc."
38
+ }