configorama 0.6.3 → 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 +18 -9
- package/src/main.js +369 -108
- 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,44 +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
|
-
|
|
168
|
-
const useDotEnv = this.originalConfig.useDotenv || this.originalConfig.useDotEnv
|
|
169
|
-
if ((useDotEnv && useDotEnv === true) || this.opts.useDotEnvFiles) {
|
|
170
|
-
const loadStageEnv = require('env-stage-loader')
|
|
171
|
-
let providerStage
|
|
172
|
-
if (this.originalConfig && this.originalConfig.provider && this.originalConfig.provider.stage) {
|
|
173
|
-
providerStage = this.originalConfig.provider.stage
|
|
174
|
-
// @TODO check value to see if variable and needs pre-resolving to resolve stage vars
|
|
175
|
-
}
|
|
176
|
-
const stage = this.opts.stage || providerStage || 'dev'
|
|
177
|
-
/* Load env variables into process.env */
|
|
178
|
-
const values = loadStageEnv({
|
|
179
|
-
// silent: true,
|
|
180
|
-
// debug: true,
|
|
181
|
-
env: stage,
|
|
182
|
-
// defaultEnv: 'prod',
|
|
183
|
-
// ignoreFiles: ['.env']
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
|
|
187
159
|
// Set configPath for file references
|
|
188
160
|
this.configPath = fileDirectory
|
|
161
|
+
// Initialize config as null - will be populated in init
|
|
162
|
+
this.config = null
|
|
163
|
+
this.originalConfig = null
|
|
189
164
|
}
|
|
190
165
|
|
|
191
166
|
// Track promise resolution
|
|
@@ -207,6 +182,23 @@ class Configorama {
|
|
|
207
182
|
* ${opt:other, "fallbackValue"}
|
|
208
183
|
*/
|
|
209
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
|
+
|
|
210
202
|
/**
|
|
211
203
|
* Self references
|
|
212
204
|
* Usage:
|
|
@@ -284,6 +276,7 @@ class Configorama {
|
|
|
284
276
|
return deeperExists
|
|
285
277
|
}
|
|
286
278
|
}
|
|
279
|
+
// console.log('fallthrough fullObject', fullObject)
|
|
287
280
|
/* is simple ${whatever} reference in same file */
|
|
288
281
|
const startOf = varString.split('.')
|
|
289
282
|
return fullObject[startOf[0]]
|
|
@@ -308,8 +301,9 @@ class Configorama {
|
|
|
308
301
|
this.variableTypes = this.variableTypes.concat(fallThroughSelfMatcher)
|
|
309
302
|
|
|
310
303
|
// const variablesKnownTypes = new RegExp(`^(${this.variableTypes.map((v) => v.prefix || v.type).join('|')}):`)
|
|
311
|
-
const variablesKnownTypes = combineRegexes(
|
|
312
|
-
|
|
304
|
+
const variablesKnownTypes = combineRegexes(
|
|
305
|
+
this.variableTypes.filter((v) => v.type !== 'string').map((v) => v.match)
|
|
306
|
+
)
|
|
313
307
|
this.variablesKnownTypes = variablesKnownTypes
|
|
314
308
|
|
|
315
309
|
// this.allPatterns = combineRegexes(...this.variableTypes.map((v) => v.match))
|
|
@@ -430,6 +424,49 @@ class Configorama {
|
|
|
430
424
|
this.functions = Object.assign({}, this.functions, options.functions)
|
|
431
425
|
}
|
|
432
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
|
+
|
|
433
470
|
if (VERBOSE) {
|
|
434
471
|
logHeader('Config Input before processing')
|
|
435
472
|
console.log()
|
|
@@ -437,10 +474,14 @@ class Configorama {
|
|
|
437
474
|
console.log()
|
|
438
475
|
}
|
|
439
476
|
|
|
477
|
+
const variableSyntax = this.variableSyntax
|
|
478
|
+
const variablesKnownTypes = this.variablesKnownTypes
|
|
479
|
+
|
|
440
480
|
if (VERBOSE || showFoundVariables) {
|
|
441
481
|
const foundVariables = []
|
|
442
482
|
const variableData = {}
|
|
443
483
|
let matchCount = 1
|
|
484
|
+
// console.log('this.originalConfig', this.originalConfig)
|
|
444
485
|
traverse(this.originalConfig).forEach(function (rawValue) {
|
|
445
486
|
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
446
487
|
const configValuePath = this.path.join('.')
|
|
@@ -472,45 +513,63 @@ class Configorama {
|
|
|
472
513
|
resolveOrder: [],
|
|
473
514
|
resolveDetails: nested,
|
|
474
515
|
}
|
|
475
|
-
|
|
516
|
+
let defaultValueIsVar = false
|
|
476
517
|
function calculateResolveOrder(item) {
|
|
477
518
|
if (item && item.fallbackValues) {
|
|
478
519
|
let hasResolvedFallback
|
|
520
|
+
// console.log('item.fallbackValues', item.fallbackValues)
|
|
479
521
|
const order = ([item.valueBeforeFallback]).concat(item.fallbackValues.map((f, i) => {
|
|
522
|
+
// console.log('f', f)
|
|
480
523
|
if (f.fallbackValues) {
|
|
481
524
|
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
525
|
+
// console.log('nestedOrder', nestedOrder)
|
|
526
|
+
// console.log('nestedResolvedFallback', nestedResolvedFallback)
|
|
482
527
|
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
483
528
|
hasResolvedFallback = nestedResolvedFallback
|
|
484
529
|
}
|
|
485
530
|
return nestedOrder // Return just the order part
|
|
486
531
|
}
|
|
532
|
+
|
|
487
533
|
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
488
534
|
hasResolvedFallback = f.stringValue
|
|
489
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)
|
|
490
544
|
return `${f.stringValue || f.variable}${f.isResolvedFallback ? ' (Resolved default fallback)' : ''}`
|
|
491
545
|
})).flat()
|
|
492
546
|
|
|
493
547
|
return [order, hasResolvedFallback]
|
|
494
548
|
}
|
|
495
|
-
return [[item.variable],
|
|
549
|
+
return [[item.variable], undefined] // Return array instead of just the value
|
|
496
550
|
}
|
|
497
551
|
|
|
498
552
|
const [resolveOrder, hasResolvedFallback] = calculateResolveOrder(lastItem)
|
|
499
553
|
varData.resolveOrder = resolveOrder
|
|
500
554
|
|
|
501
|
-
if (
|
|
502
|
-
varData.
|
|
555
|
+
if (defaultValueIsVar) {
|
|
556
|
+
varData.defaultValueIsVar = defaultValueIsVar
|
|
503
557
|
}
|
|
504
558
|
|
|
559
|
+
// console.log('hasResolvedFallback', hasResolvedFallback)
|
|
560
|
+
if (typeof hasResolvedFallback !== 'undefined') {
|
|
561
|
+
varData.defaultValue = hasResolvedFallback
|
|
562
|
+
}
|
|
505
563
|
|
|
506
|
-
|
|
564
|
+
// console.log('varData.defaultValue', varData.defaultValue)
|
|
565
|
+
if (typeof varData.defaultValue === 'undefined') {
|
|
507
566
|
varData.isRequired = true
|
|
508
567
|
}
|
|
509
568
|
|
|
510
|
-
|
|
511
569
|
if (varData.resolveOrder.length > 1) {
|
|
512
570
|
varData.hasFallback = true
|
|
513
571
|
}
|
|
572
|
+
//console.log('varData', varData)
|
|
514
573
|
|
|
515
574
|
variableData[key] = (variableData[key] || []).concat(varData)
|
|
516
575
|
|
|
@@ -518,7 +577,6 @@ class Configorama {
|
|
|
518
577
|
}
|
|
519
578
|
})
|
|
520
579
|
|
|
521
|
-
|
|
522
580
|
if (!foundVariables.length) {
|
|
523
581
|
logHeader('No Variables Found in Config')
|
|
524
582
|
if (this.configFilePath) {
|
|
@@ -565,20 +623,35 @@ class Configorama {
|
|
|
565
623
|
}
|
|
566
624
|
|
|
567
625
|
logHeader('Variable Details')
|
|
626
|
+
|
|
627
|
+
const lines = this.configFileContents.split('\n')
|
|
628
|
+
// console.log('lines', lines)
|
|
568
629
|
|
|
569
630
|
const indent = ''
|
|
570
|
-
varKeys.
|
|
631
|
+
const boxes = varKeys.map((key, i) => {
|
|
571
632
|
const variableInstances = variableData[key]
|
|
633
|
+
// console.log('variableInstances', variableInstances)
|
|
572
634
|
|
|
573
635
|
const firstInstance = variableInstances[0]
|
|
574
636
|
|
|
575
637
|
let requiredText = ''
|
|
576
638
|
let defaultValueSrc = ''
|
|
577
|
-
if (
|
|
639
|
+
if (typeof firstInstance.defaultValue === 'undefined') {
|
|
578
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
|
+
}
|
|
579
649
|
/* Check if the fallback variable is a self reference */
|
|
580
650
|
const hasDotPropOrSelf = variableInstances.reduce((acc, v) => {
|
|
581
|
-
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
|
+
})
|
|
582
655
|
if (dotProp) {
|
|
583
656
|
acc.push(dotProp)
|
|
584
657
|
}
|
|
@@ -587,10 +660,12 @@ class Configorama {
|
|
|
587
660
|
acc.push(v.resolveDetails[0])
|
|
588
661
|
}
|
|
589
662
|
return acc
|
|
590
|
-
},
|
|
663
|
+
}, dotPropArr)
|
|
591
664
|
// console.log('hasDotPropOrSelf', hasDotPropOrSelf)
|
|
665
|
+
|
|
592
666
|
if (!hasDotPropOrSelf.length) {
|
|
593
|
-
|
|
667
|
+
const debug = (false) ? JSON.stringify(firstInstance, null, 2) : ''
|
|
668
|
+
requiredText = `[Required Variable] ${debug}`
|
|
594
669
|
} else {
|
|
595
670
|
const fallBackValues = variableInstances.filter((v) => v.resolveDetails.find((d) => d.hasFallback)).map((v) => v.resolveDetails)
|
|
596
671
|
// console.log('fallBackValues', fallBackValues)
|
|
@@ -609,6 +684,9 @@ class Configorama {
|
|
|
609
684
|
// truncate niceString to 100 characters
|
|
610
685
|
const truncatedString = niceString.length > 100 ? niceString.substring(0, 90) + '...' : niceString
|
|
611
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`)
|
|
612
690
|
}
|
|
613
691
|
}
|
|
614
692
|
//this.originalConfig[key] = undefined
|
|
@@ -621,14 +699,16 @@ class Configorama {
|
|
|
621
699
|
const keyChalk = chalk.whiteBright
|
|
622
700
|
const valueChalk = chalk.hex(VALUE_HEX)
|
|
623
701
|
|
|
624
|
-
if (firstInstance.defaultValue) {
|
|
625
|
-
|
|
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, ' '))}`
|
|
626
706
|
// ensure padding is even
|
|
627
|
-
varMsg += `${defaultValueText} ${valueChalk(
|
|
707
|
+
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}`
|
|
628
708
|
}
|
|
629
709
|
|
|
630
|
-
if(defaultValueSrc) {
|
|
631
|
-
varMsg += `\n${indent}${keyChalk('
|
|
710
|
+
if (defaultValueSrc) {
|
|
711
|
+
varMsg += `\n${indent}${keyChalk('Default value path:'.padEnd(titleText.length, ' '))} `
|
|
632
712
|
varMsg += `${valueChalk(defaultValueSrc)}`
|
|
633
713
|
}
|
|
634
714
|
|
|
@@ -640,26 +720,34 @@ class Configorama {
|
|
|
640
720
|
|
|
641
721
|
let locationRender = valueChalk(variableInstances[0].path)
|
|
642
722
|
|
|
643
|
-
let locationLabel = `${indent}${keyChalk('
|
|
723
|
+
let locationLabel = `${indent}${keyChalk('Path:'.padEnd(titleText.length, ' '))}`
|
|
644
724
|
if (variableInstances.length > 1) {
|
|
645
725
|
locationRender = `\n${variableInstances.map((v) => valueChalk(`${indent}- ${v.path}`)).join('\n')}`
|
|
646
|
-
const locationLabelText = `${indent}${keyChalk('
|
|
726
|
+
const locationLabelText = `${indent}${keyChalk('Paths:')}`
|
|
647
727
|
locationLabel = locationLabelText
|
|
648
728
|
}
|
|
649
729
|
|
|
650
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
|
|
651
735
|
|
|
652
736
|
// console.log(` ${chalk.bold(key)}`)
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if(i < varKeys.length - 1) {
|
|
660
|
-
//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
|
+
},
|
|
661
743
|
}
|
|
662
744
|
})
|
|
745
|
+
|
|
746
|
+
console.log(makeStackedBoxes(boxes, {
|
|
747
|
+
borderColor: 'gray',
|
|
748
|
+
minWidth: 120,
|
|
749
|
+
borderStyle: 'bold',
|
|
750
|
+
}))
|
|
663
751
|
}
|
|
664
752
|
|
|
665
753
|
/* Exit early if list or info flag is set */
|
|
@@ -668,33 +756,32 @@ class Configorama {
|
|
|
668
756
|
}
|
|
669
757
|
}
|
|
670
758
|
|
|
671
|
-
this.deep = []
|
|
672
|
-
this.callCount = 0
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
initialCall(func) {
|
|
676
|
-
this.deep = []
|
|
677
|
-
this.tracker.start()
|
|
678
|
-
return func().finally(() => {
|
|
679
|
-
this.tracker.stop()
|
|
680
|
-
this.deep = []
|
|
681
|
-
})
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Populate all variables in the service, conveniently remove and restore the service attributes
|
|
686
|
-
* that confuse the population methods.
|
|
687
|
-
* @param cliOpts An options hive to use for ${opt:...} variables.
|
|
688
|
-
* @returns {Promise.<TResult>|*} A promise resolving to the populated service.
|
|
689
|
-
*/
|
|
690
|
-
init(cliOpts) {
|
|
691
|
-
this.options = cliOpts || {}
|
|
692
|
-
const configoramaOpts = this.opts
|
|
693
759
|
const originalConfig = this.originalConfig
|
|
694
760
|
|
|
695
761
|
/* If no variables found just return early */
|
|
696
762
|
if (this.originalString && !this.originalString.match(this.variableSyntax)) {
|
|
697
|
-
return Promise.resolve(originalConfig)
|
|
763
|
+
return Promise.resolve(this.originalConfig)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const useDotEnv = this.originalConfig.useDotenv || this.originalConfig.useDotEnv
|
|
767
|
+
if ((useDotEnv && useDotEnv === true) || this.opts.useDotEnvFiles) {
|
|
768
|
+
let providerStage
|
|
769
|
+
/* has hardcoded stage */
|
|
770
|
+
if (
|
|
771
|
+
this.originalConfig && this.originalConfig.provider &&
|
|
772
|
+
this.originalConfig.provider.stage && !this.originalConfig.provider.stage.match(this.variableSyntax)
|
|
773
|
+
) {
|
|
774
|
+
providerStage = this.originalConfig.provider.stage
|
|
775
|
+
}
|
|
776
|
+
const stage = cliOpts.stage || providerStage || process.env.NODE_ENV || 'dev'
|
|
777
|
+
/* Load env variables into process.env */
|
|
778
|
+
const values = require('env-stage-loader')({
|
|
779
|
+
// silent: true,
|
|
780
|
+
// debug: true,
|
|
781
|
+
env: stage,
|
|
782
|
+
// defaultEnv: 'prod',
|
|
783
|
+
// ignoreFiles: ['.env']
|
|
784
|
+
})
|
|
698
785
|
}
|
|
699
786
|
|
|
700
787
|
/* Parse variables */
|
|
@@ -706,18 +793,28 @@ class Configorama {
|
|
|
706
793
|
// console.log('Final Config', this.config)
|
|
707
794
|
const transform = this.runFunction.bind(this)
|
|
708
795
|
const varSyntax = this.variableSyntax
|
|
796
|
+
const leaves = this.leaves
|
|
797
|
+
// console.log('leaves two', leaves)
|
|
709
798
|
// Traverse resolved object and run functions
|
|
710
799
|
// console.log('this.config', this.config)
|
|
711
800
|
traverse(this.config).forEach(function (rawValue) {
|
|
712
801
|
/* Pass through unknown variables */
|
|
713
802
|
if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
|
|
714
803
|
const configValuePath = this.path.join('.')
|
|
804
|
+
console.log(this.path)
|
|
715
805
|
const ogValue = dotProp.get(originalConfig, configValuePath)
|
|
716
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
|
+
// }
|
|
717
812
|
const errorMessage = `
|
|
718
|
-
|
|
719
|
-
"${configValuePath}" resolved to "undefined"
|
|
720
|
-
|
|
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` : ''}`
|
|
721
818
|
throw new Error(errorMessage)
|
|
722
819
|
}
|
|
723
820
|
if (typeof rawValue === 'string') {
|
|
@@ -782,7 +879,7 @@ class Configorama {
|
|
|
782
879
|
var hasFunc = funcRegex.exec(variableString)
|
|
783
880
|
// TODO finish Function handling. Need to move this down below resolver to resolve inner refs first
|
|
784
881
|
// console.log('hasFunc', hasFunc)
|
|
785
|
-
if (!hasFunc) {
|
|
882
|
+
if (!hasFunc || hasFunc && (hasFunc[1] === 'cron' || hasFunc[1] === 'eval')) {
|
|
786
883
|
return variableString
|
|
787
884
|
}
|
|
788
885
|
// test for object
|
|
@@ -880,12 +977,23 @@ class Configorama {
|
|
|
880
977
|
value: current,
|
|
881
978
|
}
|
|
882
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)
|
|
883
982
|
let originalValue = dotProp.get(this.originalConfig, thePath)
|
|
884
983
|
// TODO @DWELLS make recursive
|
|
885
984
|
if (!originalValue) {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
+
}
|
|
889
997
|
}
|
|
890
998
|
leaf.originalSource = originalValue
|
|
891
999
|
if (originalValue && isString(originalValue)) {
|
|
@@ -912,11 +1020,27 @@ class Configorama {
|
|
|
912
1020
|
*/
|
|
913
1021
|
populateVariables(properties) {
|
|
914
1022
|
// console.log('properties', properties)
|
|
915
|
-
|
|
1023
|
+
let variables = properties.filter((property) => {
|
|
916
1024
|
// Initial check if value has variable string in it
|
|
917
1025
|
return isString(property.value) && property.value.match(this.variableSyntax)
|
|
918
1026
|
})
|
|
919
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
|
+
|
|
920
1044
|
return map(variables, (valueObject) => {
|
|
921
1045
|
// console.log('valueObject', valueObject)
|
|
922
1046
|
return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
|
|
@@ -958,6 +1082,7 @@ class Configorama {
|
|
|
958
1082
|
}
|
|
959
1083
|
|
|
960
1084
|
const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
|
|
1085
|
+
this.leaves = leaves
|
|
961
1086
|
// console.log('leaves', leaves)
|
|
962
1087
|
const populations = this.populateVariables(leaves)
|
|
963
1088
|
// console.log("FILL LEAVES", populations)
|
|
@@ -1301,7 +1426,19 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1301
1426
|
|
|
1302
1427
|
if (property && typeof property === 'string') {
|
|
1303
1428
|
// console.log('property', property)
|
|
1304
|
-
|
|
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
|
+
|
|
1305
1442
|
// console.log('prop', prop)
|
|
1306
1443
|
if (property.match(/^> function /g) && prop) {
|
|
1307
1444
|
// console.log('func prop', property)
|
|
@@ -1340,7 +1477,10 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1340
1477
|
}
|
|
1341
1478
|
*/
|
|
1342
1479
|
// Does not match file refs with nested vars + args
|
|
1343
|
-
|
|
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) {
|
|
1344
1484
|
// console.log('IS FUNCTION')
|
|
1345
1485
|
/* if matches function signature like ${merge('foo', 'bar')}
|
|
1346
1486
|
rewrite the variable to run the function after inputs resolved
|
|
@@ -1491,7 +1631,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1491
1631
|
if (filters) {
|
|
1492
1632
|
const string = cleanVariable(propertyString, this.variableSyntax, true, `getValueFromSrc filter ${this.callCount}`)
|
|
1493
1633
|
// console.log('string', string)
|
|
1494
|
-
const deeperValue =
|
|
1634
|
+
const deeperValue = getTextAfterOccurrence(string, variableString)
|
|
1495
1635
|
// console.log('deeperValue', deeperValue)
|
|
1496
1636
|
// console.log('filters', filters)
|
|
1497
1637
|
// console.log('variableString', variableString)
|
|
@@ -1844,7 +1984,7 @@ Unable to resolve configuration variable
|
|
|
1844
1984
|
return res
|
|
1845
1985
|
})
|
|
1846
1986
|
}
|
|
1847
|
-
getValueFromFile(variableString) {
|
|
1987
|
+
async getValueFromFile(variableString) {
|
|
1848
1988
|
// console.log('From file', `"${variableString}"`)
|
|
1849
1989
|
let matchedFileString = variableString.match(fileRefSyntax)[0]
|
|
1850
1990
|
// console.log('matchedFileString', matchedFileString)
|
|
@@ -1875,7 +2015,11 @@ Unable to resolve configuration variable
|
|
|
1875
2015
|
matchedFileString.replace(fileRefSyntax, (match, varName) => varName.trim()).replace('~', os.homedir()),
|
|
1876
2016
|
)
|
|
1877
2017
|
|
|
1878
|
-
|
|
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)
|
|
1879
2023
|
|
|
1880
2024
|
// console.log('fullFilePath', fullFilePath)
|
|
1881
2025
|
|
|
@@ -1884,13 +2028,13 @@ Unable to resolve configuration variable
|
|
|
1884
2028
|
fullFilePath = fs.realpathSync(fullFilePath)
|
|
1885
2029
|
|
|
1886
2030
|
// Only match files that are relative
|
|
1887
|
-
} else if (
|
|
2031
|
+
} else if (resolvedPath.match(/\.\//)) {
|
|
1888
2032
|
// TODO test higher parent refs
|
|
1889
|
-
const cleanName = path.basename(
|
|
2033
|
+
const cleanName = path.basename(resolvedPath)
|
|
1890
2034
|
fullFilePath = findUp.sync(cleanName, { cwd: this.configPath })
|
|
1891
2035
|
}
|
|
1892
2036
|
|
|
1893
|
-
let fileExtension =
|
|
2037
|
+
let fileExtension = resolvedPath.split('.')
|
|
1894
2038
|
|
|
1895
2039
|
fileExtension = fileExtension[fileExtension.length - 1]
|
|
1896
2040
|
|
|
@@ -1940,6 +2084,7 @@ Check if your javascript is exporting a function that returns a value.`
|
|
|
1940
2084
|
config: this.config,
|
|
1941
2085
|
opts: this.opts,
|
|
1942
2086
|
}
|
|
2087
|
+
|
|
1943
2088
|
|
|
1944
2089
|
valueToPopulate = returnValueFunction.call(jsFile, valueForFunction, ...argsToPass)
|
|
1945
2090
|
|
|
@@ -1962,8 +2107,115 @@ Check if your javascript is returning the correct data.`
|
|
|
1962
2107
|
})
|
|
1963
2108
|
}
|
|
1964
2109
|
|
|
1965
|
-
|
|
1966
|
-
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') {
|
|
1967
2219
|
/* Read initial file */
|
|
1968
2220
|
valueToPopulate = fs.readFileSync(fullFilePath, 'utf-8')
|
|
1969
2221
|
|
|
@@ -1975,6 +2227,9 @@ Check if your javascript is returning the correct data.`
|
|
|
1975
2227
|
if (fileExtension === 'toml') {
|
|
1976
2228
|
valueToPopulate = JSON.stringify(TOML.parse(valueToPopulate))
|
|
1977
2229
|
}
|
|
2230
|
+
if (fileExtension === 'ini') {
|
|
2231
|
+
valueToPopulate = INI.toJson(valueToPopulate)
|
|
2232
|
+
}
|
|
1978
2233
|
// console.log('deep', variableString)
|
|
1979
2234
|
// console.log('matchedFileString', matchedFileString)
|
|
1980
2235
|
let deepProperties = variableString.replace(matchedFileString, '')
|
|
@@ -1997,11 +2252,17 @@ Please use ":" to reference sub properties`
|
|
|
1997
2252
|
return Promise.resolve(valueToPopulate)
|
|
1998
2253
|
}
|
|
1999
2254
|
|
|
2255
|
+
if (fileExtension === 'ini') {
|
|
2256
|
+
valueToPopulate = INI.parse(valueToPopulate)
|
|
2257
|
+
return Promise.resolve(valueToPopulate)
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2000
2260
|
if (fileExtension === 'json') {
|
|
2001
2261
|
valueToPopulate = JSON.parse(valueToPopulate)
|
|
2002
2262
|
return Promise.resolve(valueToPopulate)
|
|
2003
2263
|
}
|
|
2004
2264
|
}
|
|
2265
|
+
console.log('fall thru', valueToPopulate)
|
|
2005
2266
|
return Promise.resolve(valueToPopulate)
|
|
2006
2267
|
}
|
|
2007
2268
|
getVariableFromDeep(variableString) {
|