@jsreport/jsreport-cli 3.0.0-beta.2 → 3.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +16 -12
  3. package/cli.js +93 -93
  4. package/example.config.json +43 -43
  5. package/example.server.js +14 -14
  6. package/index.js +20 -22
  7. package/jsreport.config.js +8 -8
  8. package/lib/cliExtension.js +59 -59
  9. package/lib/{createCommandParser.js → commander/createCommandParser.js} +44 -45
  10. package/lib/commander/createCustomCommandBuilder.js +150 -0
  11. package/lib/commander/createLogger.js +51 -0
  12. package/lib/commander/executeCommand.js +37 -0
  13. package/lib/commander/getCommandEventName.js +4 -0
  14. package/lib/commander/index.js +279 -0
  15. package/lib/commander/jsreportInstance.js +98 -0
  16. package/lib/commander/registerCommand.js +91 -0
  17. package/lib/commander/startCommand.js +234 -0
  18. package/lib/commander/startProcessing.js +208 -0
  19. package/lib/commands/_initializeApp.js +206 -208
  20. package/lib/commands/configure.js +393 -395
  21. package/lib/commands/help.js +453 -454
  22. package/lib/commands/init.js +76 -77
  23. package/lib/commands/kill.js +85 -86
  24. package/lib/commands/render.js +490 -488
  25. package/lib/commands/repair.js +55 -56
  26. package/lib/commands/start.js +72 -74
  27. package/lib/commands/win-install.js +124 -126
  28. package/lib/commands/win-uninstall.js +100 -102
  29. package/lib/daemonHandler.js +164 -164
  30. package/lib/daemonInstance.js +225 -225
  31. package/lib/{registerExtensionsCommands.js → detectAndRegisterExtensionsCommands.js} +74 -74
  32. package/lib/instanceHandler.js +316 -316
  33. package/lib/keepAliveProcess.js +205 -205
  34. package/lib/{errorUtils.js → utils/error.js} +149 -127
  35. package/lib/{getTempPaths.js → utils/getTempPaths.js} +39 -39
  36. package/lib/{normalizePathOptionOrArg.js → utils/normalizePathOptionOrArg.js} +41 -41
  37. package/lib/{normalizeSocketPath.js → utils/normalizeSocketPath.js} +9 -9
  38. package/lib/{startSocketServer.js → utils/startSocketServer.js} +53 -53
  39. package/package.json +105 -106
  40. package/test/testUtils.js +197 -198
  41. package/lib/commander.js +0 -1108
