@adobe/aio-cli-plugin-api-mesh 5.2.2 → 5.2.3-alpha

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.
@@ -17,7 +17,7 @@ jest.mock('../../../helpers', () => ({
17
17
  initSdk: jest.fn().mockResolvedValue({}),
18
18
  initRequestId: jest.fn().mockResolvedValue({}),
19
19
  promptConfirm: jest.fn().mockResolvedValue(true),
20
- importFiles: jest.fn().mockResolvedValue(),
20
+ importFiles: jest.fn().mockResolvedValue({}),
21
21
  }));
22
22
  jest.mock('@adobe/aio-cli-lib-console', () => ({
23
23
  init: jest.fn().mockResolvedValue(mockConsoleCLIInstance),
@@ -547,7 +547,9 @@ describe('update command tests', () => {
547
547
  });
548
548
 
549
549
  importFiles.mockResolvedValueOnce({
550
- meshConfig,
550
+ data: {
551
+ meshConfig,
552
+ },
551
553
  });
552
554
 
553
555
  const output = await UpdateCommand.run();
@@ -99,7 +99,7 @@ class CreateCommand extends Command {
99
99
  // if local files are present, import them in files array in meshConfig
100
100
  if (filesList.length) {
101
101
  try {
102
- data = await importFiles(data, filesList, args.file, flags.autoConfirmAction);
102
+ ({ data } = await importFiles(data, filesList, args.file, flags.autoConfirmAction));
103
103
  } catch (err) {
104
104
  this.log(err.message);
105
105
  this.error('Unable to import the files in the mesh config. Check the file and try again.');
@@ -112,8 +112,7 @@ class CreateCommand extends Command {
112
112
  await validateSecretsFile(secretsFilePath);
113
113
  const secretsData = await interpolateSecrets(secretsFilePath, this);
114
114
  const publicKey = await getPublicEncryptionKey(imsOrgCode);
115
- const encryptedSecrets = await encryptSecrets(publicKey, secretsData);
116
- data.secrets = encryptedSecrets;
115
+ data.secrets = await encryptSecrets(publicKey, secretsData);
117
116
  } catch (err) {
118
117
  this.log(err.message);
119
118
  this.error('Unable to import secrets. Check the file and try again.');
@@ -13,6 +13,7 @@ const { Command } = require('@oclif/core');
13
13
  const {
14
14
  portNoFlag,
15
15
  debugFlag,
16
+ inspectPortNoFlag,
16
17
  selectFlag,
17
18
  envFileFlag,
18
19
  secretsFlag,
@@ -37,8 +38,15 @@ const {
37
38
  const logger = require('../../classes/logger');
38
39
  const { getMeshId, getMeshArtifact } = require('../../lib/devConsole');
39
40
  require('dotenv').config();
40
- const { runServer } = require('../../server');
41
- const { fixPlugins } = require('../../fixPlugins');
41
+ const { start } = require('../../wranglerCli');
42
+ const {
43
+ resolveOriginalSources,
44
+ copyBuiltMeshToPackage,
45
+ BUILT_MESH_ARTIFACT_DIRECTORY,
46
+ safeDelete,
47
+ getBuiltMeshTenantDirectory,
48
+ } = require('../../project');
49
+ const { resolveRelativeSources } = require('../../meshArtifact');
42
50
 
43
51
  const { validateMesh, buildMesh, compileMesh } = meshBuilder.default;
44
52
 
@@ -55,6 +63,7 @@ class RunCommand extends Command {
55
63
 
56
64
  static flags = {
57
65
  port: portNoFlag,
66
+ inspectPort: inspectPortNoFlag,
58
67
  debug: debugFlag,
59
68
  env: envFileFlag,
60
69
  autoConfirmAction: autoConfirmActionFlag,
@@ -66,182 +75,219 @@ class RunCommand extends Command {
66
75
 
67
76
  static examples = [];
68
77
 
69
- async run() {
70
- await initRequestId();
78
+ /**
79
+ * Validate the current working directory to ensure it is set up to run the command
80
+ * @returns {Promise<void>}
81
+ * @throws {Error} when project is not set up correctly
82
+ */
83
+ async validateCwd() {
84
+ //Ensure that current directory includes package.json
85
+ if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {
86
+ throw new Error(
87
+ '`aio api-mesh run` cannot be executed because there is no package.json file in the current directory. Use `aio api-mesh init` to set up a package.',
88
+ );
89
+ }
90
+ }
71
91
 
72
- logger.info(`RequestId: ${global.requestId}`);
92
+ /**
93
+ * Handle remote mesh artifact
94
+ * @returns {Promise<string>} Mesh identifier
95
+ * @throws {Error} when failure retrieving remote mesh artifact
96
+ */
97
+ async handleRemoteMeshArtifact() {
98
+ const { imsOrgCode, projectId, workspaceId, workspaceName } = await initSdk({});
73
99
 
74
- const { args, flags } = await this.parse(RunCommand);
75
- const secretsFilePath = await flags.secrets;
100
+ // Get the mesh identifier for the workspace
101
+ let meshId;
102
+ try {
103
+ meshId = await getMeshId(imsOrgCode, projectId, workspaceId, workspaceName);
104
+ } catch (err) {
105
+ throw new Error(
106
+ `Unable to get mesh ID. Please check the details and try again. RequestId: ${global.requestId}`,
107
+ );
108
+ }
76
109
 
77
- //Initialize the meshId based on
78
- let meshId = null;
110
+ try {
111
+ await getMeshArtifact(imsOrgCode, projectId, workspaceId, workspaceName, meshId);
112
+ } catch (err) {
113
+ throw new Error(
114
+ `Unable to retrieve mesh. Please check the details and try again. RequestId: ${global.requestId}`,
115
+ );
116
+ }
79
117
 
80
118
  try {
81
- //Ensure that current directory includes package.json
82
- if (fs.existsSync(path.join(process.cwd(), 'package.json'))) {
83
- //If select flag is present then getMeshId for the specified org
84
- if (flags.select) {
85
- const { imsOrgCode, projectId, workspaceId, workspaceName } = await initSdk({});
86
-
87
- try {
88
- meshId = await getMeshId(imsOrgCode, projectId, workspaceId, workspaceName);
89
- } catch (err) {
90
- throw new Error(
91
- `Unable to get mesh ID. Please check the details and try again. RequestId: ${global.requestId}`,
92
- );
93
- }
94
-
95
- try {
96
- await getMeshArtifact(imsOrgCode, projectId, workspaceId, workspaceName, meshId);
97
- } catch (err) {
98
- throw new Error(
99
- `Unable to retrieve mesh. Please check the details and try again. RequestId: ${global.requestId}`,
100
- );
101
- }
102
-
103
- try {
104
- await setUpTenantFiles(meshId);
105
- } catch (err) {
106
- throw new Error('Failed to install downloaded mesh');
107
- }
108
-
109
- this.log('Successfully downloaded mesh');
110
- } else {
111
- if (!args.file) {
112
- throw new Error('Missing file path. Run aio api-mesh run --help for more info.');
113
- }
114
-
115
- const envFilePath = await flags.env;
116
-
117
- //Read the mesh input file
118
- let inputMeshData = await readFileContents(args.file, this, 'mesh');
119
- let data;
120
-
121
- if (checkPlaceholders(inputMeshData)) {
122
- this.log(
123
- 'The provided mesh contains placeholders. Starting mesh interpolation process.',
124
- );
125
- data = await validateAndInterpolateMesh(inputMeshData, envFilePath, this);
126
- } else {
127
- try {
128
- data = JSON.parse(inputMeshData);
129
- } catch (err) {
130
- this.log(err.message);
131
- throw new Error(
132
- 'Input mesh file is not a valid JSON. Please check the file provided.',
133
- );
134
- }
135
- }
136
-
137
- let filesList = [];
138
-
139
- try {
140
- filesList = getFilesInMeshConfig(data, args.file);
141
- } catch (err) {
142
- this.log(err.message);
143
- throw new Error('Input mesh config is not valid.');
144
- }
145
-
146
- // if local files are present, import them in files array in meshConfig
147
- if (filesList.length) {
148
- try {
149
- // minification of js will not be done for run command if debugging is enabled
150
- data = await importFiles(
151
- data,
152
- filesList,
153
- args.file,
154
- flags.autoConfirmAction,
155
- !flags.debug,
156
- );
157
- } catch (err) {
158
- this.log(err.message);
159
- throw new Error(
160
- 'Unable to import the files in the mesh config. Please check the file and try again.',
161
- );
162
- }
163
- }
164
-
165
- //Generating unique mesh id
166
- meshId = 'testMesh';
167
-
168
- await validateMesh(data.meshConfig);
169
- await buildMesh(meshId, data.meshConfig);
170
- await compileMesh(meshId);
171
- }
172
- let portNo;
173
- //secrets management
174
- if (secretsFilePath) {
175
- try {
176
- await validateSecretsFile(secretsFilePath);
177
- const stringifiedSecrets = await interpolateSecrets(secretsFilePath, this);
178
- await writeSecretsFile(stringifiedSecrets, meshId);
179
- } catch (error) {
180
- this.log(error.message);
181
- this.error('Unable to import secrets. Please check the file and try again.');
182
- }
183
- }
184
-
185
- await this.copyMeshContent(meshId);
186
-
187
- //To set the port number using the environment file
188
- if (process.env.PORT !== undefined) {
189
- if (isNaN(process.env.PORT) || !Number.isInteger(parseInt(process.env.PORT))) {
190
- throw new Error('PORT value in the .env file is not a valid integer');
191
- }
192
-
193
- portNo = process.env.PORT;
194
- }
195
-
196
- //To set the port number as the provided value in the command
197
- if (flags.port !== undefined) {
198
- portNo = flags.port;
199
- }
200
-
201
- //To set the default port to 5000
202
- if (!portNo) {
203
- portNo = 5000;
204
- }
205
- this.log(`Starting server on port : ${portNo}`);
206
- await runServer(portNo);
207
- this.log(`Server is running on http://localhost:${portNo}/graphql`);
208
- } else {
119
+ await setUpTenantFiles(meshId);
120
+ } catch (err) {
121
+ throw new Error('Failed to install downloaded mesh');
122
+ }
123
+
124
+ // Resolve relative sources in built mesh for local development
125
+ const builtMeshTenantDir = getBuiltMeshTenantDirectory(meshId);
126
+ await resolveRelativeSources(builtMeshTenantDir);
127
+
128
+ this.log('Successfully downloaded mesh');
129
+
130
+ return meshId;
131
+ }
132
+
133
+ /**
134
+ * Handle local mesh configuration
135
+ * @param args {unknown} Arguments
136
+ * @param flags {unknown} Flags
137
+ * @returns {Promise<string>}
138
+ * @throws {Error} when failure building local mesh artifact
139
+ */
140
+ async handleLocalMeshConfig(args, flags) {
141
+ if (!args.file) {
142
+ throw new Error('Missing file path. Run aio api-mesh run --help for more info.');
143
+ }
144
+
145
+ const envFilePath = await flags.env;
146
+
147
+ //Read the mesh input file
148
+ let inputMeshData = await readFileContents(args.file, this, 'mesh');
149
+ let data;
150
+
151
+ // TODO: Should we check for secrets in use when no secrets file provided?
152
+ if (checkPlaceholders(inputMeshData)) {
153
+ this.log('The provided mesh contains placeholders. Starting mesh interpolation process.');
154
+ data = await validateAndInterpolateMesh(inputMeshData, envFilePath, this);
155
+ } else {
156
+ try {
157
+ data = JSON.parse(inputMeshData);
158
+ } catch (err) {
159
+ this.log(err.message);
160
+ throw new Error('Input mesh file is not a valid JSON. Please check the file provided.');
161
+ }
162
+ }
163
+
164
+ let filesList = [];
165
+
166
+ try {
167
+ filesList = getFilesInMeshConfig(data, args.file);
168
+ } catch (err) {
169
+ this.log(err.message);
170
+ throw new Error('Input mesh config is not valid.');
171
+ }
172
+
173
+ // if local files are present, import them in files array in meshConfig
174
+ let localFileOverrides;
175
+ if (filesList.length) {
176
+ try {
177
+ // minification of js will not be done for run command if debugging is enabled
178
+ ({ data, localFileOverrides } = await importFiles(
179
+ data,
180
+ filesList,
181
+ args.file,
182
+ flags.autoConfirmAction,
183
+ !flags.debug,
184
+ ));
185
+ } catch (err) {
186
+ this.log(err.message);
209
187
  throw new Error(
210
- '`aio api-mesh run` cannot be executed because there is no package.json file in the current directory. Use `aio api-mesh init` to set up a package.',
188
+ 'Unable to import the files in the mesh config. Please check the file and try again.',
211
189
  );
212
190
  }
213
- } catch (error) {
214
- this.error(error.message);
215
191
  }
192
+ // Empty mesh-artifact directory
193
+ safeDelete(BUILT_MESH_ARTIFACT_DIRECTORY);
194
+
195
+ //Generating unique mesh id
196
+ const meshId = 'testMesh';
197
+ await validateMesh(data.meshConfig);
198
+ await buildMesh(meshId, data.meshConfig);
199
+ await compileMesh(meshId);
200
+
201
+ // Resolve relative sources in built mesh for local development
202
+ const builtMeshTenantDir = getBuiltMeshTenantDirectory(meshId);
203
+ await resolveRelativeSources(builtMeshTenantDir);
204
+ await resolveOriginalSources(builtMeshTenantDir, localFileOverrides);
205
+
206
+ return meshId;
216
207
  }
217
208
 
218
- async copyMeshContent(meshId) {
219
- // Remove mesh artifact directory if exists
220
- if (fs.existsSync('.mesh')) {
221
- fs.rmSync('.mesh', { recursive: true });
209
+ updateFlagsFromEnv(flags) {
210
+ if (process.env.PORT !== undefined) {
211
+ flags.port = this.parseInt(
212
+ process.env.PORT,
213
+ 'PORT value in the .env file is not a valid integer',
214
+ );
215
+ }
216
+ if (process.env.INSPECT_PORT !== undefined) {
217
+ flags.inspectPort = this.parseInt(
218
+ process.env.INSPECT_PORT,
219
+ 'INSPECT_PORT value in the .env file is not a valid integer',
220
+ );
222
221
  }
223
- // Move built mesh artifact to expect directory
224
- fs.renameSync(`mesh-artifact/${meshId}`, '.mesh');
225
- // Remove tenant files directory if exists
226
- if (fs.existsSync('tenantFiles')) {
227
- fs.rmSync('tenantFiles', { recursive: true });
222
+ }
223
+
224
+ /**
225
+ * Parse integer from string
226
+ * @param value {string} String value
227
+ * @param errorMessage {string?} Optional error message when parsing fails
228
+ * @returns {number}
229
+ * @throws {Error} when value is not a valid integer
230
+ */
231
+ parseInt(value, errorMessage) {
232
+ const int = parseInt(value);
233
+ if (isNaN(int) || !Number.isInteger(int)) {
234
+ throw new Error(errorMessage || `Value is not a valid integer`);
228
235
  }
229
- // Move built tenant files if exists
230
- if (fs.existsSync('mesh-artifact/tenantFiles')) {
231
- // Tenant files included in the bundle for runtime/dynamic imports
232
- fs.cpSync('mesh-artifact/tenantFiles', '.mesh/tenantFiles', { recursive: true });
233
- fs.renameSync('mesh-artifact/tenantFiles', 'tenantFiles');
234
- // Tenant files used at worker build time
235
- fs.cpSync('tenantFiles', `${__dirname}/../../../tenantFiles`, { recursive: true });
236
+ return int;
237
+ }
238
+
239
+ /**
240
+ * Handle secrets feature
241
+ * @param secretsFilePath {string} File path to secrets
242
+ * @param meshId {string} Mesh identifier
243
+ * @returns {Promise<void>}
244
+ */
245
+ async handleSecretsFeature(secretsFilePath, meshId) {
246
+ if (secretsFilePath) {
247
+ try {
248
+ await validateSecretsFile(secretsFilePath);
249
+ const stringifiedSecrets = await interpolateSecrets(secretsFilePath, this);
250
+ await writeSecretsFile(stringifiedSecrets, meshId);
251
+ } catch (error) {
252
+ this.log(error.message);
253
+ this.error('Unable to import secrets. Please check the file and try again.');
254
+ }
236
255
  }
256
+ }
237
257
 
238
- await fixPlugins('.mesh/index.js');
258
+ async run() {
259
+ try {
260
+ await initRequestId();
261
+ logger.info(`RequestId: ${global.requestId}`);
262
+
263
+ const { args, flags } = await this.parse(RunCommand);
264
+ await this.validateCwd();
265
+ this.updateFlagsFromEnv(flags);
266
+
267
+ // Use remote or local mesh artifact
268
+ let meshId;
269
+ if (flags.select) {
270
+ meshId = await this.handleRemoteMeshArtifact();
271
+ } else {
272
+ meshId = await this.handleLocalMeshConfig(args, flags);
273
+ }
239
274
 
240
- if (fs.existsSync(`${__dirname}/../../../.mesh`)) {
241
- fs.rmSync(`${__dirname}/../../../.mesh`, { recursive: true });
275
+ //secrets management
276
+ const secretsFilePath = await flags.secrets;
277
+ if (secretsFilePath) {
278
+ await this.handleSecretsFeature(secretsFilePath, meshId);
279
+ }
280
+
281
+ const builtMeshTenantDir = getBuiltMeshTenantDirectory(meshId);
282
+ await copyBuiltMeshToPackage(builtMeshTenantDir);
283
+
284
+ start(this, flags.port, flags.debug, flags.inspectPort);
285
+ if (flags.debug) {
286
+ this.log(`Debugging enabled on inspect port: ${flags.inspectPort}`);
287
+ }
288
+ } catch (error) {
289
+ this.error(error.message);
242
290
  }
243
- // At this time the bundle and build files must be copied out to the plugin directory
244
- fs.cpSync('.mesh', `${__dirname}/../../../.mesh`, { recursive: true });
245
291
  }
246
292
  }
247
293
 
@@ -106,7 +106,7 @@ class UpdateCommand extends Command {
106
106
  // if local files are present, import them in files array in meshConfig
107
107
  if (filesList.length) {
108
108
  try {
109
- data = await importFiles(data, filesList, args.file, flags.autoConfirmAction);
109
+ ({ data } = await importFiles(data, filesList, args.file, flags.autoConfirmAction));
110
110
  } catch (err) {
111
111
  this.log(err.message);
112
112
  this.error('Unable to import the files in the mesh config. Check the file and try again.');
@@ -119,8 +119,7 @@ class UpdateCommand extends Command {
119
119
  await validateSecretsFile(secretsFilePath);
120
120
  const secretsData = await interpolateSecrets(secretsFilePath, this);
121
121
  const publicKey = await getPublicEncryptionKey(imsOrgCode);
122
- const encryptedSecrets = await encryptSecrets(publicKey, secretsData);
123
- data.secrets = encryptedSecrets;
122
+ data.secrets = await encryptSecrets(publicKey, secretsData);
124
123
  } catch (err) {
125
124
  this.log(err.message);
126
125
  this.error('Unable to import secrets. Check the file and try again.');
@@ -10,21 +10,19 @@ OF ANY KIND, either express or implied. See the License for the specific languag
10
10
  governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- const IndexCommand = require('..');
13
+ const { Help, Command } = require('@oclif/core');
14
14
 
15
- test('exports', async () => {
16
- expect(typeof IndexCommand).toEqual('function');
17
- });
15
+ /**
16
+ * API Mesh command. Defers to topic for help text.
17
+ */
18
+ class ApiMeshCommand extends Command {
19
+ async run() {
20
+ const help = new Help(this.config);
21
+ await help.showHelp(['api-mesh', '--help']);
22
+ }
23
+ }
18
24
 
19
- describe('command tests', () => {
20
- let command;
25
+ ApiMeshCommand.description = 'Create, run, test, and deploy API Mesh';
26
+ ApiMeshCommand.args = [];
21
27
 
22
- beforeEach(() => {
23
- command = new IndexCommand([]);
24
- });
25
-
26
- test('run', async () => {
27
- command.argv = [];
28
- await expect(command.run()).resolves.not.toThrowError();
29
- });
30
- });
28
+ module.exports = ApiMeshCommand;
package/src/helpers.js CHANGED
@@ -445,7 +445,7 @@ async function initRequestId() {
445
445
  * Function to run the CLI Y/N prompt to confirm the user's action
446
446
  *
447
447
  * @param {string} message
448
- * @returns boolean
448
+ * @returns Promise<boolean>
449
449
  */
450
450
  async function promptConfirm(message) {
451
451
  const prompt = inquirer.createPromptModule({ output: process.stderr });
@@ -505,7 +505,6 @@ async function promptSelect(message, choices) {
505
505
  * Function to run the CLI selectable list
506
506
  *
507
507
  * @param {string} message - prompt message
508
- * @param {object[]} choices - list of options
509
508
  * @returns {object[]} - selected options
510
509
  */
511
510
  async function promptInput(message) {
@@ -524,9 +523,11 @@ async function promptInput(message) {
524
523
  * Import the files in the files array in meshConfig
525
524
  *
526
525
  * @param data MeshConfig
527
- * @param filesList List of files in meshConfig
526
+ * @param filesListArray List of files in meshConfig
528
527
  * @param meshConfigName MeshConfigName
529
528
  * @param autoConfirmActionFlag The user won't be prompted any questions, if this flag is set
529
+ * @param shouldMinifyJS
530
+ * @returns Promise<{{ data, localFileOverrides: string[] }}>
530
531
  */
531
532
  async function importFiles(
532
533
  data,
@@ -589,29 +590,38 @@ async function importFiles(
589
590
  );
590
591
  }
591
592
 
593
+ // Result of override resolution
594
+ const localFileOverrides = {};
592
595
  for (let i = 0; i < overrideArr.length; i++) {
596
+ const fileName = overrideArr[i].fileName;
593
597
  shouldOverride = await promptConfirm(
594
- `Do you want to override the ${path.basename(overrideArr[i].fileName)} file?`,
598
+ `Do you want to override the ${path.basename(fileName)} file?`,
595
599
  );
596
600
 
597
601
  if (shouldOverride) {
598
602
  resultData = updateFilesArray(
599
603
  resultData,
600
- overrideArr[i].fileName,
604
+ fileName,
601
605
  meshConfigName,
602
606
  overrideArr[i].index,
603
607
  shouldMinifyJS,
604
608
  );
609
+ localFileOverrides[fileName] = true;
610
+ } else {
611
+ localFileOverrides[fileName] = false;
605
612
  }
606
613
  }
607
614
 
608
- return resultData;
615
+ return {
616
+ data: resultData,
617
+ localFileOverrides,
618
+ };
609
619
  }
610
620
 
611
621
  /**loads the pupa module dynamically and then interpolates the raw data from mesh file with object data
612
- * @param {data}
613
- * @param {obj}
614
- * @returns {object} having interpolationStatus, missingKeys and interpolatedMesh
622
+ * @param data
623
+ * @param obj
624
+ * @returns {object}
615
625
  */
616
626
 
617
627
  async function interpolateMesh(data, obj) {
@@ -845,7 +855,7 @@ async function processFileConfig(config) {
845
855
  * This function sets up the tenantFiles used in a particular mesh config
846
856
  * into the tenantFiles folder
847
857
  *
848
- * @param config
858
+ * @param meshId
849
859
  */
850
860
  async function setUpTenantFiles(meshId) {
851
861
  if (fs.existsSync(path.resolve(process.cwd(), 'mesh-artifact', meshId, 'files.json'))) {
@@ -1064,7 +1064,7 @@ const getMeshDeployments = async (organizationCode, projectId, workspaceId, mesh
1064
1064
  * As a result, we provide the publicKey used for secrets encryption.
1065
1065
  * The near-term goal is to stop using Dev Console as a proxy for all routes.
1066
1066
  * @param organizationCode
1067
- * @returns string
1067
+ * @returns Promise<string>
1068
1068
  */
1069
1069
  const getPublicEncryptionKey = async organizationCode => {
1070
1070
  const { accessToken } = await getDevConsoleConfig();