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/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 { getTextAfterOccurance, findNestedVariable } = require('./utils/textUtils')
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\((~?[\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
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
- // set config objects
162
- this.config = configObject
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(this.variableTypes.filter((v) => v.type !== 'string').map((v) => v.match))
292
- // console.log('variablesKnownTypes', variablesKnownTypes)
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], false] // Return array instead of just the value
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 (hasResolvedFallback) {
482
- varData.defaultValue = hasResolvedFallback
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
- if (!varData.defaultValue) {
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.forEach((key, i) => {
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 (!firstInstance.defaultValue) {
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) => d.varType === 'dot.prop')
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
- requiredText = '[Required] '
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
- const defaultValueText = `${indent}${keyChalk(`DefaultValue:`.padEnd(titleText.length, ' '))}`
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(firstInstance.defaultValue)}`
707
+ varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}`
608
708
  }
609
709
 
610
- if(defaultValueSrc) {
611
- varMsg += `\n${indent}${keyChalk('DefaultValue path:'.padEnd(titleText.length, ' '))} `
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('Used at:'.padEnd(titleText.length, ' '))}`
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('Used at:')}`
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
- console.log(makeBox(varMsg, {
634
- title: `${key}`,
635
- borderColor: 'gray',
636
- // style: 'bold',
637
- minWidth: 120,
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
- Config error:
720
- "${configValuePath}" resolved to "undefined"
721
- Verify the ${varDisplay} in config at "${configValuePath}"`
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
- const parentArray = leaf.path.slice(0, -1)
888
- const parentPath = parentArray > 1 ? parentArray.join('.') : parentArray[0]
889
- originalValue = dotProp.get(this.originalConfig, parentPath)
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
- const variables = properties.filter((property) => {
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
- const prop = cleanVariable(property, this.variableSyntax, true, `populateVariable string ${this.callCount}`)
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
- if (!prop.match(/file\((~?[a-zA-Z0-9._\-\/,'"\{\}\.$: ]+?)\)/) && func) {
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 = getTextAfterOccurance(string, variableString)
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
- let fullFilePath = path.isAbsolute(relativePath) ? relativePath : path.join(this.configPath, relativePath)
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 (relativePath.match(/\.\//)) {
2031
+ } else if (resolvedPath.match(/\.\//)) {
1889
2032
  // TODO test higher parent refs
1890
- const cleanName = path.basename(relativePath)
2033
+ const cleanName = path.basename(resolvedPath)
1891
2034
  fullFilePath = findUp.sync(cleanName, { cwd: this.configPath })
1892
2035
  }
1893
2036
 
1894
- let fileExtension = relativePath.split('.')
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
- // Process everything except JS
1967
- if (fileExtension !== 'js') {
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) {