@adobe/aio-cli-plugin-api-mesh 2.0.0 → 2.2.0-beta.1
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.
- package/oclif.manifest.json +1 -1
- package/package.json +5 -3
- package/src/commands/__fixtures__/env_invalid +8 -0
- package/src/commands/__fixtures__/env_valid +3 -0
- package/src/commands/__fixtures__/files/requestParams.json +3 -0
- package/src/commands/__fixtures__/openapi-schema.json +4 -0
- package/src/commands/__fixtures__/requestParams.json +3 -0
- package/src/commands/__fixtures__/sample_fully_qualified_mesh.json +29 -0
- package/src/commands/__fixtures__/sample_invalid_mesh.txt +17 -0
- package/src/commands/__fixtures__/sample_mesh_files.json +23 -0
- package/src/commands/__fixtures__/sample_mesh_invalid_file_content.json +14 -0
- package/src/commands/__fixtures__/sample_mesh_invalid_file_name.json +27 -0
- package/src/commands/__fixtures__/sample_mesh_invalid_paths.json +23 -0
- package/src/commands/__fixtures__/sample_mesh_invalid_type.json +27 -0
- package/src/commands/__fixtures__/sample_mesh_mismatching_path.json +29 -0
- package/src/commands/__fixtures__/sample_mesh_outside_workspace_dir.json +23 -0
- package/src/commands/__fixtures__/sample_mesh_path_from_home.json +14 -0
- package/src/commands/__fixtures__/sample_mesh_subdirectory.json +23 -0
- package/src/commands/__fixtures__/sample_mesh_with_files_array.json +29 -0
- package/src/commands/__fixtures__/sample_mesh_with_placeholder +17 -0
- package/src/commands/api-mesh/__tests__/create.test.js +1334 -140
- package/src/commands/api-mesh/__tests__/delete.test.js +3 -3
- package/src/commands/api-mesh/__tests__/init.test.js +390 -0
- package/src/commands/api-mesh/__tests__/update.test.js +524 -109
- package/src/commands/api-mesh/create.js +47 -14
- package/src/commands/api-mesh/init.js +168 -0
- package/src/commands/api-mesh/update.js +49 -17
- package/src/helpers.js +254 -3
- package/src/lib/devConsole.js +1 -2
- package/src/templates/gitignore +1 -0
- package/src/templates/package.json +38 -0
- package/src/utils.js +337 -33
|
@@ -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
|
+
}
|
package/src/utils.js
CHANGED
|
@@ -1,40 +1,22 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const logger = require('../src/classes/logger');
|
|
4
|
+
const { Flags } = require('@oclif/core');
|
|
5
|
+
const { readFile } = require('fs/promises');
|
|
6
|
+
const { interpolateMesh } = require('./helpers');
|
|
7
|
+
const dotenv = require('dotenv');
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
|
-
*
|
|
3
|
-
* If the path evaluates to false, the default string is returned.
|
|
4
|
-
*
|
|
5
|
-
* @param {object} obj
|
|
6
|
-
* @param {Array<string>} path
|
|
7
|
-
* @param {string} defaultString
|
|
8
|
-
* @returns {string}
|
|
10
|
+
* @returns returns the root directory of the project
|
|
9
11
|
*/
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// For each item in the path, dig into the object
|
|
16
|
-
for (let i = 0; i < path.length; i++) {
|
|
17
|
-
// If the item isn't found, return the default (or null)
|
|
18
|
-
if (!current[path[i]]) return defaultString;
|
|
19
|
-
|
|
20
|
-
// Otherwise, update the current value
|
|
21
|
-
current = current[path[i]];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (typeof current === 'string') {
|
|
25
|
-
return current;
|
|
26
|
-
} else if (typeof current === 'object') {
|
|
27
|
-
return JSON.stringify(current, null, 2);
|
|
28
|
-
} else {
|
|
29
|
-
return defaultString;
|
|
30
|
-
}
|
|
31
|
-
} catch (error) {
|
|
32
|
-
return defaultString;
|
|
12
|
+
function getAppRootDir() {
|
|
13
|
+
let currentDir = __dirname;
|
|
14
|
+
while (!fs.existsSync(path.join(currentDir, 'package.json'))) {
|
|
15
|
+
currentDir = path.join(currentDir, '..');
|
|
33
16
|
}
|
|
17
|
+
return currentDir;
|
|
34
18
|
}
|
|
35
19
|
|
|
36
|
-
const { Flags } = require('@oclif/core');
|
|
37
|
-
|
|
38
20
|
const ignoreCacheFlag = Flags.boolean({
|
|
39
21
|
char: 'i',
|
|
40
22
|
description: 'Ignore cache and force manual org -> project -> workspace selection',
|
|
@@ -53,9 +35,331 @@ const jsonFlag = Flags.boolean({
|
|
|
53
35
|
default: false,
|
|
54
36
|
});
|
|
55
37
|
|
|
38
|
+
const envFileFlag = Flags.string({
|
|
39
|
+
char: 'e',
|
|
40
|
+
description: 'Path to env file',
|
|
41
|
+
default: '.env',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse the meshConfig and get the list of (local) files to be imported
|
|
46
|
+
*
|
|
47
|
+
* @param data MeshConfig
|
|
48
|
+
* @param meshConfigName MeshConfig
|
|
49
|
+
* @returns files[] files present in meshConfig
|
|
50
|
+
*/
|
|
51
|
+
function getFilesInMeshConfig(data, meshConfigName) {
|
|
52
|
+
//ignore if the file names start with http or https
|
|
53
|
+
const fileURLRegex = /^(http|s:\/\/)/;
|
|
54
|
+
|
|
55
|
+
let filesList = [];
|
|
56
|
+
|
|
57
|
+
data.meshConfig.sources.forEach(source => {
|
|
58
|
+
// JSONSchema handler
|
|
59
|
+
source.handler.JsonSchema?.operations.forEach(operation => {
|
|
60
|
+
if (operation.requestSchema && !fileURLRegex.test(operation.requestSchema)) {
|
|
61
|
+
filesList.push(operation.requestSchema);
|
|
62
|
+
}
|
|
63
|
+
if (operation.responseSchema && !fileURLRegex.test(operation.responseSchema)) {
|
|
64
|
+
filesList.push(operation.responseSchema);
|
|
65
|
+
}
|
|
66
|
+
if (operation.requestSample && !fileURLRegex.test(operation.requestSample)) {
|
|
67
|
+
filesList.push(operation.requestSample);
|
|
68
|
+
}
|
|
69
|
+
if (operation.responseSample && !fileURLRegex.test(operation.responseSample)) {
|
|
70
|
+
filesList.push(operation.responseSample);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// OpenAPI handler
|
|
75
|
+
if (source.handler.openapi && !fileURLRegex.test(source.handler.openapi.source)) {
|
|
76
|
+
filesList.push(source.handler.openapi.source);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Additional Resolvers
|
|
81
|
+
data.meshConfig.additionalResolvers?.forEach(additionalResolver => {
|
|
82
|
+
if (!fileURLRegex.test(additionalResolver)) {
|
|
83
|
+
filesList.push(additionalResolver);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ReplaceField Transform - source level
|
|
88
|
+
data.meshConfig.sources.transforms?.forEach(transform => {
|
|
89
|
+
transform.replaceField?.replacements.forEach(replacement => {
|
|
90
|
+
if (replacement.composer && !fileURLRegex.test(replacement.composer)) {
|
|
91
|
+
filesList.push(replacement.composer);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ReplaceField Transform - mesh level
|
|
97
|
+
data.meshConfig.transforms?.forEach(transform => {
|
|
98
|
+
transform.replaceField?.replacements.forEach(replacement => {
|
|
99
|
+
if (replacement.composer && !fileURLRegex.test(replacement.composer)) {
|
|
100
|
+
filesList.push(replacement.composer);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (filesList.length) {
|
|
107
|
+
checkFilesAreUnderMeshDirectory(filesList, meshConfigName);
|
|
108
|
+
validateFileType(filesList);
|
|
109
|
+
validateFileName(filesList, data);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
logger.error(err.message);
|
|
113
|
+
throw new Error(err.message);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return filesList;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Checks if files are in the same directory or subdirectories of mesh
|
|
121
|
+
*
|
|
122
|
+
* @param data MeshConfig
|
|
123
|
+
* @param meshConfigName MeshConfig
|
|
124
|
+
*/
|
|
125
|
+
function checkFilesAreUnderMeshDirectory(filesList, meshConfigName) {
|
|
126
|
+
//handle files that are outside to the directory and subdirectories of meshConfig
|
|
127
|
+
let invalidPaths = [];
|
|
128
|
+
for (let i = 0; i < filesList.length; i++) {
|
|
129
|
+
if (
|
|
130
|
+
!path
|
|
131
|
+
.resolve(path.dirname(meshConfigName), filesList[i])
|
|
132
|
+
.includes(path.resolve(path.dirname(meshConfigName))) ||
|
|
133
|
+
filesList[i].includes('~')
|
|
134
|
+
) {
|
|
135
|
+
invalidPaths.push(path.basename(filesList[i]));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
filesOutsideRootDir(invalidPaths);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Error out if the files are outside the mesh directory
|
|
144
|
+
*
|
|
145
|
+
* @param invalidPaths Array
|
|
146
|
+
*/
|
|
147
|
+
function filesOutsideRootDir(invalidPaths) {
|
|
148
|
+
if (invalidPaths.length) {
|
|
149
|
+
throw new Error(`File(s): ${invalidPaths.join(', ')} is outside the mesh directory.`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if there are any placeholders in the input mesh file
|
|
155
|
+
* The below regular expressions are part of pupa string interpolation
|
|
156
|
+
* doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi
|
|
157
|
+
* braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi
|
|
158
|
+
* The above regex has been enhanced to include prefix '.env'
|
|
159
|
+
* @param {string} mesh
|
|
160
|
+
* @returns {boolean}
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
function checkPlaceholders(mesh) {
|
|
164
|
+
const doubleBraceRegex = /{{env\.(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi;
|
|
165
|
+
const braceRegex = /{env\.(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi;
|
|
166
|
+
const foundDoubleBraceRegex = mesh.match(doubleBraceRegex);
|
|
167
|
+
const foundSingleBraceRegex = mesh.match(braceRegex);
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
typeof foundDoubleBraceRegex === 'object' &&
|
|
171
|
+
foundDoubleBraceRegex === null &&
|
|
172
|
+
typeof foundSingleBraceRegex === 'object' &&
|
|
173
|
+
foundSingleBraceRegex === null
|
|
174
|
+
) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Read the file contents. If there is any error, report it to the user.
|
|
182
|
+
* @param {string} file
|
|
183
|
+
* @param {object} command
|
|
184
|
+
* @param {string} filetype
|
|
185
|
+
* @returns {string}
|
|
186
|
+
*/
|
|
187
|
+
async function readFileContents(file, command, filetype) {
|
|
188
|
+
try {
|
|
189
|
+
return await readFile(file, 'utf8');
|
|
190
|
+
} catch (error) {
|
|
191
|
+
command.log(error.message);
|
|
192
|
+
if (filetype === 'mesh') {
|
|
193
|
+
command.error(
|
|
194
|
+
`Unable to read the mesh configuration file provided. Please check the file and try again.`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
command.error(`Unable to read the file ${file}. Please check the file and try again.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if the files are of valid types .js, .json
|
|
203
|
+
*
|
|
204
|
+
* @param filesList List of files in mesh config
|
|
205
|
+
*/
|
|
206
|
+
function validateFileType(filesList) {
|
|
207
|
+
const filesWithInvalidTypes = [];
|
|
208
|
+
|
|
209
|
+
filesList.forEach(file => {
|
|
210
|
+
const extension = path.extname(file);
|
|
211
|
+
const isValidFileType = ['.js', '.json'].includes(extension);
|
|
212
|
+
|
|
213
|
+
if (!isValidFileType) {
|
|
214
|
+
filesWithInvalidTypes.push(path.basename(file));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (filesWithInvalidTypes.length) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Mesh files must be JavaScript or JSON. Other file types are not supported. The following file(s) are invalid: ${filesWithInvalidTypes}.`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Validate the filenames
|
|
227
|
+
*
|
|
228
|
+
* @param filesList Files in sources, tranforms or additionalResolvers in the meshConfig
|
|
229
|
+
* @param data MeshConfig
|
|
230
|
+
*/
|
|
231
|
+
function validateFileName(filesList, data) {
|
|
232
|
+
const filesWithInvalidNames = [];
|
|
233
|
+
|
|
234
|
+
// Check if the file names are less than 25 characters
|
|
235
|
+
filesList.forEach(file => {
|
|
236
|
+
const fileName = path.basename(file);
|
|
237
|
+
if (fileName.length > 25) {
|
|
238
|
+
filesWithInvalidNames.push(fileName);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (filesWithInvalidNames.length) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Mesh file names must be less than 25 characters. The following file(s) are invalid: ${filesWithInvalidNames}.`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// check if the the filePaths in the files array match
|
|
249
|
+
// the fileNames in sources, transforms or additionalResolvers
|
|
250
|
+
|
|
251
|
+
if (data.meshConfig.files) {
|
|
252
|
+
for (let i = 0; i < data.meshConfig.files.length; i++) {
|
|
253
|
+
if (filesList.indexOf(data.meshConfig.files[i].path) == -1) {
|
|
254
|
+
throw new Error(`Please make sure the file names are matching in meshConfig.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**validates the environment file content
|
|
261
|
+
* @param {string} envContent
|
|
262
|
+
* @returns {object} containing the status of validation
|
|
263
|
+
* If validation is failed then the error property including the formatting errors is returned.
|
|
264
|
+
*/
|
|
265
|
+
function validateEnvFileFormat(envContent) {
|
|
266
|
+
//Key should start with a underscore or an alphabet followed by underscore/alphanumeric characters
|
|
267
|
+
const envKeyRegex = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
|
|
268
|
+
|
|
269
|
+
const envValueRegex = /^(?:"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'|[^'"\s])+$/;
|
|
270
|
+
|
|
271
|
+
/*
|
|
272
|
+
The above regex matches one or more of below :
|
|
273
|
+
(?:"(?:\\.|[^\\"])*"|'(?:\\.|[^\\'])*'|[^'"\s])
|
|
274
|
+
which is
|
|
275
|
+
1. ?:"(?:\\.|[^\\"])*" : Non capturing group starts and ends with '"'
|
|
276
|
+
*/
|
|
277
|
+
const envDict = {};
|
|
278
|
+
const lines = envContent.split(/\r?\n/);
|
|
279
|
+
const errors = [];
|
|
280
|
+
|
|
281
|
+
for (let index = 0; index < lines.length; index++) {
|
|
282
|
+
const line = lines[index];
|
|
283
|
+
const trimmedLine = line.trim();
|
|
284
|
+
if (trimmedLine.startsWith('#') || trimmedLine === '') {
|
|
285
|
+
// ignore comment or empty lines
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!trimmedLine.includes('=')) {
|
|
290
|
+
errors.push(`Invalid format << ${trimmedLine} >> on line ${index + 1}`);
|
|
291
|
+
} else {
|
|
292
|
+
const [key, value] = trimmedLine.split('=', 2);
|
|
293
|
+
if (!envKeyRegex.test(key) || !envValueRegex.test(value)) {
|
|
294
|
+
// invalid format: key or value does not match regex
|
|
295
|
+
errors.push(`Invalid format for key/value << ${trimmedLine} >> on line ${index + 1}`);
|
|
296
|
+
}
|
|
297
|
+
if (key in envDict) {
|
|
298
|
+
// duplicate key found
|
|
299
|
+
errors.push(`Duplicate key << ${key} >> on line ${index + 1}`);
|
|
300
|
+
}
|
|
301
|
+
envDict[key] = value;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (errors.length) {
|
|
305
|
+
return {
|
|
306
|
+
valid: false,
|
|
307
|
+
error: errors.toString(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
valid: true,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Read the environment file, checks for validation status and interpolate mesh
|
|
317
|
+
* @param {string} inputMeshData
|
|
318
|
+
* @param {string} envFilePath
|
|
319
|
+
* @param {object} command
|
|
320
|
+
* @returns {string}
|
|
321
|
+
*/
|
|
322
|
+
async function validateAndInterpolateMesh(inputMeshData, envFilePath, command) {
|
|
323
|
+
//Read the environment file
|
|
324
|
+
const envFileContent = await readFileContents(envFilePath, command, 'env');
|
|
325
|
+
|
|
326
|
+
//Validate the environment file
|
|
327
|
+
const envFileValidity = validateEnvFileFormat(envFileContent);
|
|
328
|
+
if (envFileValidity.valid) {
|
|
329
|
+
//load env file using dotenv and add 'env' as the root property in the object
|
|
330
|
+
const envObj = { env: dotenv.config({ path: envFilePath }).parsed };
|
|
331
|
+
const { interpolationStatus, missingKeys, interpolatedMeshData } = await interpolateMesh(
|
|
332
|
+
inputMeshData,
|
|
333
|
+
envObj,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
if (interpolationStatus == 'failed') {
|
|
337
|
+
command.error(
|
|
338
|
+
'The mesh file cannot be interpolated due to missing keys : ' + missingKeys.join(' , '),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
return JSON.parse(interpolatedMeshData);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
command.log(err.message);
|
|
346
|
+
command.log(interpolatedMeshData);
|
|
347
|
+
command.error('Interpolated mesh is not a valid JSON. Please check the generated json file.');
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
command.error(`Issue in ${envFilePath} file - ` + envFileValidity.error);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
56
354
|
module.exports = {
|
|
57
|
-
objToString,
|
|
58
355
|
ignoreCacheFlag,
|
|
59
356
|
autoConfirmActionFlag,
|
|
60
357
|
jsonFlag,
|
|
358
|
+
getFilesInMeshConfig,
|
|
359
|
+
envFileFlag,
|
|
360
|
+
checkPlaceholders,
|
|
361
|
+
readFileContents,
|
|
362
|
+
validateEnvFileFormat,
|
|
363
|
+
validateAndInterpolateMesh,
|
|
364
|
+
getAppRootDir,
|
|
61
365
|
};
|