@adobe/aio-cli-plugin-app 10.0.3 → 10.0.4

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.
@@ -0,0 +1,631 @@
1
+ /*
2
+ Copyright 2020 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ Unless required by applicable law or agreed to in writing, software distributed under
7
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8
+ OF ANY KIND, either express or implied. See the License for the specific language
9
+ governing permissions and limitations under the License.
10
+ */
11
+
12
+ const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:import', { provider: 'debug' })
13
+ const config = require('@adobe/aio-lib-core-config')
14
+ const { defaultOwApihost } = require('./defaults')
15
+ const path = require('path')
16
+ const fs = require('fs-extra')
17
+ const inquirer = require('inquirer')
18
+ const yaml = require('js-yaml')
19
+ const hjson = require('hjson')
20
+ const Ajv = require('ajv')
21
+ const { EOL } = require('os')
22
+
23
+ const AIO_FILE = '.aio'
24
+ const ENV_FILE = '.env'
25
+ const AIO_ENV_PREFIX = 'AIO_'
26
+ const AIO_ENV_SEPARATOR = '_'
27
+ const FILE_FORMAT_ENV = 'env'
28
+ const FILE_FORMAT_JSON = 'json'
29
+ const CONSOLE_CONFIG_KEY = 'console'
30
+
31
+ // make sure to prompt to stderr
32
+ // Note: if this get's turned into a lib make sure to call
33
+ // this into an init/constructor as it might create mocking issues in jest
34
+ const prompt = inquirer.createPromptModule({ output: process.stderr })
35
+
36
+ /**
37
+ * Validate the config json
38
+ *
39
+ * @param {object} configJson the json to validate
40
+ * @returns {object} with keys valid (boolean) and errors (object). errors is null if no errors
41
+ */
42
+ function validateConfig (configJson) {
43
+ /* eslint-disable-next-line node/no-unpublished-require */
44
+ const schema = require('../../schema/config.schema.json')
45
+ const ajv = new Ajv({ allErrors: true })
46
+ const validate = ajv.compile(schema)
47
+
48
+ return { valid: validate(configJson), errors: validate.errors }
49
+ }
50
+
51
+ /**
52
+ * Load a config file
53
+ *
54
+ * @param {string} fileOrBuffer the path to the config file or a Buffer
55
+ * @returns {object} object with properties `value` and `format`
56
+ */
57
+ function loadConfigFile (fileOrBuffer) {
58
+ let contents
59
+ if (typeof fileOrBuffer === 'string') {
60
+ contents = fs.readFileSync(fileOrBuffer, 'utf-8')
61
+ } else if (Buffer.isBuffer(fileOrBuffer)) {
62
+ contents = fileOrBuffer.toString('utf-8')
63
+ } else {
64
+ contents = ''
65
+ }
66
+
67
+ contents = contents.trim()
68
+
69
+ if (contents) {
70
+ if (contents[0] === '{') {
71
+ try {
72
+ return { values: hjson.parse(contents), format: 'json' }
73
+ } catch (e) {
74
+ throw new Error('Cannot parse json')
75
+ }
76
+ } else {
77
+ try {
78
+ return { values: yaml.load(contents, { json: true }), format: 'yaml' }
79
+ } catch (e) {
80
+ throw new Error('Cannot parse yaml')
81
+ }
82
+ }
83
+ }
84
+ return { values: {}, format: 'json' }
85
+ }
86
+
87
+ /**
88
+ * Load and validate a config file
89
+ *
90
+ * @param {string} fileOrBuffer the path to the config file or a Buffer
91
+ * @returns {object} object with properties `value` and `format`
92
+ */
93
+ function loadAndValidateConfigFile (fileOrBuffer) {
94
+ const res = loadConfigFile(fileOrBuffer)
95
+ const { valid: configIsValid, errors: configErrors } = validateConfig(res.values)
96
+ if (!configIsValid) {
97
+ const message = `Missing or invalid keys in config: ${JSON.stringify(configErrors, null, 2)}`
98
+ throw new Error(message)
99
+ }
100
+ return res
101
+ }
102
+
103
+ /**
104
+ * Writes default app config to .aio file
105
+ *
106
+ * @param {string} parentDir the parent folder to write the .aio file to
107
+ * @param {object} [flags] flags for file writing
108
+ * @param {boolean} [flags.overwrite=false] set to true to overwrite the existing .env file
109
+ * @param {boolean} [flags.merge=false] set to true to merge in the existing .env file (takes precedence over overwrite)
110
+ * @param {boolean} [flags.interactive=false] set to true to prompt the user for file overwrite
111
+ * @returns {Promise} promise from writeFile call
112
+ */
113
+ function writeDefaultAppConfig (parentDir, flags) {
114
+ // write app config to .aio file
115
+ const appConfig = {
116
+ app: {
117
+ actions: 'actions',
118
+ dist: 'dist',
119
+ web: 'web-src'
120
+ }
121
+ }
122
+ return writeAio(appConfig, parentDir, flags)
123
+ }
124
+
125
+ /**
126
+ * Pretty prints the json object as a string.
127
+ * Delimited by 2 spaces.
128
+ *
129
+ * @param {object} json the json to pretty print
130
+ * @returns {string} the transformed json as a string
131
+ */
132
+ function prettyPrintJson (json) {
133
+ return JSON.stringify(json, null, 2)
134
+ }
135
+
136
+ /**
137
+ * Confirmation prompt for overwriting, or merging a file if it already exists.
138
+ *
139
+ * @param {string} filePath the file to ovewrite
140
+ * @returns {object} ovewrite, merge, abort (properties, that are set to true if chosen)
141
+ */
142
+ async function checkFileConflict (filePath) {
143
+ if (fs.existsSync(filePath)) {
144
+ const answer = await prompt([
145
+ {
146
+ type: 'expand',
147
+ message: `The file ${filePath} already exists:`,
148
+ name: 'conflict',
149
+ choices: [
150
+ {
151
+ key: 'o',
152
+ name: 'Overwrite',
153
+ value: 'overwrite'
154
+ },
155
+ {
156
+ key: 'm',
157
+ name: 'Merge',
158
+ value: 'merge'
159
+ },
160
+ {
161
+ key: 'x',
162
+ name: 'Abort',
163
+ value: 'abort'
164
+ }
165
+ ]
166
+ }
167
+ ])
168
+
169
+ switch (answer.conflict) {
170
+ case 'overwrite':
171
+ return { overwrite: true }
172
+ case 'merge':
173
+ return { merge: true }
174
+ case 'abort':
175
+ return { abort: true }
176
+ default:
177
+ return {}
178
+ }
179
+ } else {
180
+ return {}
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Transform a json object to a flattened version. Any nesting is separated by the `separator` string.
186
+ * For example, if you have the `_` separator string, flattening this:
187
+ *
188
+ * {
189
+ * foo: {
190
+ * bar: 'a',
191
+ * baz: {
192
+ * faz: 'b'
193
+ * }
194
+ * }
195
+ * }
196
+ *
197
+ * const result = flattenObjectWithSeparator(json, {}, '', '_)
198
+ * The result would then be:
199
+ * {
200
+ * 'foo_bar': 'a',
201
+ * 'foo_baz_faz': 'b'
202
+ * }
203
+ *
204
+ * Any underscores in the object key are escaped with an underscore.
205
+ *
206
+ * @param {object} json the json object to transform
207
+ * @param {object} result the result object to initialize the function with
208
+ * @param {string} prefix the prefix to add to the final key
209
+ * @param {string} separator the separator string to separate the nested levels with
210
+ * @returns {object} the transformed json
211
+ */
212
+ function flattenObjectWithSeparator (json, result = {}, prefix = AIO_ENV_PREFIX, separator = AIO_ENV_SEPARATOR) {
213
+ Object
214
+ .keys(json)
215
+ .forEach(key => {
216
+ const _key = key.replace(/_/gi, '__') // replace any underscores in key with double underscores
217
+
218
+ if (Array.isArray(json[key])) {
219
+ result[`${prefix}${_key}`] = JSON.stringify(json[key])
220
+ return result
221
+ } else if (typeof (json[key]) === 'object') {
222
+ flattenObjectWithSeparator(json[key], result, `${prefix}${_key}${separator}`)
223
+ } else {
224
+ result[`${prefix}${_key}`] = json[key]
225
+ return result
226
+ }
227
+ })
228
+
229
+ return result
230
+ }
231
+
232
+ /**
233
+ * Split line from .env
234
+ *
235
+ * @param {string} line env line to split
236
+ * @returns {Array} tuple, first item is key, second item is value or null if it's a comment
237
+ */
238
+ function splitEnvLine (line) {
239
+ const trimmedLine = line.trim()
240
+ if (trimmedLine.startsWith('#')) { // skip comments
241
+ aioLogger.debug(`splitEnvLine - processing comment: ${line}`)
242
+ return [trimmedLine, undefined]
243
+ }
244
+
245
+ const items = line.split('=')
246
+ if (items.length >= 2) {
247
+ const key = items.shift().trim() // pop first element
248
+ const value = items.join('=').trimStart() // join the rest
249
+
250
+ return [key, value]
251
+ } else {
252
+ aioLogger.debug(`splitEnvLine - cannot process line: ${line}`)
253
+ }
254
+
255
+ return null
256
+ }
257
+
258
+ /**
259
+ * Merge .env data
260
+ * (we don't want to go through the .env to json conversion)
261
+ * Note that comments will not be preserved.
262
+ *
263
+ * @param {string} oldEnv existing env values
264
+ * @param {string} newEnv new env values (takes precedence)
265
+ * @returns {string} the merged env data
266
+ */
267
+ function mergeEnv (oldEnv, newEnv) {
268
+ aioLogger.debug(`mergeEnv - oldEnv: ${oldEnv}`)
269
+ aioLogger.debug(`mergeEnv - newEnv:${newEnv}`)
270
+
271
+ const result = {}
272
+ const NEWLINES = /\n|\r|\r\n/
273
+
274
+ aioLogger.debug(`mergeEnv - oldEnv:${oldEnv}`)
275
+ aioLogger.debug(`mergeEnv - newEnv:${newEnv}`)
276
+
277
+ const splitHelper = line => {
278
+ const tuple = splitEnvLine(line)
279
+ if (tuple) {
280
+ result[tuple[0]] = tuple[1]
281
+ }
282
+ }
283
+
284
+ oldEnv.split(NEWLINES).forEach(splitHelper)
285
+ newEnv.split(NEWLINES).forEach(splitHelper)
286
+
287
+ const mergedEnv = Object
288
+ .keys(result)
289
+ .map(key => result[key] !== undefined ? `${key}=${result[key]}` : key)
290
+ .join(EOL)
291
+ .concat(EOL) // add a new line
292
+ aioLogger.debug(`mergeEnv - mergedEnv:${mergedEnv}`)
293
+
294
+ return mergedEnv
295
+ }
296
+
297
+ /**
298
+ * Merge json data
299
+ *
300
+ * @param {string} oldData existing values
301
+ * @param {string} newData new values (takes precedence)
302
+ * @returns {object} the merged json
303
+ */
304
+ function mergeJson (oldData, newData) {
305
+ const { values: oldJson } = loadConfigFile(Buffer.from(oldData))
306
+ const { values: newJson } = loadConfigFile(Buffer.from(newData))
307
+
308
+ aioLogger.debug(`mergeJson - oldJson:${prettyPrintJson(oldJson)}`)
309
+ aioLogger.debug(`mergeJson - newJson:${prettyPrintJson(newJson)}`)
310
+
311
+ const mergedJson = prettyPrintJson({ ...oldJson, ...newJson })
312
+ aioLogger.debug(`mergeJson - mergedJson:${mergedJson}`)
313
+
314
+ return mergedJson
315
+ }
316
+
317
+ /**
318
+ * Merge .env or json data
319
+ *
320
+ * @param {string} oldData the data to merge to
321
+ * @param {string} newData the new data to merge from (these contents take precedence)
322
+ * @param {*} fileFormat the file format of the data (env, json)
323
+ * @returns {string | object} the merged env or json data
324
+ */
325
+ function mergeData (oldData, newData, fileFormat) {
326
+ aioLogger.debug(`mergeData - oldData: ${oldData}`)
327
+ aioLogger.debug(`mergeData - newData: ${newData}`)
328
+
329
+ if (fileFormat === FILE_FORMAT_ENV) {
330
+ return mergeEnv(oldData, newData)
331
+ } else { // FILE_FORMAT_JSON default
332
+ return mergeJson(oldData, newData)
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Writes the data to file.
338
+ * Checks for conflicts and gives options to overwrite, merge, or abort.
339
+ *
340
+ * @param {string} destination the file to write to
341
+ * @param {string} data the data to write to disk
342
+ * @param {object} [flags] flags for file writing
343
+ * @param {boolean} [flags.overwrite=false] set to true to overwrite the existing .env file
344
+ * @param {boolean} [flags.merge=false] set to true to merge in the existing .env file (takes precedence over overwrite)
345
+ * @param {boolean} [flags.interactive=false] set to true to prompt the user for file overwrite
346
+ * @param {boolean} [flags.fileFormat=json] set the file format to write (defaults to json)
347
+ * @returns {Promise} the writefile
348
+ */
349
+ async function writeFile (destination, data, flags = {}) {
350
+ const { overwrite = false, merge = false, fileFormat = FILE_FORMAT_JSON, interactive = false } = flags
351
+ aioLogger.debug(`writeFile - destination: ${destination} flags:${flags}`)
352
+ aioLogger.debug(`writeFile - data: ${data}`)
353
+
354
+ let answer = { overwrite, merge } // for non-interactive, get from the flags
355
+
356
+ if (interactive) {
357
+ answer = await checkFileConflict(destination)
358
+ aioLogger.debug(`writeFile - answer (interactive): ${JSON.stringify(answer)}`)
359
+ }
360
+
361
+ if (answer.abort) {
362
+ return
363
+ }
364
+
365
+ if (answer.merge) {
366
+ if (fs.existsSync(destination)) {
367
+ const oldData = fs.readFileSync(destination, 'utf-8')
368
+ data = mergeData(oldData, data, fileFormat)
369
+ }
370
+ }
371
+
372
+ return fs.writeFile(destination, data, {
373
+ flag: (answer.overwrite || answer.merge) ? 'w' : 'wx'
374
+ })
375
+ }
376
+
377
+ /**
378
+ * Writes the json object as AIO_ env vars to the .env file in the specified parent folder.
379
+ *
380
+ * @param {object} json the json object to transform and write to disk
381
+ * @param {string} parentFolder the parent folder to write the .env file to
382
+ * @param {object} [flags] flags for file writing
383
+ * @param {boolean} [flags.overwrite=false] set to true to overwrite the existing .env file
384
+ * @param {boolean} [flags.merge=false] set to true to merge in the existing .env file (takes precedence over overwrite)
385
+ * @param {boolean} [flags.interactive=false] set to true to prompt the user for file overwrite
386
+ * @param {object} [extraEnvVars={}] extra environment variables key/value pairs to add to the generated .env.
387
+ * Extra variables are treated as raw and won't be rewritten to comply with aio-lib-core-config
388
+ * @returns {Promise} promise from writeFile call
389
+ */
390
+ async function writeEnv (json, parentFolder, flags, extraEnvVars) {
391
+ aioLogger.debug(`writeEnv - json: ${JSON.stringify(json)} parentFolder:${parentFolder} flags:${flags} extraEnvVars:${extraEnvVars}`)
392
+
393
+ const destination = path.join(parentFolder, ENV_FILE)
394
+ aioLogger.debug(`writeEnv - destination: ${destination}`)
395
+
396
+ const resultObject = { ...flattenObjectWithSeparator(json), ...extraEnvVars }
397
+ aioLogger.debug(`convertJsonToEnv - flattened and separated json: ${prettyPrintJson(resultObject)}`)
398
+
399
+ const data = Object
400
+ .keys(resultObject)
401
+ .map(key => `${key}=${resultObject[key]}`)
402
+ .join(EOL)
403
+ .concat(EOL)
404
+ aioLogger.debug(`writeEnv - data:${data}`)
405
+
406
+ return writeFile(destination, data, { ...flags, fileFormat: FILE_FORMAT_ENV })
407
+ }
408
+
409
+ /**
410
+ * Writes the org, project, and workspace information to the global console config.
411
+ *
412
+ * @param {object} json the json object to write to the console config
413
+ */
414
+ async function writeConsoleConfig (json) {
415
+ aioLogger.debug(`writeConsoleConfig - json: ${JSON.stringify(json)}`)
416
+
417
+ const { project } = json
418
+ const { org, workspace } = project
419
+
420
+ const data = {
421
+ org: {
422
+ id: org.id,
423
+ name: org.name,
424
+ code: org.ims_org_id
425
+ },
426
+ project: {
427
+ name: project.name,
428
+ id: project.id,
429
+ title: project.title,
430
+ description: project.description,
431
+ org_id: org.id
432
+ },
433
+ workspace: {
434
+ id: workspace.id,
435
+ name: workspace.name
436
+ }
437
+ }
438
+
439
+ config.set(CONSOLE_CONFIG_KEY, data)
440
+ }
441
+
442
+ /**
443
+ * Writes the json object to the .aio file in the specified parent folder.
444
+ *
445
+ * @param {object} json the json object to write to disk
446
+ * @param {string} parentFolder the parent folder to write the .aio file to
447
+ * @param {object} [flags] flags for file writing
448
+ * @param {boolean} [flags.overwrite=false] set to true to overwrite the existing .env file
449
+ * @param {boolean} [flags.merge=false] set to true to merge in the existing .env file (takes precedence over overwrite)
450
+ * @param {boolean} [flags.interactive=false] set to true to prompt the user for file overwrite
451
+ * @returns {Promise} promise from writeFile call
452
+ */
453
+ async function writeAio (json, parentFolder, flags) {
454
+ aioLogger.debug(`writeAio - parentFolder:${parentFolder} flags:${flags}`)
455
+ aioLogger.debug(`writeAio - json: ${prettyPrintJson(json)}`)
456
+
457
+ const destination = path.join(parentFolder, AIO_FILE)
458
+ aioLogger.debug(`writeAio - destination: ${destination}`)
459
+
460
+ const data = prettyPrintJson(json)
461
+ return writeFile(destination, data, flags)
462
+ }
463
+
464
+ /**
465
+ * Transform runtime object value to what this plugin expects (single runtime namespace).
466
+ *
467
+ * @example
468
+ * from:
469
+ * {
470
+ * "namespaces": [
471
+ * {
472
+ * "name": "abc",
473
+ * "auth": "123"
474
+ * }
475
+ * ]
476
+ * }
477
+ * to:
478
+ * {
479
+ * "namespace": "abc",
480
+ * "auth": "123"
481
+ * }
482
+ *
483
+ * @param {object} runtime the runtime value to transform
484
+ * @returns {object} the transformed runtime object
485
+ * @private
486
+ */
487
+ function transformRuntime (runtime) {
488
+ const newRuntime = (runtime.namespaces.length > 0) ? runtime.namespaces[0] : {}
489
+ if (newRuntime.name) {
490
+ newRuntime.namespace = newRuntime.name
491
+ delete newRuntime.name
492
+ // apihost is not sent in console config
493
+ newRuntime.apihost = defaultOwApihost
494
+ }
495
+
496
+ return newRuntime
497
+ }
498
+
499
+ /**
500
+ * Transforms a credentials array to an object, to what this plugin expects.
501
+ * Enrich with ims_org_id if it is a jwt credential.
502
+ *
503
+ * @example
504
+ * from:
505
+ * [{
506
+ * "id": "17561142",
507
+ * "name": "Project Foo",
508
+ * "integration_type": "oauthweb",
509
+ * "oauth2": {
510
+ * "client_id": "XYXYXYXYXYXYXYXYX",
511
+ * "client_secret": "XYXYXYXYZZZZZZ",
512
+ * "redirect_uri": "https://test123"
513
+ * }
514
+ * }]
515
+ * to:
516
+ * {
517
+ * "Project Foo": {
518
+ * "client_id": "XYXYXYXYXYXYXYXYX",
519
+ * "client_secret": "XYXYXYXYZZZZZZ",
520
+ * "redirect_uri": "https://test123"
521
+ * }
522
+ * }
523
+ *
524
+ * @param {Array} credentials array from Downloadable File Format
525
+ * @param {string} imsOrgId the ims org id
526
+ * @returns {object} the Credentials object
527
+ * @private
528
+ */
529
+ function transformCredentials (credentials, imsOrgId) {
530
+ // find jwt credential
531
+ const credential = credentials.find(credential => typeof credential.jwt === 'object')
532
+
533
+ // enrich jwt credentials with ims org id
534
+ if (credential && credential.jwt && !credential.jwt.ims_org_id) {
535
+ aioLogger.debug('adding ims_org_id to ims.jwt config')
536
+ credential.jwt.ims_org_id = imsOrgId
537
+ }
538
+
539
+ return credentials.reduce((acc, credential) => {
540
+ // the json schema enforces for jwt OR oauth2 OR apiKey in a credential
541
+ const value = credential.oauth2 || credential.jwt || credential.api_key
542
+
543
+ const name = credential.name.replace(/ /gi, '_') // replace any spaces with underscores
544
+ acc[name] = value
545
+
546
+ return acc
547
+ }, {})
548
+ }
549
+
550
+ /**
551
+ * Trim the credentials array to only keep a reference to each integration credential.
552
+ * Replace spaces in the name with _ and lowercase the name
553
+ *
554
+ * @example
555
+ * from:
556
+ * [{
557
+ * "id": "17561142",
558
+ * "name": "Project Foo",
559
+ * "integration_type": "oauthweb",
560
+ * "oauth2": {
561
+ * "client_id": "XYXYXYXYXYXYXYXYX",
562
+ * "client_secret": "XYXYXYXYZZZZZZ",
563
+ * "redirect_uri": "https://test123"
564
+ * }
565
+ * }]
566
+ * to:
567
+ * [{
568
+ * "id": "17561142",
569
+ * "name": "project_foo",
570
+ * "integration_type": "oauthweb"
571
+ * }]
572
+ *
573
+ * @param {Array} credentials array from Downloadable File Format
574
+ * @returns {object} an array holding only the references to the credentials
575
+ * @private
576
+ */
577
+ function credentialsReferences (credentials) {
578
+ return credentials.map(c => ({ id: c.id, name: c.name.replace(/ /gi, '_'), integration_type: c.integration_type }))
579
+ }
580
+
581
+ /**
582
+ * Import a downloadable config and write to the appropriate .env (credentials) and .aio (non-credentials) files.
583
+ *
584
+ * @param {string} configFileOrBuffer the path to the config file to import or a buffer
585
+ * @param {string} [destinationFolder=the current working directory] the path to the folder to write the .env and .aio files to
586
+ * @param {object} [flags={}] flags for file writing
587
+ * @param {boolean} [flags.overwrite=false] set to true to overwrite the existing .env file
588
+ * @param {boolean} [flags.merge=false] set to true to merge in the existing .env file (takes precedence over overwrite)
589
+ * @param {object} [extraEnvVars={}] extra environment variables key/value pairs to add to the generated .env.
590
+ * Extra variables are treated as raw and won't be rewritten to comply with aio-lib-core-config
591
+ * @returns {Promise} promise from writeAio call
592
+ */
593
+ async function importConfigJson (configFileOrBuffer, destinationFolder = process.cwd(), flags = {}, extraEnvVars = {}) {
594
+ aioLogger.debug(`importConfigJson - configFileOrBuffer: ${configFileOrBuffer} destinationFolder:${destinationFolder} flags:${flags} extraEnvVars:${extraEnvVars}`)
595
+
596
+ const { values: config, format } = loadAndValidateConfigFile(configFileOrBuffer)
597
+
598
+ aioLogger.debug(`importConfigJson - format: ${format} config:${prettyPrintJson(config)} `)
599
+
600
+ const { runtime, credentials } = config.project.workspace.details
601
+
602
+ await writeEnv({
603
+ runtime: transformRuntime(runtime),
604
+ ims: { contexts: transformCredentials(credentials, config.project.org.ims_org_id) }
605
+ }, destinationFolder, flags, extraEnvVars)
606
+
607
+ // remove the credentials
608
+ delete config.project.workspace.details.runtime
609
+ // keep only a reference to the credentials in the aio config (hiding secrets)
610
+ config.project.workspace.details.credentials = credentialsReferences(config.project.workspace.details.credentials)
611
+
612
+ // write to the console config (for the `aio console` commands)
613
+ await writeConsoleConfig(config)
614
+
615
+ return writeAio(config, destinationFolder, flags)
616
+ }
617
+
618
+ module.exports = {
619
+ validateConfig,
620
+ loadConfigFile,
621
+ loadAndValidateConfigFile,
622
+ writeConsoleConfig,
623
+ writeAio,
624
+ writeDefaultAppConfig,
625
+ writeEnv,
626
+ flattenObjectWithSeparator,
627
+ importConfigJson,
628
+ mergeEnv,
629
+ splitEnvLine,
630
+ CONSOLE_CONFIG_KEY
631
+ }