@@ -1,395 +1,393 @@
1
- 'use strict'
2
-
3
- const path = require('path')
4
- const fs = require('fs')
5
- const nanoid = require('nanoid')
6
- const inquirer = require('inquirer')
7
-
8
- const description = 'Generates a jsreport configuration file (*.config.json) based on some questions'
9
- const command = 'configure'
10
-
11
- exports.command = command
12
- exports.description = description
13
-
14
- exports.builder = (yargs) => {
15
- const commandOptions = {
16
- print: {
17
- alias: 't',
18
- description: 'Print the generated configuration to the console instead to save it to a file',
19
- type: 'boolean'
20
- }
21
- }
22
-
23
- const options = Object.keys(commandOptions)
24
-
25
- return (
26
- yargs
27
- .group(options, 'Command options:')
28
- .options(commandOptions)
29
- )
30
- }
31
-
32
- exports.handler = async (argv) => {
33
- const shouldJustPrint = argv.print
34
- const context = argv.context
35
- const cwd = context.cwd
36
- const logger = context.logger
37
- const localConfigPath = path.join(cwd, 'jsreport.config.json')
38
-
39
- let answers
40
-
41
- if (argv.context.answers != null && typeof argv.context.answers === 'object') {
42
- logger.debug('questions already answered..')
43
-
44
- // just useful for testing
45
- answers = argv.context.answers
46
- } else {
47
- logger.debug('starting with questions..')
48
-
49
- const questions = [
50
- {
51
- type: 'confirm',
52
- name: 'serverEnabled',
53
- message: 'Do you want to enable web server?',
54
- default: true
55
- },
56
- {
57
- type: 'list',
58
- name: 'serverProtocol',
59
- message: 'Which protocol should web server use?',
60
- choices: [{
61
- name: 'http',
62
- value: 'http'
63
- }, {
64
- name: 'https',
65
- value: 'https'
66
- }, {
67
- name: 'http and https (http will redirect to https)',
68
- value: 'http-and-https'
69
- }],
70
- default: 0,
71
- when: (answers) => {
72
- return answers.serverEnabled
73
- }
74
- },
75
- {
76
- type: 'input',
77
- name: 'serverHttpsKey',
78
- message: 'To use https you need a key file, specify the path to this file:',
79
- default: 'certificates/server.key',
80
- when: (answers) => {
81
- return answers.serverProtocol === 'https' || answers.serverProtocol === 'http-and-https'
82
- }
83
- },
84
- {
85
- type: 'input',
86
- name: 'serverHttpsCert',
87
- message: 'To use https you need a cert file, specify the path to this file:',
88
- default: 'certificates/server.cert',
89
- when: (answers) => {
90
- return answers.serverProtocol === 'https' || answers.serverProtocol === 'http-and-https'
91
- }
92
- },
93
- {
94
- type: 'input',
95
- name: 'serverPort',
96
- message: (answers) => {
97
- let msg
98
-
99
- if (answers.serverProtocol === 'http-and-https') {
100
- msg = 'Specify the http port for web server:'
101
- } else {
102
- msg = 'Specify the ' + answers.serverProtocol + ' port for web server:'
103
- }
104
-
105
- return msg
106
- },
107
- default: 5488,
108
- validate: (input) => {
109
- const valid = !isNaN(parseInt(input, 10))
110
-
111
- if (valid) {
112
- return true
113
- }
114
-
115
- return 'port must be a valid number'
116
- },
117
- filter: (input) => {
118
- return parseInt(input, 10)
119
- },
120
- when: (answers) => {
121
- return answers.serverEnabled
122
- }
123
- },
124
- {
125
- type: 'input',
126
- name: 'serverHttpsPort',
127
- message: (answers) => {
128
- return 'Specify the https port for web server:'
129
- },
130
- default: 5489,
131
- validate: (input) => {
132
- const valid = !isNaN(parseInt(input, 10))
133
-
134
- if (valid) {
135
- return true
136
- }
137
-
138
- return 'port must be a valid number'
139
- },
140
- filter: (input) => {
141
- return parseInt(input, 10)
142
- },
143
- when: (answers) => {
144
- return answers.serverEnabled && answers.serverProtocol === 'http-and-https'
145
- }
146
- },
147
- {
148
- type: 'confirm',
149
- name: 'serverAuthEnabled',
150
- message: 'Do you want to enable authentication in web server?',
151
- default: false,
152
- when: (answers) => {
153
- return answers.serverEnabled
154
- }
155
- },
156
- {
157
- type: 'input',
158
- name: 'serverAuthUsername',
159
- message: 'Specify the admin username to use in web server authentication:',
160
- default: 'admin',
161
- when: (answers) => {
162
- return answers.serverAuthEnabled
163
- }
164
- },
165
- {
166
- type: 'password',
167
- name: 'serverAuthPassword',
168
- message: 'Specify the admin password to use in web server authentication:',
169
- validate: (input) => {
170
- const valid = (String(input) !== '')
171
-
172
- if (valid) {
173
- return true
174
- }
175
-
176
- return 'password can\'t be empty'
177
- },
178
- when: (answers) => {
179
- return answers.serverAuthEnabled
180
- }
181
- },
182
- {
183
- type: 'input',
184
- name: 'serverAuthCookieSecret',
185
- message: 'Specify a secret text for the authentication cookie session:',
186
- default: () => {
187
- return nanoid(16)
188
- },
189
- validate: (input) => {
190
- if (input.length > 0) {
191
- return true
192
- }
193
-
194
- return 'secret must be not empty'
195
- },
196
- when: (answers) => {
197
- return answers.serverAuthEnabled
198
- }
199
- },
200
- {
201
- type: 'list',
202
- name: 'store',
203
- message: 'Do you want to persist jsreport objects and logs on disk?',
204
- choices: [{
205
- name: 'Yes templates and logs will be saved on disk',
206
- value: 'fs',
207
- short: 'Yes everything saved on disk'
208
- }, {
209
- name: 'Yes, but without log files.',
210
- value: 'fs-without-log',
211
- short: 'Yes, but logs won\'t be written on disk'
212
- }, {
213
- name: 'No, objects will live in memory until process is finished',
214
- value: 'memory',
215
- short: 'No, only memory will be used'
216
- }],
217
- default: 0
218
- },
219
- {
220
- type: 'confirm',
221
- name: 'allowLocalFilesAccess',
222
- message: 'Can jsreport trust the rendering requests and allow access to local files and modules?',
223
- default: true
224
- },
225
- {
226
- type: 'input',
227
- name: 'reportTimeout',
228
- message: (answers) => {
229
- return 'Specify the general timeout for rendering:'
230
- },
231
- default: 60000,
232
- validate: (input) => {
233
- const valid = !isNaN(parseInt(input, 10))
234
-
235
- if (valid) {
236
- return true
237
- }
238
-
239
- return 'reportTimeout must be a valid number'
240
- },
241
- filter: (input) => {
242
- return parseInt(input, 10)
243
- }
244
- },
245
- {
246
- type: 'confirm',
247
- name: 'createExamples',
248
- message: 'Would you like that we create some default examples for you?',
249
- default: true,
250
- when: (answers) => {
251
- return answers.store === 'fs' || answers.store.indexOf('fs') === 0
252
- }
253
- }
254
- ]
255
-
256
- answers = await inquirer.prompt(questions)
257
- }
258
-
259
- const extensionsConf = {}
260
- const config = {}
261
-
262
- logger.debug('finishing with questions..')
263
- logger.debug('answers:')
264
- logger.debug(JSON.stringify(answers, null, 2))
265
-
266
- if (!answers.serverEnabled) {
267
- extensionsConf.express = {
268
- enabled: false
269
- }
270
- }
271
-
272
- if (answers.serverEnabled) {
273
- if (answers.serverProtocol === 'http-and-https') {
274
- config.httpPort = answers.serverPort
275
- config.httpsPort = answers.serverHttpsPort
276
-
277
- config.certificate = {
278
- key: answers.serverHttpsKey,
279
- cert: answers.serverHttpsCert
280
- }
281
- } else {
282
- config[answers.serverProtocol === 'http' ? 'httpPort' : 'httpsPort'] = answers.serverPort
283
-
284
- if (answers.serverProtocol === 'https') {
285
- config.certificate = {
286
- key: answers.serverHttpsKey,
287
- cert: answers.serverHttpsCert
288
- }
289
- }
290
- }
291
-
292
- if (answers.serverAuthEnabled) {
293
- extensionsConf.authentication = {
294
- cookieSession: {
295
- secret: answers.serverAuthCookieSecret
296
- },
297
- admin: {
298
- username: answers.serverAuthUsername,
299
- password: answers.serverAuthPassword
300
- },
301
- enabled: true
302
- }
303
- } else {
304
- extensionsConf.authentication = {
305
- cookieSession: {},
306
- admin: {
307
- username: 'admin',
308
- password: 'password'
309
- },
310
- enabled: false
311
- }
312
- }
313
- }
314
-
315
- if (answers.store === 'fs') {
316
- config.store = {
317
- provider: 'fs'
318
- }
319
-
320
- config.blobStorage = {
321
- provider: 'fs'
322
- }
323
-
324
- config.logger = {
325
- console: { transport: 'console', level: 'debug' },
326
- file: { transport: 'file', level: 'info', filename: 'logs/reporter.log' },
327
- error: { transport: 'file', level: 'error', filename: 'logs/error.log' }
328
- }
329
- } else if (answers.store === 'fs-without-log') {
330
- config.store = {
331
- provider: 'fs'
332
- }
333
-
334
- config.blobStorage = {
335
- provider: 'fs'
336
- }
337
-
338
- config.logger = {
339
- console: { transport: 'console', level: 'debug' }
340
- }
341
- } else {
342
- config.store = {
343
- provider: 'memory'
344
- }
345
-
346
- config.blobStorage = {
347
- provider: 'memory'
348
- }
349
-
350
- config.logger = {
351
- console: { transport: 'console', level: 'debug' }
352
- }
353
- }
354
-
355
- config.allowLocalFilesAccess = answers.allowLocalFilesAccess === true
356
-
357
- if (answers.createExamples) {
358
- extensionsConf['sample-template'] = {
359
- createSamples: true
360
- }
361
- }
362
-
363
- config.reportTimeout = answers.reportTimeout
364
-
365
- config.workers = {
366
- numberOfWorkers: 2
367
- }
368
-
369
- config.extensions = extensionsConf
370
-
371
- if (shouldJustPrint) {
372
- logger.info('generated config:')
373
- logger.info(JSON.stringify(config, null, 2))
374
- } else {
375
- logger.debug('generated config:')
376
- logger.debug(JSON.stringify(config, null, 2))
377
- }
378
-
379
- if (shouldJustPrint) {
380
- return {
381
- config: config
382
- }
383
- }
384
-
385
- const configFile = localConfigPath
386
-
387
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2))
388
-
389
- logger.info('config saved in:', configFile)
390
-
391
- return {
392
- config: config,
393
- filePath: configFile
394
- }
395
- }
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+ const { nanoid } = require('nanoid')
4
+ const inquirer = require('inquirer')
5
+
6
+ const description = 'Generates a jsreport configuration file (*.config.json) based on some questions'
7
+ const command = 'configure'
8
+
9
+ exports.command = command
10
+ exports.description = description
11
+
12
+ exports.builder = (yargs) => {
13
+ const commandOptions = {
14
+ print: {
15
+ alias: 't',
16
+ description: 'Print the generated configuration to the console instead to save it to a file',
17
+ type: 'boolean'
18
+ }
19
+ }
20
+
21
+ const options = Object.keys(commandOptions)
22
+
23
+ return (
24
+ yargs
25
+ .group(options, 'Command options:')
26
+ .options(commandOptions)
27
+ )
28
+ }
29
+
30
+ exports.handler = async (argv) => {
31
+ const shouldJustPrint = argv.print
32
+ const context = argv.context
33
+ const cwd = context.cwd
34
+ const logger = context.logger
35
+ const localConfigPath = path.join(cwd, 'jsreport.config.json')
36
+
37
+ let answers
38
+
39
+ if (argv.context.answers != null && typeof argv.context.answers === 'object') {
40
+ logger.debug('questions already answered..')
41
+
42
+ // just useful for testing
43
+ answers = argv.context.answers
44
+ } else {
45
+ logger.debug('starting with questions..')
46
+
47
+ const questions = [
48
+ {
49
+ type: 'confirm',
50
+ name: 'serverEnabled',
51
+ message: 'Do you want to enable web server?',
52
+ default: true
53
+ },
54
+ {
55
+ type: 'list',
56
+ name: 'serverProtocol',
57
+ message: 'Which protocol should web server use?',
58
+ choices: [{
59
+ name: 'http',
60
+ value: 'http'
61
+ }, {
62
+ name: 'https',
63
+ value: 'https'
64
+ }, {
65
+ name: 'http and https (http will redirect to https)',
66
+ value: 'http-and-https'
67
+ }],
68
+ default: 0,
69
+ when: (answers) => {
70
+ return answers.serverEnabled
71
+ }
72
+ },
73
+ {
74
+ type: 'input',
75
+ name: 'serverHttpsKey',
76
+ message: 'To use https you need a key file, specify the path to this file:',
77
+ default: 'certificates/server.key',
78
+ when: (answers) => {
79
+ return answers.serverProtocol === 'https' || answers.serverProtocol === 'http-and-https'
80
+ }
81
+ },
82
+ {
83
+ type: 'input',
84
+ name: 'serverHttpsCert',
85
+ message: 'To use https you need a cert file, specify the path to this file:',
86
+ default: 'certificates/server.cert',
87
+ when: (answers) => {
88
+ return answers.serverProtocol === 'https' || answers.serverProtocol === 'http-and-https'
89
+ }
90
+ },
91
+ {
92
+ type: 'input',
93
+ name: 'serverPort',
94
+ message: (answers) => {
95
+ let msg
96
+
97
+ if (answers.serverProtocol === 'http-and-https') {
98
+ msg = 'Specify the http port for web server:'
99
+ } else {
100
+ msg = 'Specify the ' + answers.serverProtocol + ' port for web server:'
101
+ }
102
+
103
+ return msg
104
+ },
105
+ default: 5488,
106
+ validate: (input) => {
107
+ const valid = !isNaN(parseInt(input, 10))
108
+
109
+ if (valid) {
110
+ return true
111
+ }
112
+
113
+ return 'port must be a valid number'
114
+ },
115
+ filter: (input) => {
116
+ return parseInt(input, 10)
117
+ },
118
+ when: (answers) => {
119
+ return answers.serverEnabled
120
+ }
121
+ },
122
+ {
123
+ type: 'input',
124
+ name: 'serverHttpsPort',
125
+ message: (answers) => {
126
+ return 'Specify the https port for web server:'
127
+ },
128
+ default: 5489,
129
+ validate: (input) => {
130
+ const valid = !isNaN(parseInt(input, 10))
131
+
132
+ if (valid) {
133
+ return true
134
+ }
135
+
136
+ return 'port must be a valid number'
137
+ },
138
+ filter: (input) => {
139
+ return parseInt(input, 10)
140
+ },
141
+ when: (answers) => {
142
+ return answers.serverEnabled && answers.serverProtocol === 'http-and-https'
143
+ }
144
+ },
145
+ {
146
+ type: 'confirm',
147
+ name: 'serverAuthEnabled',
148
+ message: 'Do you want to enable authentication in web server?',
149
+ default: false,
150
+ when: (answers) => {
151
+ return answers.serverEnabled
152
+ }
153
+ },
154
+ {
155
+ type: 'input',
156
+ name: 'serverAuthUsername',
157
+ message: 'Specify the admin username to use in web server authentication:',
158
+ default: 'admin',
159
+ when: (answers) => {
160
+ return answers.serverAuthEnabled
161
+ }
162
+ },
163
+ {
164
+ type: 'password',
165
+ name: 'serverAuthPassword',
166
+ message: 'Specify the admin password to use in web server authentication:',
167
+ validate: (input) => {
168
+ const valid = (String(input) !== '')
169
+
170
+ if (valid) {
171
+ return true
172
+ }
173
+
174
+ return 'password can\'t be empty'
175
+ },
176
+ when: (answers) => {
177
+ return answers.serverAuthEnabled
178
+ }
179
+ },
180
+ {
181
+ type: 'input',
182
+ name: 'serverAuthCookieSecret',
183
+ message: 'Specify a secret text for the authentication cookie session:',
184
+ default: () => {
185
+ return nanoid(16)
186
+ },
187
+ validate: (input) => {
188
+ if (input.length > 0) {
189
+ return true
190
+ }
191
+
192
+ return 'secret must be not empty'
193
+ },
194
+ when: (answers) => {
195
+ return answers.serverAuthEnabled
196
+ }
197
+ },
198
+ {
199
+ type: 'list',
200
+ name: 'store',
201
+ message: 'Do you want to persist jsreport objects and logs on disk?',
202
+ choices: [{
203
+ name: 'Yes templates and logs will be saved on disk',
204
+ value: 'fs',
205
+ short: 'Yes everything saved on disk'
206
+ }, {
207
+ name: 'Yes, but without log files.',
208
+ value: 'fs-without-log',
209
+ short: 'Yes, but logs won\'t be written on disk'
210
+ }, {
211
+ name: 'No, objects will live in memory until process is finished',
212
+ value: 'memory',
213
+ short: 'No, only memory will be used'
214
+ }],
215
+ default: 0
216
+ },
217
+ {
218
+ type: 'confirm',
219
+ name: 'allowLocalFilesAccess',
220
+ message: 'Can jsreport trust the rendering requests and allow access to local files and modules?',
221
+ default: true
222
+ },
223
+ {
224
+ type: 'input',
225
+ name: 'reportTimeout',
226
+ message: (answers) => {
227
+ return 'Specify the general timeout for rendering:'
228
+ },
229
+ default: 60000,
230
+ validate: (input) => {
231
+ const valid = !isNaN(parseInt(input, 10))
232
+
233
+ if (valid) {
234
+ return true
235
+ }
236
+
237
+ return 'reportTimeout must be a valid number'
238
+ },
239
+ filter: (input) => {
240
+ return parseInt(input, 10)
241
+ }
242
+ },
243
+ {
244
+ type: 'confirm',
245
+ name: 'createExamples',
246
+ message: 'Would you like that we create some default examples for you?',
247
+ default: true,
248
+ when: (answers) => {
249
+ return answers.store === 'fs' || answers.store.indexOf('fs') === 0
250
+ }
251
+ }
252
+ ]
253
+
254
+ answers = await inquirer.prompt(questions)
255
+ }
256
+
257
+ const extensionsConf = {}
258
+ const config = {}
259
+
260
+ logger.debug('finishing with questions..')
261
+ logger.debug('answers:')
262
+ logger.debug(JSON.stringify(answers, null, 2))
263
+
264
+ if (!answers.serverEnabled) {
265
+ extensionsConf.express = {
266
+ enabled: false
267
+ }
268
+ }
269
+
270
+ if (answers.serverEnabled) {
271
+ if (answers.serverProtocol === 'http-and-https') {
272
+ config.httpPort = answers.serverPort
273
+ config.httpsPort = answers.serverHttpsPort
274
+
275
+ config.certificate = {
276
+ key: answers.serverHttpsKey,
277
+ cert: answers.serverHttpsCert
278
+ }
279
+ } else {
280
+ config[answers.serverProtocol === 'http' ? 'httpPort' : 'httpsPort'] = answers.serverPort
281
+
282
+ if (answers.serverProtocol === 'https') {
283
+ config.certificate = {
284
+ key: answers.serverHttpsKey,
285
+ cert: answers.serverHttpsCert
286
+ }
287
+ }
288
+ }
289
+
290
+ if (answers.serverAuthEnabled) {
291
+ extensionsConf.authentication = {
292
+ cookieSession: {
293
+ secret: answers.serverAuthCookieSecret
294
+ },
295
+ admin: {
296
+ username: answers.serverAuthUsername,
297
+ password: answers.serverAuthPassword
298
+ },
299
+ enabled: true
300
+ }
301
+ } else {
302
+ extensionsConf.authentication = {
303
+ cookieSession: {},
304
+ admin: {
305
+ username: 'admin',
306
+ password: 'password'
307
+ },
308
+ enabled: false
309
+ }
310
+ }
311
+ }
312
+
313
+ if (answers.store === 'fs') {
314
+ config.store = {
315
+ provider: 'fs'
316
+ }
317
+
318
+ config.blobStorage = {
319
+ provider: 'fs'
320
+ }
321
+
322
+ config.logger = {
323
+ console: { transport: 'console', level: 'debug' },
324
+ file: { transport: 'file', level: 'info', filename: 'logs/reporter.log' },
325
+ error: { transport: 'file', level: 'error', filename: 'logs/error.log' }
326
+ }
327
+ } else if (answers.store === 'fs-without-log') {
328
+ config.store = {
329
+ provider: 'fs'
330
+ }
331
+
332
+ config.blobStorage = {
333
+ provider: 'fs'
334
+ }
335
+
336
+ config.logger = {
337
+ console: { transport: 'console', level: 'debug' }
338
+ }
339
+ } else {
340
+ config.store = {
341
+ provider: 'memory'
342
+ }
343
+
344
+ config.blobStorage = {
345
+ provider: 'memory'
346
+ }
347
+
348
+ config.logger = {
349
+ console: { transport: 'console', level: 'debug' }
350
+ }
351
+ }
352
+
353
+ config.allowLocalFilesAccess = answers.allowLocalFilesAccess === true
354
+
355
+ if (answers.createExamples) {
356
+ extensionsConf['sample-template'] = {
357
+ createSamples: true
358
+ }
359
+ }
360
+
361
+ config.reportTimeout = answers.reportTimeout
362
+
363
+ config.workers = {
364
+ numberOfWorkers: 2
365
+ }
366
+
367
+ config.extensions = extensionsConf
368
+
369
+ if (shouldJustPrint) {
370
+ logger.info('generated config:')
371
+ logger.info(JSON.stringify(config, null, 2))
372
+ } else {
373
+ logger.debug('generated config:')
374
+ logger.debug(JSON.stringify(config, null, 2))
375
+ }
376
+
377
+ if (shouldJustPrint) {
378
+ return {
379
+ config: config
380
+ }
381
+ }
382
+
383
+ const configFile = localConfigPath
384
+
385
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2))
386
+
387
+ logger.info('config saved in:', configFile)
388
+
389
+ return {
390
+ config: config,
391
+ filePath: configFile
392
+ }
393
+ }