configorama 0.6.4 → 0.6.5
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/README.md +211 -12
- package/cli.js +10 -3
- package/index.d.ts +45 -0
- package/package.json +12 -3
- package/src/index.js +9 -5
- package/src/main.js +348 -88
- package/src/parsers/esm.js +69 -0
- package/src/parsers/index.js +3 -1
- package/src/parsers/ini.js +51 -0
- package/src/parsers/ini.test.js +133 -0
- package/src/parsers/json5.js +1 -0
- package/src/parsers/typescript.js +154 -0
- package/src/parsers/yaml.test.js +1 -1
- package/src/resolvers/valueFromCron.js +252 -0
- package/src/resolvers/valueFromCron.test.js +132 -0
- package/src/resolvers/valueFromEval.js +37 -0
- package/src/resolvers/valueFromEval.test.js +44 -0
- package/src/resolvers/valueFromGit.js +6 -4
- package/src/types.d.ts +112 -0
- package/src/utils/cleanVariable.js +67 -3
- package/src/utils/createEditorLink.js +23 -0
- package/src/utils/find-nested-variables.js +10 -1
- package/src/utils/logs.js +2 -1
- package/src/utils/parse.js +40 -0
- package/src/utils/resolveAlias.js +152 -0
- package/src/utils/resolveAlias.test.js +98 -0
- package/src/utils/resolveAliasOld.js +65 -0
- package/src/utils/textUtils.js +2 -2
package/src/main.js
CHANGED
|
@@ -13,16 +13,20 @@ const findUp = require('find-up')
|
|
|
13
13
|
const traverse = require('traverse')
|
|
14
14
|
const dotProp = require('dot-prop')
|
|
15
15
|
const chalk = require('./utils/chalk')
|
|
16
|
+
const { resolveAlias } = require('./utils/resolveAlias')
|
|
16
17
|
|
|
17
18
|
/* Default Value resolvers */
|
|
18
19
|
const getValueFromString = require('./resolvers/valueFromString')
|
|
19
20
|
const getValueFromNumber = require('./resolvers/valueFromNumber')
|
|
20
21
|
const getValueFromEnv = require('./resolvers/valueFromEnv')
|
|
21
22
|
const getValueFromOptions = require('./resolvers/valueFromOptions')
|
|
23
|
+
const getValueFromCron = require('./resolvers/valueFromCron')
|
|
24
|
+
const getValueFromEval = require('./resolvers/valueFromEval')
|
|
22
25
|
const createGitResolver = require('./resolvers/valueFromGit')
|
|
23
26
|
/* Default File Parsers */
|
|
24
27
|
const YAML = require('./parsers/yaml')
|
|
25
28
|
const TOML = require('./parsers/toml')
|
|
29
|
+
const INI = require('./parsers/ini')
|
|
26
30
|
/* functions */
|
|
27
31
|
const md5Function = require('./functions/md5')
|
|
28
32
|
|
|
@@ -44,14 +48,15 @@ const {
|
|
|
44
48
|
const { parseFileContents } = require('./utils/parse')
|
|
45
49
|
const { splitCsv } = require('./utils/splitCsv')
|
|
46
50
|
const { replaceAll } = require('./utils/replaceAll')
|
|
47
|
-
const {
|
|
51
|
+
const { getTextAfterOccurrence, findNestedVariable } = require('./utils/textUtils')
|
|
48
52
|
const { getFallbackString, verifyVariable } = require('./utils/variableUtils')
|
|
49
53
|
const { encodeUnknown, decodeUnknown } = require('./utils/unknownValues')
|
|
50
54
|
const { mergeByKeys } = require('./utils/mergeByKeys')
|
|
51
55
|
const { arrayToJsonPath } = require('./utils/arrayToJsonPath')
|
|
52
56
|
const { findNestedVariables } = require('./utils/find-nested-variables')
|
|
53
|
-
const { makeBox } = require('@davidwells/box-logger')
|
|
57
|
+
const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
|
|
54
58
|
const { logHeader } = require('./utils/logs')
|
|
59
|
+
const { createEditorLink } = require('./utils/createEditorLink')
|
|
55
60
|
/**
|
|
56
61
|
* Maintainer's notes:
|
|
57
62
|
*
|
|
@@ -70,7 +75,7 @@ const deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/)
|
|
|
70
75
|
const deepIndexReplacePattern = new RegExp(/^deep:|(\.[^}]+)*$/g)
|
|
71
76
|
const deepIndexPattern = /deep\:(\d*)/
|
|
72
77
|
const deepPrefixReplacePattern = /(?:^deep:)\d+\.?/g
|
|
73
|
-
const fileRefSyntax = RegExp(/^file\((~?[
|
|
78
|
+
const fileRefSyntax = RegExp(/^file\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
74
79
|
// TODO update file regex ^file\((~?[a-zA-Z0-9._\-\/, ]+?)\)
|
|
75
80
|
// To match file(asyncValue.js, lol) input params
|
|
76
81
|
const envRefSyntax = RegExp(/^env:/g)
|
|
@@ -104,8 +109,6 @@ class Configorama {
|
|
|
104
109
|
if (opts && !opts.sync) {
|
|
105
110
|
handleSignalEvents()
|
|
106
111
|
}
|
|
107
|
-
|
|
108
|
-
const showFoundVariables = opts && opts.dynamicArgs && (opts.dynamicArgs.list || opts.dynamicArgs.info)
|
|
109
112
|
|
|
110
113
|
const options = opts || {}
|
|
111
114
|
// Set opts to pass into JS file calls
|
|
@@ -148,24 +151,16 @@ class Configorama {
|
|
|
148
151
|
const fileDirectory = path.dirname(path.resolve(fileOrObject))
|
|
149
152
|
const fileType = path.extname(fileOrObject)
|
|
150
153
|
|
|
151
|
-
// Parse file contents using extracted function
|
|
152
|
-
const configObject = parseFileContents(
|
|
153
|
-
fileContents,
|
|
154
|
-
fileType,
|
|
155
|
-
fileOrObject,
|
|
156
|
-
varRegex,
|
|
157
|
-
this.opts
|
|
158
|
-
)
|
|
159
|
-
|
|
160
154
|
this.configFilePath = fileOrObject
|
|
161
|
-
//
|
|
162
|
-
this.
|
|
155
|
+
// Set configFileType
|
|
156
|
+
this.configFileType = fileType
|
|
163
157
|
// Keep a copy of the original file contents
|
|
164
158
|
this.originalString = fileContents
|
|
165
|
-
// Keep a copy
|
|
166
|
-
this.originalConfig = cloneDeep(configObject)
|
|
167
159
|
// Set configPath for file references
|
|
168
160
|
this.configPath = fileDirectory
|
|
161
|
+
// Initialize config as null - will be populated in init
|
|
162
|
+
this.config = null
|
|
163
|
+
this.originalConfig = null
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
// Track promise resolution
|
|
@@ -187,6 +182,23 @@ class Configorama {
|
|
|
187
182
|
* ${opt:other, "fallbackValue"}
|
|
188
183
|
*/
|
|
189
184
|
getValueFromOptions,
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Cron expressions
|
|
188
|
+
* Usage:
|
|
189
|
+
* ${cron(every minute)}
|
|
190
|
+
* ${cron(weekdays)}
|
|
191
|
+
* ${cron(at 9:30)}
|
|
192
|
+
*/
|
|
193
|
+
getValueFromCron,
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Eval expressions
|
|
197
|
+
* Usage:
|
|
198
|
+
* ${eval(${self:valueTwo} > ${self:valueOne})}
|
|
199
|
+
*/
|
|
200
|
+
getValueFromEval,
|
|
201
|
+
|
|
190
202
|
/**
|
|
191
203
|
* Self references
|
|
192
204
|
* Usage:
|
|
@@ -264,6 +276,7 @@ class Configorama {
|
|
|
264
276
|
return deeperExists
|
|
265
277
|
}
|
|
266
278
|
}
|
|
279
|
+
// console.log('fallthrough fullObject', fullObject)
|
|
267
280
|
/* is simple ${whatever} reference in same file */
|
|
268
281
|
const startOf = varString.split('.')
|
|
269
282
|
return fullObject[startOf[0]]
|
|
@@ -288,8 +301,9 @@ class Configorama {
|
|
|
288
301
|
this.variableTypes = this.variableTypes.concat(fallThroughSelfMatcher)
|
|
289
302
|
|
|
290
303
|
// const variablesKnownTypes = new RegExp(`^(${this.variableTypes.map((v) => v.prefix || v.type).join('|')}):`)
|
|
291
|
-
const variablesKnownTypes = combineRegexes(
|
|
292
|
-
|
|
304
|
+
const variablesKnownTypes = combineRegexes(
|
|
305
|
+
this.variableTypes.filter((v) => v.type !== 'string').map((v) => v.match)
|
|
306
|
+
)
|
|
293
307
|
this.variablesKnownTypes = variablesKnownTypes
|
|
294
308
|
|
|
295
309
|
// this.allPatterns = combineRegexes(...this.variableTypes.map((v) => v.match))
|
|
@@ -410,6 +424,49 @@ class Configorama {
|
|
|
410
424
|
this.functions = Object.assign({}, this.functions, options.functions)
|
|
411
425
|
}
|
|
412
426
|
|
|
427
|
+
this.deep = []
|
|
428
|
+
this.leaves = []
|
|
429
|
+
this.callCount = 0
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
initialCall(func) {
|
|
433
|
+
this.deep = []
|
|
434
|
+
this.tracker.start()
|
|
435
|
+
return func().finally(() => {
|
|
436
|
+
this.tracker.stop()
|
|
437
|
+
this.deep = []
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Populate all variables in the service, conveniently remove and restore the service attributes
|
|
443
|
+
* that confuse the population methods.
|
|
444
|
+
* @param cliOpts An options hive to use for ${opt:...} variables.
|
|
445
|
+
* @returns {Promise.<TResult>|*} A promise resolving to the populated service.
|
|
446
|
+
*/
|
|
447
|
+
async init(cliOpts) {
|
|
448
|
+
this.options = cliOpts || {}
|
|
449
|
+
const configoramaOpts = this.opts
|
|
450
|
+
|
|
451
|
+
const showFoundVariables = configoramaOpts && configoramaOpts.dynamicArgs && (configoramaOpts.dynamicArgs.list || configoramaOpts.dynamicArgs.info)
|
|
452
|
+
|
|
453
|
+
// If we have a file path but no config yet, parse it now
|
|
454
|
+
if (this.configFilePath && !this.config) {
|
|
455
|
+
const configObject = await parseFileContents(
|
|
456
|
+
this.originalString,
|
|
457
|
+
this.configFileType,
|
|
458
|
+
this.configFilePath,
|
|
459
|
+
this.variableSyntax,
|
|
460
|
+
this.opts
|
|
461
|
+
)
|
|
462
|
+
this.configFileContents = ''
|
|
463
|
+
if (VERBOSE || showFoundVariables) {
|
|
464
|
+
this.configFileContents = fs.readFileSync(this.configFilePath, 'utf8')
|
|
465
|
+
}
|
|
466
|
+
this.config = configObject
|
|
467
|
+
this.originalConfig = cloneDeep(configObject)
|
|
468
|
+
}
|
|
469
|
+
|
|
413
470
|
if (VERBOSE) {
|
|
414
471
|
logHeader('Config Input before processing')
|
|
415
472
|
console.log()
|
|
@@ -417,10 +474,14 @@ class Configorama {
|
|
|
417
474
|
console.log()
|
|
418
475
|
}
|
|
419
476
|
|
|
477
|
+
const variableSyntax = this.variableSyntax
|
|
478
|
+
const variablesKnownTypes = this.variablesKnownTypes
|
|
479
|
+
|
|
420
480
|
if (VERBOSE || showFoundVariables) {
|
|
421
481
|
const foundVariables = []
|
|
422
482
|
const variableData = {}
|
|
423
483
|
let matchCount = 1
|
|
484
|
+
// console.log('this.originalConfig', this.originalConfig)
|
|
424
485
|
traverse(this.originalConfig).forEach(function (rawValue) {
|
|
425
486
|
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
426
487
|
const configValuePath = this.path.join('.')
|
|
@@ -452,45 +513,63 @@ class Configorama {
|
|
|
452
513
|
resolveOrder: [],
|
|
453
514
|
resolveDetails: nested,
|
|
454
515
|
}
|
|
455
|
-
|
|
516
|
+
let defaultValueIsVar = false
|
|
456
517
|
function calculateResolveOrder(item) {
|
|
457
518
|
if (item && item.fallbackValues) {
|
|
458
519
|
let hasResolvedFallback
|
|
520
|
+
// console.log('item.fallbackValues', item.fallbackValues)
|
|
459
521
|
const order = ([item.valueBeforeFallback]).concat(item.fallbackValues.map((f, i) => {
|
|
522
|
+
// console.log('f', f)
|
|
460
523
|
if (f.fallbackValues) {
|
|
461
524
|
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
525
|
+
// console.log('nestedOrder', nestedOrder)
|
|
526
|
+
// console.log('nestedResolvedFallback', nestedResolvedFallback)
|
|
462
527
|
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
463
528
|
hasResolvedFallback = nestedResolvedFallback
|
|
464
529
|
}
|
|
465
530
|
return nestedOrder // Return just the order part
|
|
466
531
|
}
|
|
532
|
+
|
|
467
533
|
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
468
534
|
hasResolvedFallback = f.stringValue
|
|
469
535
|
}
|
|
536
|
+
if (f.isResolvedFallback) {
|
|
537
|
+
hasResolvedFallback = f.stringValue
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!hasResolvedFallback && f.isVariable) {
|
|
541
|
+
defaultValueIsVar = f
|
|
542
|
+
}
|
|
543
|
+
// console.log('hasResolvedFallback', hasResolvedFallback)
|
|
470
544
|
return `${f.stringValue || f.variable}${f.isResolvedFallback ? ' (Resolved default fallback)' : ''}`
|
|
471
545
|
})).flat()
|
|
472
546
|
|
|
473
547
|
return [order, hasResolvedFallback]
|
|
474
548
|
}
|
|
475
|
-
return [[item.variable],
|
|
549
|
+
return [[item.variable], undefined] // Return array instead of just the value
|
|
476
550
|
}
|
|
477
551
|
|
|
478
552
|
const [resolveOrder, hasResolvedFallback] = calculateResolveOrder(lastItem)
|
|
479
553
|
varData.resolveOrder = resolveOrder
|
|
480
554
|
|
|
481
|
-
if (
|
|
482
|
-
varData.
|
|
555
|
+
if (defaultValueIsVar) {
|
|
556
|
+
varData.defaultValueIsVar = defaultValueIsVar
|
|
483
557
|
}
|
|
484
558
|
|
|
559
|
+
// console.log('hasResolvedFallback', hasResolvedFallback)
|
|
560
|
+
if (typeof hasResolvedFallback !== 'undefined') {
|
|
561
|
+
varData.defaultValue = hasResolvedFallback
|
|
562
|
+
}
|
|
485
563
|
|
|
486
|
-
|
|
564
|
+
// console.log('varData.defaultValue', varData.defaultValue)
|
|
565
|
+
if (typeof varData.defaultValue === 'undefined') {
|
|
487
566
|
varData.isRequired = true
|
|
488
567
|
}
|
|
489
568
|
|
|
490
|
-
|
|
491
569
|
if (varData.resolveOrder.length > 1) {
|
|
492
570
|
varData.hasFallback = true
|
|
493
571
|
}
|
|
572
|
+
//console.log('varData', varData)
|
|
494
573
|
|
|
495
574
|
variableData[key] = (variableData[key] || []).concat(varData)
|
|
496
575
|
|
|
@@ -498,7 +577,6 @@ class Configorama {
|
|
|
498
577
|
}
|
|
499
578
|
})
|
|
500
579
|
|
|
501
|
-
|
|
502
580
|
if (!foundVariables.length) {
|
|
503
581
|
logHeader('No Variables Found in Config')
|
|
504
582
|
if (this.configFilePath) {
|
|
@@ -545,20 +623,35 @@ class Configorama {
|
|
|
545
623
|
}
|
|
546
624
|
|
|
547
625
|
logHeader('Variable Details')
|
|
626
|
+
|
|
627
|
+
const lines = this.configFileContents.split('\n')
|
|
628
|
+
// console.log('lines', lines)
|
|
548
629
|
|
|
549
630
|
const indent = ''
|
|
550
|
-
varKeys.
|
|
631
|
+
const boxes = varKeys.map((key, i) => {
|
|
551
632
|
const variableInstances = variableData[key]
|
|
633
|
+
// console.log('variableInstances', variableInstances)
|
|
552
634
|
|
|
553
635
|
const firstInstance = variableInstances[0]
|
|
554
636
|
|
|
555
637
|
let requiredText = ''
|
|
556
638
|
let defaultValueSrc = ''
|
|
557
|
-
if (
|
|
639
|
+
if (typeof firstInstance.defaultValue === 'undefined') {
|
|
558
640
|
// console.log('no default value', firstInstance)
|
|
641
|
+
|
|
642
|
+
let dotPropArr = []
|
|
643
|
+
if (firstInstance.defaultValueIsVar && (
|
|
644
|
+
firstInstance.defaultValueIsVar.varType === 'self:' ||
|
|
645
|
+
firstInstance.defaultValueIsVar.varType === 'dot.prop'
|
|
646
|
+
)) {
|
|
647
|
+
dotPropArr = [firstInstance.defaultValueIsVar]
|
|
648
|
+
}
|
|
559
649
|
/* Check if the fallback variable is a self reference */
|
|
560
650
|
const hasDotPropOrSelf = variableInstances.reduce((acc, v) => {
|
|
561
|
-
const dotProp = v.resolveDetails.find((d) =>
|
|
651
|
+
const dotProp = v.resolveDetails.find((d) => {
|
|
652
|
+
// console.log('d', d)
|
|
653
|
+
return d.varType === 'dot.prop'
|
|
654
|
+
})
|
|
562
655
|
if (dotProp) {
|
|
563
656
|
acc.push(dotProp)
|
|
564
657
|
}
|
|
@@ -567,10 +660,12 @@ class Configorama {
|
|
|
567
660
|
acc.push(v.resolveDetails[0])
|
|
568
661
|
}
|
|
569
662
|
return acc
|
|
570
|
-
},
|
|
663
|
+
}, dotPropArr)
|
|
571
664
|
// console.log('hasDotPropOrSelf', hasDotPropOrSelf)
|
|
665
|
+
|
|
572
666
|
if (!hasDotPropOrSelf.length) {
|
|
573
|
-
|
|
667
|
+
const debug = (false) ? JSON.stringify(firstInstance, null, 2) : ''
|
|
668
|
+
requiredText = `[Required Variable] ${debug}`
|
|
574
669
|
} else {
|
|
575
670
|
const fallBackValues = variableInstances.filter((v) => v.resolveDetails.find((d) => d.hasFallback)).map((v) => v.resolveDetails)
|
|
576
671
|
// console.log('fallBackValues', fallBackValues)
|
|
@@ -589,6 +684,9 @@ class Configorama {
|
|
|
589
684
|
// truncate niceString to 100 characters
|
|
590
685
|
const truncatedString = niceString.length > 100 ? niceString.substring(0, 90) + '...' : niceString
|
|
591
686
|
firstInstance.defaultValue = truncatedString
|
|
687
|
+
} else {
|
|
688
|
+
deepLog('Missing default var', firstInstance)
|
|
689
|
+
throw new Error(`Variable misconfiguration at ${firstInstance.variable}\n\n"${hasDotPropOrSelf[0].variable}" resolves to undefined value.\n`)
|
|
592
690
|
}
|
|
593
691
|
}
|
|
594
692
|
//this.originalConfig[key] = undefined
|
|
@@ -601,14 +699,16 @@ class Configorama {
|
|
|
601
699
|
const keyChalk = chalk.whiteBright
|
|
602
700
|
const valueChalk = chalk.hex(VALUE_HEX)
|
|
603
701
|
|
|
604
|
-
if (firstInstance.defaultValue) {
|
|
605
|
-
|
|
702
|
+
if (typeof firstInstance.defaultValue !== 'undefined') {
|
|
703
|
+
// console.log('firstInstance.defaultValue', firstInstance.defaultValue)
|
|
704
|
+
const defaultValueRender = firstInstance.defaultValue === '' ? '""' : firstInstance.defaultValue
|
|
705
|
+
const defaultValueText = `${indent}${keyChalk(`Default value:`.padEnd(titleText.length, ' '))}`
|
|
606
706
|
// ensure padding is even
|
|
607
|
-
varMsg += `${defaultValueText} ${valueChalk(
|
|
707
|
+
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}`
|
|
608
708
|
}
|
|
609
709
|
|
|
610
|
-
if(defaultValueSrc) {
|
|
611
|
-
varMsg += `\n${indent}${keyChalk('
|
|
710
|
+
if (defaultValueSrc) {
|
|
711
|
+
varMsg += `\n${indent}${keyChalk('Default value path:'.padEnd(titleText.length, ' '))} `
|
|
612
712
|
varMsg += `${valueChalk(defaultValueSrc)}`
|
|
613
713
|
}
|
|
614
714
|
|
|
@@ -620,26 +720,34 @@ class Configorama {
|
|
|
620
720
|
|
|
621
721
|
let locationRender = valueChalk(variableInstances[0].path)
|
|
622
722
|
|
|
623
|
-
let locationLabel = `${indent}${keyChalk('
|
|
723
|
+
let locationLabel = `${indent}${keyChalk('Path:'.padEnd(titleText.length, ' '))}`
|
|
624
724
|
if (variableInstances.length > 1) {
|
|
625
725
|
locationRender = `\n${variableInstances.map((v) => valueChalk(`${indent}- ${v.path}`)).join('\n')}`
|
|
626
|
-
const locationLabelText = `${indent}${keyChalk('
|
|
726
|
+
const locationLabelText = `${indent}${keyChalk('Paths:')}`
|
|
627
727
|
locationLabel = locationLabelText
|
|
628
728
|
}
|
|
629
729
|
|
|
630
730
|
varMsg += `\n${locationLabel} ${locationRender}`
|
|
731
|
+
|
|
732
|
+
// find the match in our lines
|
|
733
|
+
const line = lines.findIndex((line) => line.includes(key))
|
|
734
|
+
const lineNumber = line + 1
|
|
631
735
|
|
|
632
736
|
// console.log(` ${chalk.bold(key)}`)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if(i < varKeys.length - 1) {
|
|
640
|
-
//console.log()
|
|
737
|
+
return {
|
|
738
|
+
text: varMsg,
|
|
739
|
+
title: {
|
|
740
|
+
left: `▶ ${lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, key) : key}`,
|
|
741
|
+
right: lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, `Line: ${lineNumber}`, 'gray') : '',
|
|
742
|
+
},
|
|
641
743
|
}
|
|
642
744
|
})
|
|
745
|
+
|
|
746
|
+
console.log(makeStackedBoxes(boxes, {
|
|
747
|
+
borderColor: 'gray',
|
|
748
|
+
minWidth: 120,
|
|
749
|
+
borderStyle: 'bold',
|
|
750
|
+
}))
|
|
643
751
|
}
|
|
644
752
|
|
|
645
753
|
/* Exit early if list or info flag is set */
|
|
@@ -648,33 +756,11 @@ class Configorama {
|
|
|
648
756
|
}
|
|
649
757
|
}
|
|
650
758
|
|
|
651
|
-
this.deep = []
|
|
652
|
-
this.callCount = 0
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
initialCall(func) {
|
|
656
|
-
this.deep = []
|
|
657
|
-
this.tracker.start()
|
|
658
|
-
return func().finally(() => {
|
|
659
|
-
this.tracker.stop()
|
|
660
|
-
this.deep = []
|
|
661
|
-
})
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Populate all variables in the service, conveniently remove and restore the service attributes
|
|
666
|
-
* that confuse the population methods.
|
|
667
|
-
* @param cliOpts An options hive to use for ${opt:...} variables.
|
|
668
|
-
* @returns {Promise.<TResult>|*} A promise resolving to the populated service.
|
|
669
|
-
*/
|
|
670
|
-
init(cliOpts) {
|
|
671
|
-
this.options = cliOpts || {}
|
|
672
|
-
const configoramaOpts = this.opts
|
|
673
759
|
const originalConfig = this.originalConfig
|
|
674
760
|
|
|
675
761
|
/* If no variables found just return early */
|
|
676
762
|
if (this.originalString && !this.originalString.match(this.variableSyntax)) {
|
|
677
|
-
return Promise.resolve(originalConfig)
|
|
763
|
+
return Promise.resolve(this.originalConfig)
|
|
678
764
|
}
|
|
679
765
|
|
|
680
766
|
const useDotEnv = this.originalConfig.useDotenv || this.originalConfig.useDotEnv
|
|
@@ -707,18 +793,28 @@ class Configorama {
|
|
|
707
793
|
// console.log('Final Config', this.config)
|
|
708
794
|
const transform = this.runFunction.bind(this)
|
|
709
795
|
const varSyntax = this.variableSyntax
|
|
796
|
+
const leaves = this.leaves
|
|
797
|
+
// console.log('leaves two', leaves)
|
|
710
798
|
// Traverse resolved object and run functions
|
|
711
799
|
// console.log('this.config', this.config)
|
|
712
800
|
traverse(this.config).forEach(function (rawValue) {
|
|
713
801
|
/* Pass through unknown variables */
|
|
714
802
|
if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
|
|
715
803
|
const configValuePath = this.path.join('.')
|
|
804
|
+
console.log(this.path)
|
|
716
805
|
const ogValue = dotProp.get(originalConfig, configValuePath)
|
|
717
806
|
const varDisplay = ogValue ? `"${ogValue}" variable` : 'variable'
|
|
807
|
+
|
|
808
|
+
const leaf = leaves.find((l) => l.path.join('.') === configValuePath)
|
|
809
|
+
// if (leaf) {
|
|
810
|
+
// deepLog('leaf', leaf)
|
|
811
|
+
// }
|
|
718
812
|
const errorMessage = `
|
|
719
|
-
|
|
720
|
-
"${configValuePath}" resolved to "undefined"
|
|
721
|
-
|
|
813
|
+
Config error:\n
|
|
814
|
+
Path "${configValuePath}" resolved to "undefined".\n
|
|
815
|
+
Verify the ${varDisplay} in config at "${configValuePath}".\n
|
|
816
|
+
${leaf ? `See:\n ${leaf.originalValuePath}: ${leaf.originalSource} ` : ''}
|
|
817
|
+
${leaf && leaf.isFileRef ? `\n The error could be deeper in the referenced file at ${configValuePath.replace(leaf.originalValuePath, '').replace(/^\./, '')} key.\n` : ''}`
|
|
722
818
|
throw new Error(errorMessage)
|
|
723
819
|
}
|
|
724
820
|
if (typeof rawValue === 'string') {
|
|
@@ -783,7 +879,7 @@ class Configorama {
|
|
|
783
879
|
var hasFunc = funcRegex.exec(variableString)
|
|
784
880
|
// TODO finish Function handling. Need to move this down below resolver to resolve inner refs first
|
|
785
881
|
// console.log('hasFunc', hasFunc)
|
|
786
|
-
if (!hasFunc) {
|
|
882
|
+
if (!hasFunc || hasFunc && (hasFunc[1] === 'cron' || hasFunc[1] === 'eval')) {
|
|
787
883
|
return variableString
|
|
788
884
|
}
|
|
789
885
|
// test for object
|
|
@@ -881,12 +977,23 @@ class Configorama {
|
|
|
881
977
|
value: current,
|
|
882
978
|
}
|
|
883
979
|
const thePath = leaf.path.length > 1 ? leaf.path.join('.') : leaf.path[0]
|
|
980
|
+
// console.log('thePath', thePath)
|
|
981
|
+
// console.log('this.originalConfig', this.originalConfig)
|
|
884
982
|
let originalValue = dotProp.get(this.originalConfig, thePath)
|
|
885
983
|
// TODO @DWELLS make recursive
|
|
886
984
|
if (!originalValue) {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
985
|
+
// Recurse up the tree until we find a value
|
|
986
|
+
let currentPathArray = leaf.path.slice(0, -1)
|
|
987
|
+
while (currentPathArray.length > 0 && !originalValue) {
|
|
988
|
+
const currentPath = currentPathArray.length > 1 ? currentPathArray.join('.') : currentPathArray[0]
|
|
989
|
+
// console.log('checking parent path:', currentPath)
|
|
990
|
+
originalValue = dotProp.get(this.originalConfig, currentPath)
|
|
991
|
+
if (typeof originalValue !== 'undefined') {
|
|
992
|
+
leaf.originalValuePath = currentPath
|
|
993
|
+
leaf.currentConfig = this.config
|
|
994
|
+
}
|
|
995
|
+
currentPathArray = currentPathArray.slice(0, -1)
|
|
996
|
+
}
|
|
890
997
|
}
|
|
891
998
|
leaf.originalSource = originalValue
|
|
892
999
|
if (originalValue && isString(originalValue)) {
|
|
@@ -913,11 +1020,27 @@ class Configorama {
|
|
|
913
1020
|
*/
|
|
914
1021
|
populateVariables(properties) {
|
|
915
1022
|
// console.log('properties', properties)
|
|
916
|
-
|
|
1023
|
+
let variables = properties.filter((property) => {
|
|
917
1024
|
// Initial check if value has variable string in it
|
|
918
1025
|
return isString(property.value) && property.value.match(this.variableSyntax)
|
|
919
1026
|
})
|
|
920
1027
|
|
|
1028
|
+
/*
|
|
1029
|
+
console.log(`variables at call count ${this.callCount}`, variables)
|
|
1030
|
+
/** */
|
|
1031
|
+
|
|
1032
|
+
/* Exclude git messages from being processed */
|
|
1033
|
+
// Was failing on git msgs like "xyz cron:pattern to cron(pattern) for improved clarity"
|
|
1034
|
+
if (this.callCount > 1) {
|
|
1035
|
+
// filter out git vars
|
|
1036
|
+
variables = variables.filter(property => {
|
|
1037
|
+
if (property.originalSource && typeof property.originalSource === 'string') {
|
|
1038
|
+
return !property.originalSource.startsWith('${git:')
|
|
1039
|
+
}
|
|
1040
|
+
return true
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
|
|
921
1044
|
return map(variables, (valueObject) => {
|
|
922
1045
|
// console.log('valueObject', valueObject)
|
|
923
1046
|
return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
|
|
@@ -959,6 +1082,7 @@ class Configorama {
|
|
|
959
1082
|
}
|
|
960
1083
|
|
|
961
1084
|
const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
|
|
1085
|
+
this.leaves = leaves
|
|
962
1086
|
// console.log('leaves', leaves)
|
|
963
1087
|
const populations = this.populateVariables(leaves)
|
|
964
1088
|
// console.log("FILL LEAVES", populations)
|
|
@@ -1302,7 +1426,19 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1302
1426
|
|
|
1303
1427
|
if (property && typeof property === 'string') {
|
|
1304
1428
|
// console.log('property', property)
|
|
1305
|
-
|
|
1429
|
+
let prop = cleanVariable(
|
|
1430
|
+
property,
|
|
1431
|
+
this.variableSyntax,
|
|
1432
|
+
true,
|
|
1433
|
+
`populateVariable string ${this.callCount}`,
|
|
1434
|
+
// true // recursive
|
|
1435
|
+
)
|
|
1436
|
+
|
|
1437
|
+
// Double processing needed for `${eval(${self:three} > ${self:four})}`
|
|
1438
|
+
if (prop.startsWith('${')) {
|
|
1439
|
+
prop = cleanVariable(prop, this.variableSyntax, true, `populateVariable string ${this.callCount}`)
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1306
1442
|
// console.log('prop', prop)
|
|
1307
1443
|
if (property.match(/^> function /g) && prop) {
|
|
1308
1444
|
// console.log('func prop', property)
|
|
@@ -1341,7 +1477,10 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1341
1477
|
}
|
|
1342
1478
|
*/
|
|
1343
1479
|
// Does not match file refs with nested vars + args
|
|
1344
|
-
|
|
1480
|
+
// @TODO fix this for eval refs
|
|
1481
|
+
// console.log('prop', prop)
|
|
1482
|
+
// console.log('func', func)
|
|
1483
|
+
if (!prop.match(fileRefSyntax) && !prop.match(getValueFromEval.match) && func) {
|
|
1345
1484
|
// console.log('IS FUNCTION')
|
|
1346
1485
|
/* if matches function signature like ${merge('foo', 'bar')}
|
|
1347
1486
|
rewrite the variable to run the function after inputs resolved
|
|
@@ -1492,7 +1631,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1492
1631
|
if (filters) {
|
|
1493
1632
|
const string = cleanVariable(propertyString, this.variableSyntax, true, `getValueFromSrc filter ${this.callCount}`)
|
|
1494
1633
|
// console.log('string', string)
|
|
1495
|
-
const deeperValue =
|
|
1634
|
+
const deeperValue = getTextAfterOccurrence(string, variableString)
|
|
1496
1635
|
// console.log('deeperValue', deeperValue)
|
|
1497
1636
|
// console.log('filters', filters)
|
|
1498
1637
|
// console.log('variableString', variableString)
|
|
@@ -1845,7 +1984,7 @@ Unable to resolve configuration variable
|
|
|
1845
1984
|
return res
|
|
1846
1985
|
})
|
|
1847
1986
|
}
|
|
1848
|
-
getValueFromFile(variableString) {
|
|
1987
|
+
async getValueFromFile(variableString) {
|
|
1849
1988
|
// console.log('From file', `"${variableString}"`)
|
|
1850
1989
|
let matchedFileString = variableString.match(fileRefSyntax)[0]
|
|
1851
1990
|
// console.log('matchedFileString', matchedFileString)
|
|
@@ -1876,7 +2015,11 @@ Unable to resolve configuration variable
|
|
|
1876
2015
|
matchedFileString.replace(fileRefSyntax, (match, varName) => varName.trim()).replace('~', os.homedir()),
|
|
1877
2016
|
)
|
|
1878
2017
|
|
|
1879
|
-
|
|
2018
|
+
// Resolve alias if the path contains alias syntax
|
|
2019
|
+
const resolvedPath = resolveAlias(relativePath, this.configPath)
|
|
2020
|
+
// console.log('resolvedPath', resolvedPath)
|
|
2021
|
+
|
|
2022
|
+
let fullFilePath = path.isAbsolute(resolvedPath) ? resolvedPath : path.join(this.configPath, resolvedPath)
|
|
1880
2023
|
|
|
1881
2024
|
// console.log('fullFilePath', fullFilePath)
|
|
1882
2025
|
|
|
@@ -1885,13 +2028,13 @@ Unable to resolve configuration variable
|
|
|
1885
2028
|
fullFilePath = fs.realpathSync(fullFilePath)
|
|
1886
2029
|
|
|
1887
2030
|
// Only match files that are relative
|
|
1888
|
-
} else if (
|
|
2031
|
+
} else if (resolvedPath.match(/\.\//)) {
|
|
1889
2032
|
// TODO test higher parent refs
|
|
1890
|
-
const cleanName = path.basename(
|
|
2033
|
+
const cleanName = path.basename(resolvedPath)
|
|
1891
2034
|
fullFilePath = findUp.sync(cleanName, { cwd: this.configPath })
|
|
1892
2035
|
}
|
|
1893
2036
|
|
|
1894
|
-
let fileExtension =
|
|
2037
|
+
let fileExtension = resolvedPath.split('.')
|
|
1895
2038
|
|
|
1896
2039
|
fileExtension = fileExtension[fileExtension.length - 1]
|
|
1897
2040
|
|
|
@@ -1941,6 +2084,7 @@ Check if your javascript is exporting a function that returns a value.`
|
|
|
1941
2084
|
config: this.config,
|
|
1942
2085
|
opts: this.opts,
|
|
1943
2086
|
}
|
|
2087
|
+
|
|
1944
2088
|
|
|
1945
2089
|
valueToPopulate = returnValueFunction.call(jsFile, valueForFunction, ...argsToPass)
|
|
1946
2090
|
|
|
@@ -1963,8 +2107,115 @@ Check if your javascript is returning the correct data.`
|
|
|
1963
2107
|
})
|
|
1964
2108
|
}
|
|
1965
2109
|
|
|
1966
|
-
|
|
1967
|
-
if (fileExtension
|
|
2110
|
+
|
|
2111
|
+
if (fileExtension === 'ts') {
|
|
2112
|
+
const { executeTypeScriptFile } = require('./parsers/typescript')
|
|
2113
|
+
let returnValueFunction
|
|
2114
|
+
const variableArray = variableString.split(':')
|
|
2115
|
+
|
|
2116
|
+
try {
|
|
2117
|
+
const tsFile = await executeTypeScriptFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
2118
|
+
// console.log('fullFilePath', fullFilePath)
|
|
2119
|
+
// console.log('tsFile', tsFile)
|
|
2120
|
+
returnValueFunction = tsFile.config || tsFile.default || tsFile
|
|
2121
|
+
|
|
2122
|
+
if (variableArray[1]) {
|
|
2123
|
+
let tsModule = variableArray[1]
|
|
2124
|
+
tsModule = tsModule.split('.')[0]
|
|
2125
|
+
returnValueFunction = tsFile[tsModule]
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
if (typeof returnValueFunction !== 'function') {
|
|
2129
|
+
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
2130
|
+
Check if your TypeScript is exporting a function that returns a value.`
|
|
2131
|
+
return Promise.reject(new Error(errorMessage))
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
const valueForFunction = {
|
|
2135
|
+
originalConfig: this.originalConfig,
|
|
2136
|
+
config: this.config,
|
|
2137
|
+
opts: this.opts,
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
valueToPopulate = returnValueFunction.call(tsFile, valueForFunction, ...argsToPass)
|
|
2141
|
+
|
|
2142
|
+
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
2143
|
+
let deepProperties = variableString.replace(matchedFileString, '')
|
|
2144
|
+
deepProperties = deepProperties.slice(1).split('.')
|
|
2145
|
+
deepProperties.splice(0, 1)
|
|
2146
|
+
// Trim prop keys for starting/trailing spaces
|
|
2147
|
+
deepProperties = deepProperties.map((prop) => {
|
|
2148
|
+
return trim(prop)
|
|
2149
|
+
})
|
|
2150
|
+
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
2151
|
+
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
2152
|
+
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
2153
|
+
Check if your TypeScript is returning the correct data.`
|
|
2154
|
+
return Promise.reject(new Error(errorMessage))
|
|
2155
|
+
}
|
|
2156
|
+
return Promise.resolve(deepValueToPopulateResolved)
|
|
2157
|
+
})
|
|
2158
|
+
})
|
|
2159
|
+
} catch (err) {
|
|
2160
|
+
return Promise.reject(new Error(`Error processing TypeScript file: ${err.message}`))
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
if (fileExtension === 'mjs' || fileExtension === 'esm') {
|
|
2165
|
+
const { executeESMFile } = require('./parsers/esm')
|
|
2166
|
+
let returnValueFunction
|
|
2167
|
+
const variableArray = variableString.split(':')
|
|
2168
|
+
|
|
2169
|
+
try {
|
|
2170
|
+
const esmFile = await executeESMFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
2171
|
+
// console.log('ESM fullFilePath', fullFilePath)
|
|
2172
|
+
// console.log('ESM esmFile', esmFile, 'type:', typeof esmFile)
|
|
2173
|
+
returnValueFunction = esmFile.config || esmFile.default || esmFile
|
|
2174
|
+
|
|
2175
|
+
if (variableArray[1]) {
|
|
2176
|
+
let esmModule = variableArray[1]
|
|
2177
|
+
esmModule = esmModule.split('.')[0]
|
|
2178
|
+
returnValueFunction = esmFile[esmModule]
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
if (typeof returnValueFunction !== 'function') {
|
|
2182
|
+
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
2183
|
+
Check if your ESM is exporting a function that returns a value.`
|
|
2184
|
+
return Promise.reject(new Error(errorMessage))
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
const valueForFunction = {
|
|
2188
|
+
originalConfig: this.originalConfig,
|
|
2189
|
+
config: this.config,
|
|
2190
|
+
opts: this.opts,
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
valueToPopulate = returnValueFunction.call(esmFile, valueForFunction, ...argsToPass)
|
|
2194
|
+
|
|
2195
|
+
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
2196
|
+
let deepProperties = variableString.replace(matchedFileString, '')
|
|
2197
|
+
deepProperties = deepProperties.slice(1).split('.')
|
|
2198
|
+
deepProperties.splice(0, 1)
|
|
2199
|
+
// Trim prop keys for starting/trailing spaces
|
|
2200
|
+
deepProperties = deepProperties.map((prop) => {
|
|
2201
|
+
return trim(prop)
|
|
2202
|
+
})
|
|
2203
|
+
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
2204
|
+
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
2205
|
+
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
2206
|
+
Check if your ESM is returning the correct data.`
|
|
2207
|
+
return Promise.reject(new Error(errorMessage))
|
|
2208
|
+
}
|
|
2209
|
+
return Promise.resolve(deepValueToPopulateResolved)
|
|
2210
|
+
})
|
|
2211
|
+
})
|
|
2212
|
+
} catch (err) {
|
|
2213
|
+
return Promise.reject(new Error(`Error processing ESM file: ${err.message}`))
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// Process everything except JS, TS, and ESM
|
|
2218
|
+
if (fileExtension !== 'js' && fileExtension !== 'ts' && fileExtension !== 'mjs' && fileExtension !== 'esm') {
|
|
1968
2219
|
/* Read initial file */
|
|
1969
2220
|
valueToPopulate = fs.readFileSync(fullFilePath, 'utf-8')
|
|
1970
2221
|
|
|
@@ -1976,6 +2227,9 @@ Check if your javascript is returning the correct data.`
|
|
|
1976
2227
|
if (fileExtension === 'toml') {
|
|
1977
2228
|
valueToPopulate = JSON.stringify(TOML.parse(valueToPopulate))
|
|
1978
2229
|
}
|
|
2230
|
+
if (fileExtension === 'ini') {
|
|
2231
|
+
valueToPopulate = INI.toJson(valueToPopulate)
|
|
2232
|
+
}
|
|
1979
2233
|
// console.log('deep', variableString)
|
|
1980
2234
|
// console.log('matchedFileString', matchedFileString)
|
|
1981
2235
|
let deepProperties = variableString.replace(matchedFileString, '')
|
|
@@ -1998,11 +2252,17 @@ Please use ":" to reference sub properties`
|
|
|
1998
2252
|
return Promise.resolve(valueToPopulate)
|
|
1999
2253
|
}
|
|
2000
2254
|
|
|
2255
|
+
if (fileExtension === 'ini') {
|
|
2256
|
+
valueToPopulate = INI.parse(valueToPopulate)
|
|
2257
|
+
return Promise.resolve(valueToPopulate)
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2001
2260
|
if (fileExtension === 'json') {
|
|
2002
2261
|
valueToPopulate = JSON.parse(valueToPopulate)
|
|
2003
2262
|
return Promise.resolve(valueToPopulate)
|
|
2004
2263
|
}
|
|
2005
2264
|
}
|
|
2265
|
+
console.log('fall thru', valueToPopulate)
|
|
2006
2266
|
return Promise.resolve(valueToPopulate)
|
|
2007
2267
|
}
|
|
2008
2268
|
getVariableFromDeep(variableString) {
|