configorama 0.6.18 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +78 -2
  2. package/cli.js +1 -0
  3. package/index.d.ts +90 -37
  4. package/package.json +8 -2
  5. package/src/index.js +42 -14
  6. package/src/main.js +135 -62
  7. package/src/parsers/index.js +10 -0
  8. package/src/parsers/typescript.js +20 -43
  9. package/src/parsers/yaml.js +35 -2
  10. package/src/resolvers/valueFromFile.js +87 -24
  11. package/src/resolvers/valueFromGit.js +11 -3
  12. package/src/utils/encoders/js-fixes.js +44 -0
  13. package/src/utils/parsing/mergeByKeys.js +4 -3
  14. package/src/utils/parsing/parse.js +4 -2
  15. package/src/utils/parsing/preProcess.js +42 -25
  16. package/src/utils/parsing/preProcess.test.js +214 -0
  17. package/src/utils/paths/getFullFilePath.js +1 -1
  18. package/src/utils/resolution/preResolveVariable.js +1 -0
  19. package/src/utils/strings/quoteUtils.js +2 -2
  20. package/src/utils/strings/splitCsv.js +1 -1
  21. package/src/utils/ui/logs.js +4 -4
  22. package/src/utils/validation/warnIfNotFound.js +3 -3
  23. package/src/utils/variables/findNestedVariables.js +2 -2
  24. package/src/utils/variables/variableUtils.js +43 -0
  25. package/src/utils/variables/variableUtils.test.js +38 -1
  26. package/types/cli.d.ts +3 -0
  27. package/types/cli.d.ts.map +1 -0
  28. package/types/src/functions/md5.d.ts +3 -0
  29. package/types/src/functions/md5.d.ts.map +1 -0
  30. package/types/src/index.d.ts +100 -0
  31. package/types/src/index.d.ts.map +1 -0
  32. package/types/src/main.d.ts +275 -0
  33. package/types/src/main.d.ts.map +1 -0
  34. package/types/src/parsers/esm.d.ts +15 -0
  35. package/types/src/parsers/esm.d.ts.map +1 -0
  36. package/types/src/parsers/hcl.d.ts +1 -0
  37. package/types/src/parsers/hcl.d.ts.map +1 -0
  38. package/types/src/parsers/index.d.ts +18 -0
  39. package/types/src/parsers/index.d.ts.map +1 -0
  40. package/types/src/parsers/ini.d.ts +6 -0
  41. package/types/src/parsers/ini.d.ts.map +1 -0
  42. package/types/src/parsers/json5.d.ts +10 -0
  43. package/types/src/parsers/json5.d.ts.map +1 -0
  44. package/types/src/parsers/toml.d.ts +7 -0
  45. package/types/src/parsers/toml.d.ts.map +1 -0
  46. package/types/src/parsers/typescript.d.ts +15 -0
  47. package/types/src/parsers/typescript.d.ts.map +1 -0
  48. package/types/src/parsers/yaml.d.ts +45 -0
  49. package/types/src/parsers/yaml.d.ts.map +1 -0
  50. package/types/src/resolvers/valueFromCron.d.ts +14 -0
  51. package/types/src/resolvers/valueFromCron.d.ts.map +1 -0
  52. package/types/src/resolvers/valueFromEnv.d.ts +8 -0
  53. package/types/src/resolvers/valueFromEnv.d.ts.map +1 -0
  54. package/types/src/resolvers/valueFromEval.d.ts +7 -0
  55. package/types/src/resolvers/valueFromEval.d.ts.map +1 -0
  56. package/types/src/resolvers/valueFromFile.d.ts +58 -0
  57. package/types/src/resolvers/valueFromFile.d.ts.map +1 -0
  58. package/types/src/resolvers/valueFromGit.d.ts +11 -0
  59. package/types/src/resolvers/valueFromGit.d.ts.map +1 -0
  60. package/types/src/resolvers/valueFromNumber.d.ts +6 -0
  61. package/types/src/resolvers/valueFromNumber.d.ts.map +1 -0
  62. package/types/src/resolvers/valueFromOptions.d.ts +9 -0
  63. package/types/src/resolvers/valueFromOptions.d.ts.map +1 -0
  64. package/types/src/resolvers/valueFromSelf.d.ts +1 -0
  65. package/types/src/resolvers/valueFromSelf.d.ts.map +1 -0
  66. package/types/src/resolvers/valueFromString.d.ts +6 -0
  67. package/types/src/resolvers/valueFromString.d.ts.map +1 -0
  68. package/types/src/sync.d.ts +3 -0
  69. package/types/src/sync.d.ts.map +1 -0
  70. package/types/src/utils/PromiseTracker.d.ts +19 -0
  71. package/types/src/utils/PromiseTracker.d.ts.map +1 -0
  72. package/types/src/utils/encoders/index.d.ts +2 -0
  73. package/types/src/utils/encoders/index.d.ts.map +1 -0
  74. package/types/src/utils/encoders/js-fixes.d.ts +23 -0
  75. package/types/src/utils/encoders/js-fixes.d.ts.map +1 -0
  76. package/types/src/utils/encoders/unknown-values.d.ts +18 -0
  77. package/types/src/utils/encoders/unknown-values.d.ts.map +1 -0
  78. package/types/src/utils/handleSignalEvents.d.ts +3 -0
  79. package/types/src/utils/handleSignalEvents.d.ts.map +1 -0
  80. package/types/src/utils/lodash.d.ts +4 -0
  81. package/types/src/utils/lodash.d.ts.map +1 -0
  82. package/types/src/utils/parsing/arrayToJsonPath.d.ts +5 -0
  83. package/types/src/utils/parsing/arrayToJsonPath.d.ts.map +1 -0
  84. package/types/src/utils/parsing/cloudformationSchema.d.ts +2 -0
  85. package/types/src/utils/parsing/cloudformationSchema.d.ts.map +1 -0
  86. package/types/src/utils/parsing/enrichMetadata.d.ts +17 -0
  87. package/types/src/utils/parsing/enrichMetadata.d.ts.map +1 -0
  88. package/types/src/utils/parsing/mergeByKeys.d.ts +5 -0
  89. package/types/src/utils/parsing/mergeByKeys.d.ts.map +1 -0
  90. package/types/src/utils/parsing/parse.d.ts +54 -0
  91. package/types/src/utils/parsing/parse.d.ts.map +1 -0
  92. package/types/src/utils/parsing/preProcess.d.ts +10 -0
  93. package/types/src/utils/parsing/preProcess.d.ts.map +1 -0
  94. package/types/src/utils/paths/filePathUtils.d.ts +32 -0
  95. package/types/src/utils/paths/filePathUtils.d.ts.map +1 -0
  96. package/types/src/utils/paths/findLineForKey.d.ts +12 -0
  97. package/types/src/utils/paths/findLineForKey.d.ts.map +1 -0
  98. package/types/src/utils/paths/findProjectRoot.d.ts +2 -0
  99. package/types/src/utils/paths/findProjectRoot.d.ts.map +1 -0
  100. package/types/src/utils/paths/getFullFilePath.d.ts +25 -0
  101. package/types/src/utils/paths/getFullFilePath.d.ts.map +1 -0
  102. package/types/src/utils/paths/resolveAlias.d.ts +14 -0
  103. package/types/src/utils/paths/resolveAlias.d.ts.map +1 -0
  104. package/types/src/utils/regex/index.d.ts +14 -0
  105. package/types/src/utils/regex/index.d.ts.map +1 -0
  106. package/types/src/utils/resolution/preResolveVariable.d.ts +51 -0
  107. package/types/src/utils/resolution/preResolveVariable.d.ts.map +1 -0
  108. package/types/src/utils/strings/bracketMatcher.d.ts +25 -0
  109. package/types/src/utils/strings/bracketMatcher.d.ts.map +1 -0
  110. package/types/src/utils/strings/formatFunctionArgs.d.ts +3 -0
  111. package/types/src/utils/strings/formatFunctionArgs.d.ts.map +1 -0
  112. package/types/src/utils/strings/quoteUtils.d.ts +31 -0
  113. package/types/src/utils/strings/quoteUtils.d.ts.map +1 -0
  114. package/types/src/utils/strings/replaceAll.d.ts +9 -0
  115. package/types/src/utils/strings/replaceAll.d.ts.map +1 -0
  116. package/types/src/utils/strings/splitByComma.d.ts +2 -0
  117. package/types/src/utils/strings/splitByComma.d.ts.map +1 -0
  118. package/types/src/utils/strings/splitCsv.d.ts +10 -0
  119. package/types/src/utils/strings/splitCsv.d.ts.map +1 -0
  120. package/types/src/utils/strings/textUtils.d.ts +15 -0
  121. package/types/src/utils/strings/textUtils.d.ts.map +1 -0
  122. package/types/src/utils/ui/chalk.d.ts +70 -0
  123. package/types/src/utils/ui/chalk.d.ts.map +1 -0
  124. package/types/src/utils/ui/configWizard.d.ts +67 -0
  125. package/types/src/utils/ui/configWizard.d.ts.map +1 -0
  126. package/types/src/utils/ui/createEditorLink.d.ts +11 -0
  127. package/types/src/utils/ui/createEditorLink.d.ts.map +1 -0
  128. package/types/src/utils/ui/deep-log.d.ts +3 -0
  129. package/types/src/utils/ui/deep-log.d.ts.map +1 -0
  130. package/types/src/utils/ui/logs.d.ts +2 -0
  131. package/types/src/utils/ui/logs.d.ts.map +1 -0
  132. package/types/src/utils/validation/warnIfNotFound.d.ts +15 -0
  133. package/types/src/utils/validation/warnIfNotFound.d.ts.map +1 -0
  134. package/types/src/utils/variables/appendDeepVariable.d.ts +3 -0
  135. package/types/src/utils/variables/appendDeepVariable.d.ts.map +1 -0
  136. package/types/src/utils/variables/cleanVariable.d.ts +3 -0
  137. package/types/src/utils/variables/cleanVariable.d.ts.map +1 -0
  138. package/types/src/utils/variables/findNestedVariables.d.ts +23 -0
  139. package/types/src/utils/variables/findNestedVariables.d.ts.map +1 -0
  140. package/types/src/utils/variables/getVariableType.d.ts +8 -0
  141. package/types/src/utils/variables/getVariableType.d.ts.map +1 -0
  142. package/types/src/utils/variables/variableUtils.d.ts +21 -0
  143. package/types/src/utils/variables/variableUtils.d.ts.map +1 -0
package/src/main.js CHANGED
@@ -27,7 +27,7 @@ const handleSignalEvents = require('./utils/handleSignalEvents')
27
27
  /* Utils - encoders */
28
28
  const { encodeUnknown, decodeUnknown } = require('./utils/encoders/unknown-values')
29
29
  const { decodeEncodedValue } = require('./utils/encoders')
30
- const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder } = require('./utils/encoders/js-fixes')
30
+ const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder, encodeJsonForVariable } = require('./utils/encoders/js-fixes')
31
31
 
32
32
  /* Utils - parsing */
33
33
  const enrichMetadata = require('./utils/parsing/enrichMetadata')
@@ -67,7 +67,7 @@ const { warnIfNotFound, isValidValue } = require('./utils/validation/warnIfNotFo
67
67
  /* Utils - variables */
68
68
  const cleanVariable = require('./utils/variables/cleanVariable')
69
69
  const appendDeepVariable = require('./utils/variables/appendDeepVariable')
70
- const { getFallbackString, verifyVariable } = require('./utils/variables/variableUtils')
70
+ const { extractVariableWrapper, getFallbackString, verifyVariable } = require('./utils/variables/variableUtils')
71
71
  const { findNestedVariables } = require('./utils/variables/findNestedVariables')
72
72
 
73
73
  /* Resolvers */
@@ -106,8 +106,8 @@ const deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/)
106
106
  const deepIndexReplacePattern = new RegExp(/^deep:|(\.[^}]+)*$/g)
107
107
  const deepIndexPattern = /deep\:(\d*)/
108
108
  const deepPrefixReplacePattern = /(?:^deep:)\d+\.?/g
109
- const fileRefSyntax = RegExp(/^file\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
110
- const textRefSyntax = RegExp(/^text\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
109
+ const fileRefSyntax = RegExp(/^file\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" =+]+?)\)/g)
110
+ const textRefSyntax = RegExp(/^text\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" =+]+?)\)/g)
111
111
  // TODO update file regex ^file\((~?[a-zA-Z0-9._\-\/, ]+?)\)
112
112
  // To match file(asyncValue.js, lol) input params
113
113
  const envRefSyntax = RegExp(/^env:/g)
@@ -132,7 +132,7 @@ class Configorama {
132
132
 
133
133
  const options = opts || {}
134
134
  // Set opts to pass into JS file calls
135
- this.opts = Object.assign({}, {
135
+ this.settings = Object.assign({}, {
136
136
  // Allow for unknown variable syntax to pass through without throwing errors
137
137
  allowUnknownVariables: false,
138
138
  // Allow undefined to be an end result.
@@ -149,7 +149,7 @@ class Configorama {
149
149
 
150
150
  // Backward compat: allowUnknownVars -> allowUnknownVariables
151
151
  if (options.allowUnknownVars !== undefined && options.allowUnknownVariables === undefined) {
152
- this.opts.allowUnknownVariables = options.allowUnknownVars
152
+ this.settings.allowUnknownVariables = options.allowUnknownVars
153
153
  }
154
154
 
155
155
  this.filterCache = {}
@@ -174,6 +174,15 @@ class Configorama {
174
174
  const variableSyntax = varRegex
175
175
  this.variableSyntax = variableSyntax
176
176
 
177
+ // Extract variable prefix/suffix from syntax regex for reconstructing variables
178
+ const syntaxWrapper = extractVariableWrapper(variableSyntax.source)
179
+ this.varPrefix = syntaxWrapper.prefix
180
+ this.varSuffix = syntaxWrapper.suffix
181
+ const escapedSuffix = this.varSuffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
182
+ this.varPrefixPattern = new RegExp('^' + this.varPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
183
+ this.varSuffixPattern = new RegExp(escapedSuffix + '$')
184
+ this.varSuffixWithSpacePattern = new RegExp('\\s+' + escapedSuffix + '$')
185
+
177
186
  // Set initial config object to populate
178
187
  if (typeof fileOrObject === 'object') {
179
188
  // set config objects
@@ -366,11 +375,13 @@ class Configorama {
366
375
  }
367
376
 
368
377
  /* attach self matcher last */
369
- this.variableTypes = this.variableTypes.concat(fallThroughSelfMatcher)
378
+ this.variableTypes = this.variableTypes.concat(/** @type {any} */ (fallThroughSelfMatcher))
370
379
 
371
380
  // const variablesKnownTypes = new RegExp(`^(${this.variableTypes.map((v) => v.prefix || v.type).join('|')}):`)
372
381
  const variablesKnownTypes = combineRegexes(
373
- this.variableTypes.filter((v) => v.type !== 'string').map((v) => v.match)
382
+ /** @type {RegExp[]} */ (this.variableTypes
383
+ .filter((v) => v.type !== 'string' && v.match instanceof RegExp)
384
+ .map((v) => v.match))
374
385
  )
375
386
  this.variablesKnownTypes = variablesKnownTypes
376
387
 
@@ -538,11 +549,11 @@ class Configorama {
538
549
  * Populate all variables in the service, conveniently remove and restore the service attributes
539
550
  * that confuse the population methods.
540
551
  * @param cliOpts An options hive to use for ${opt:...} variables.
541
- * @returns {Promise.<TResult>|*} A promise resolving to the populated service.
552
+ * @returns {Promise<any>} A promise resolving to the populated service.
542
553
  */
543
554
  async init(cliOpts) {
544
555
  this.options = cliOpts || {}
545
- const configoramaOpts = this.opts
556
+ const configoramaOpts = this.settings
546
557
 
547
558
  const showFoundVariables = configoramaOpts && configoramaOpts.dynamicArgs && (configoramaOpts.dynamicArgs.list || configoramaOpts.dynamicArgs.info)
548
559
 
@@ -553,10 +564,10 @@ class Configorama {
553
564
  contents: this.originalString,
554
565
  filePath: this.configFilePath,
555
566
  varRegex: this.variableSyntax,
556
- dynamicArgs: this.opts.dynamicArgs
567
+ dynamicArgs: this.settings.dynamicArgs
557
568
  })
558
569
  this.configFileContents = ''
559
- if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails || SETUP_MODE) {
570
+ if (VERBOSE || showFoundVariables || this.settings.returnPreResolvedVariableDetails || SETUP_MODE) {
560
571
  this.configFileContents = fs.readFileSync(this.configFilePath, 'utf8')
561
572
  }
562
573
  /*
@@ -566,7 +577,7 @@ class Configorama {
566
577
  this.rawOriginalConfig = cloneDeep(configObject)
567
578
 
568
579
  /* Preprocess step here - escapes ${} in help() args, fixes malformed fallbacks */
569
- configObject = preProcess(configObject, this.variableSyntax)
580
+ configObject = preProcess(configObject, this.variableSyntax, this.variableTypes)
570
581
  /*
571
582
  console.log('after preprocess', configObject)
572
583
  /** */
@@ -586,7 +597,7 @@ class Configorama {
586
597
  const variableSyntax = this.variableSyntax
587
598
  const variablesKnownTypes = this.variablesKnownTypes
588
599
 
589
- if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails || SETUP_MODE) {
600
+ if (VERBOSE || showFoundVariables || this.settings.returnPreResolvedVariableDetails || SETUP_MODE) {
590
601
  const metadata = this.collectVariableMetadata()
591
602
 
592
603
  const enrich = await enrichMetadata(
@@ -598,7 +609,7 @@ class Configorama {
598
609
  this.configFilePath,
599
610
  Object.keys(this.filters),
600
611
  undefined, // resolvedConfig not available yet
601
- this.opts.options,
612
+ this.settings.options,
602
613
  this.variableTypes
603
614
  )
604
615
 
@@ -616,7 +627,7 @@ class Configorama {
616
627
  const varKeys = Object.keys(variableData)
617
628
  const uniqueVarKeys = Object.keys(uniqueVariables)
618
629
 
619
- if (this.opts.returnPreResolvedVariableDetails) {
630
+ if (this.settings.returnPreResolvedVariableDetails) {
620
631
  return Object.assign({}, {
621
632
  resolved: false,
622
633
  originalConfig: this.originalConfig
@@ -635,7 +646,7 @@ class Configorama {
635
646
  if (varTypes.length) {
636
647
  const exclude = ['dot.prop', 'deep']
637
648
  console.log('\nAllowed variable types:')
638
- varTypes.filter((v) => v.type !== 'dot.prop').forEach((v) => {
649
+ varTypes.forEach((v) => {
639
650
  const vData = this.variableTypes[v]
640
651
  if (exclude.includes(vData.type)) {
641
652
  return
@@ -654,7 +665,7 @@ class Configorama {
654
665
  const fileName = this.configFilePath ? ` in ${this.configFilePath}` : ''
655
666
 
656
667
  // Extract base variable name from varMatch key (e.g., '${env:FOO, default}' -> 'env:FOO')
657
- const getBaseVarName = (key) => key.replace(/^\$\{/, '').replace(/\}$/, '').split(',')[0].trim()
668
+ const getBaseVarName = (key) => key.replace(this.varPrefixPattern, '').replace(this.varSuffixPattern, '').split(',')[0].trim()
658
669
 
659
670
  logHeader(`Found ${varKeys.length} Variables${fileName}`)
660
671
 
@@ -1132,7 +1143,7 @@ class Configorama {
1132
1143
  }
1133
1144
 
1134
1145
  const useDotEnv = this.originalConfig.useDotenv || this.originalConfig.useDotEnv
1135
- if ((useDotEnv && useDotEnv === true) || this.opts.useDotEnvFiles) {
1146
+ if ((useDotEnv && useDotEnv === true) || this.settings.useDotEnvFiles) {
1136
1147
  let providerStage
1137
1148
  /* has hardcoded stage */
1138
1149
  if (
@@ -1235,8 +1246,8 @@ class Configorama {
1235
1246
  .then(() => {
1236
1247
  // console.log('this.config', this.config)
1237
1248
  /* Final post-processing here */
1238
- if (this.mergeKeys && this.config) {
1239
- this.config = mergeByKeys(this.config, '', this.mergeKeys)
1249
+ if (this.settings.mergeKeys && this.config) {
1250
+ this.config = mergeByKeys(this.config, '', this.settings.mergeKeys)
1240
1251
  }
1241
1252
  if (VERBOSE) {
1242
1253
  logHeader('Resolved Configuration value')
@@ -1309,9 +1320,9 @@ class Configorama {
1309
1320
  .map((filter) => filter.trim())
1310
1321
  .filter(Boolean)
1311
1322
 
1312
- // Remove filters from the key (replace "| String}" with "}")
1323
+ // Remove filters from the key (replace "| String}" with suffix)
1313
1324
  // Also clean up any trailing whitespace before the closing brace
1314
- keyWithoutFilters = originalSrc.replace(filterMatch, '}').replace(/\s+}$/, '}')
1325
+ keyWithoutFilters = originalSrc.replace(filterMatch, this.varSuffix).replace(this.varSuffixWithSpacePattern, this.varSuffix)
1315
1326
  }
1316
1327
 
1317
1328
  const key = keyWithoutFilters
@@ -1337,7 +1348,7 @@ class Configorama {
1337
1348
  if (cleaned.varMatch && filterMatch) {
1338
1349
  const match = cleaned.varMatch.match(filterMatch)
1339
1350
  if (match) {
1340
- cleaned.varMatch = cleaned.varMatch.replace(filterMatch, '').replace(/\s+$/, '') + '}'
1351
+ cleaned.varMatch = cleaned.varMatch.replace(filterMatch, '').replace(/\s+$/, '') + this.varSuffix
1341
1352
  }
1342
1353
  }
1343
1354
  if (cleaned.variable && filterMatch) {
@@ -1400,6 +1411,7 @@ class Configorama {
1400
1411
  })
1401
1412
 
1402
1413
  const varData = {
1414
+ filters: foundFilters.length > 0 ? foundFilters : undefined,
1403
1415
  path: configValuePath,
1404
1416
  key: itemKey,
1405
1417
  originalStringValue: rawValue,
@@ -1407,10 +1419,12 @@ class Configorama {
1407
1419
  variableWithFilters: originalSrc,
1408
1420
  isRequired: false,
1409
1421
  defaultValue: undefined,
1422
+ defaultValueIsVar: undefined,
1423
+ defaultValueSrc: undefined,
1424
+ hasFallback: false,
1410
1425
  matchIndex: matchCount++,
1411
1426
  resolveOrder: [],
1412
1427
  resolveDetails: cleanedResolveDetails,
1413
- ...(foundFilters.length > 0 && { filters: foundFilters }),
1414
1428
  }
1415
1429
  let defaultValueIsVar = false
1416
1430
 
@@ -1682,7 +1696,7 @@ class Configorama {
1682
1696
  /**
1683
1697
  * Populate the variables in the given object.
1684
1698
  * @param objectToPopulate The object to populate variables within.
1685
- * @returns {Promise.<TResult>|*} A promise resolving to the in-place populated object.
1699
+ * @returns {Promise<any>} A promise resolving to the in-place populated object.
1686
1700
  */
1687
1701
  populateObject(objectToPopulate) {
1688
1702
  return this.initialCall(() => this.populateObjectImpl(objectToPopulate))
@@ -1732,7 +1746,7 @@ class Configorama {
1732
1746
  * ]
1733
1747
  * @typedef {Object} TerminalProperty
1734
1748
  * @property {String[]} path The path to the terminal property
1735
- * @property {Date|RegEx|String} The value of the terminal property
1749
+ * @property {Date|RegExp|String} value The value of the terminal property
1736
1750
  */
1737
1751
  /**
1738
1752
  * Generate an array of objects noting the terminal properties of the given root object and their
@@ -1848,8 +1862,7 @@ class Configorama {
1848
1862
  * Assign the populated values back to the target object
1849
1863
  * @param target The object to which the given populated terminal properties should be applied
1850
1864
  * @param populations The fully populated terminal properties
1851
- * @returns {Promise<number>} resolving with the number of changes that were applied to the given
1852
- * target
1865
+ * @returns {Promise<void>} resolving when changes have been applied to the given target
1853
1866
  */
1854
1867
  assignProperties(target, populations) {
1855
1868
  // eslint-disable-line class-methods-use-this
@@ -2089,7 +2102,7 @@ class Configorama {
2089
2102
  * @param valueObject The value to populate variables within
2090
2103
  * @param root Whether the caller is the root populater and thereby whether to recursively
2091
2104
  * populate
2092
- * @returns {PromiseLike<T>} A promise that resolves to the populated value, recursively if root
2105
+ * @returns {Promise<any>} A promise that resolves to the populated value, recursively if root
2093
2106
  * is true
2094
2107
  */
2095
2108
  populateValue(valueObject, root, caller) {
@@ -2172,11 +2185,14 @@ class Configorama {
2172
2185
  /**
2173
2186
  * Populate a given property, given the matched string to replace and the value to replace the
2174
2187
  * matched string with.
2175
- * @param valueObject.value The property to replace the matched string with the value.
2188
+ * @param {object} valueObject The value object containing the property to populate
2189
+ * @param {any} valueObject.value The property to replace the matched string with the value.
2190
+ * @param {string[]} [valueObject.path] The path to the value in the config.
2191
+ * @param {string} [valueObject.originalSource] The original source string.
2192
+ * @param {Array} [valueObject.resolutionHistory] History of resolution steps.
2176
2193
  * @param matchedString The string in the given property that was matched and is to be replaced.
2177
2194
  * @param valueToPopulate The value to replace the given matched string in the property with.
2178
- * @returns {Promise.<TResult>|*} A promise resolving to the property populated with the given
2179
- * value for all instances of the given matched string.
2195
+ * @returns {{value: any, path?: string[], originalSource?: string, resolutionHistory?: Array, __internal_only_flag?: boolean, caller?: string, count?: number}} The populated property object
2180
2196
  */
2181
2197
  populateVariable(valueObject, matchedString, valueToPopulate) {
2182
2198
  let property = valueObject.value
@@ -2202,7 +2218,7 @@ class Configorama {
2202
2218
  let foundFilters = []
2203
2219
  if (hasFilters) {
2204
2220
  foundFilters = hasFilters[0]
2205
- .replace(/}$/, '') // remove trailing }
2221
+ .replace(this.varSuffixPattern, '')
2206
2222
  .split('|')
2207
2223
  .map((filter) => filter.trim())
2208
2224
  .filter(Boolean)
@@ -2287,13 +2303,13 @@ class Configorama {
2287
2303
  console.log('isString currentMatchedString', currentMatchedString)
2288
2304
  console.log('>------')
2289
2305
  /** */
2290
- // Handle comma ${opt:stage, dev} and remove extra }
2306
+ // Handle comma ${opt:stage, dev} and remove extra suffix
2291
2307
  if (
2292
2308
  currentMatchedString.match(this.variableSyntax) &&
2293
2309
  !valueToPopulate.match(this.variableSyntax) &&
2294
- valueToPopulate.match(/}$/)
2310
+ valueToPopulate.match(this.varSuffixPattern)
2295
2311
  ) {
2296
- valueToPopulate = valueToPopulate.replace(/}$/, '')
2312
+ valueToPopulate = valueToPopulate.replace(this.varSuffixPattern, '')
2297
2313
  }
2298
2314
 
2299
2315
  property = replaceAll(currentMatchedString, valueToPopulate, property)
@@ -2315,23 +2331,30 @@ class Configorama {
2315
2331
 
2316
2332
  const objStr = JSON.stringify(valueToPopulate)
2317
2333
  /* Check if variable inside another variable. E.g. ${env:${self:someObject}} that resolves to ${env:{...}} */
2318
- if (
2334
+ const isNestedInVariable = (
2319
2335
  property.trim() !== matchedString.trim() &&
2320
2336
  property.indexOf(matchedString) !== -1 &&
2321
2337
  matchedString.match(this.variableSyntax) &&
2322
2338
  property.match(this.variableSyntax)
2323
- ) {
2339
+ )
2340
+ // Only encode for file() or text() references where JSON braces break regex matching
2341
+ const isFileOrTextRef = /\bfile\s*\(|\btext\s*\(/.test(property)
2342
+ if (isNestedInVariable && isFileOrTextRef) {
2343
+ // Encode object as base64 to avoid breaking variable syntax with nested braces
2344
+ const encodedObj = encodeJsonForVariable(valueToPopulate)
2345
+ property = replaceAll(matchedString, encodedObj, property)
2346
+ } else if (isNestedInVariable) {
2324
2347
  const isVar = /^\${[a-zA-Z0-9_]+:/.test(property)
2325
2348
  if (isVar) {
2326
- // console.log('INSIDE', property, matchedString)
2327
- // console.log('isVar', isVar)
2328
2349
  throw new Error(
2329
2350
  `Invalid variable syntax "${property}" resolves to "${replaceAll(matchedString, objStr, property)}"`,
2330
2351
  )
2331
2352
  }
2353
+ property = replaceAll(matchedString, objStr, property)
2354
+ } else {
2355
+ // console.log('OBJECT MATCH', `"${objStr}"`)
2356
+ property = replaceAll(matchedString, objStr, property)
2332
2357
  }
2333
- // console.log('OBJECT MATCH', `"${objStr}"`)
2334
- property = replaceAll(matchedString, objStr, property) // .replace(/}$/, '').replace(/^\$\{/, '')
2335
2358
  // console.log('property', property)
2336
2359
  // TODO run functions here
2337
2360
  // console.log('other new prop', property)
@@ -2340,7 +2363,7 @@ class Configorama {
2340
2363
  let missingValue = matchedString
2341
2364
 
2342
2365
  if (matchedString.match(deepRefSyntax)) {
2343
- const deepIndex = matchedString.split(':')[1].replace('}', '')
2366
+ const deepIndex = matchedString.split(':')[1].replace(this.varSuffixPattern, '')
2344
2367
  const i = Number(deepIndex)
2345
2368
  missingValue = this.deep[i]
2346
2369
  }
@@ -2357,7 +2380,7 @@ class Configorama {
2357
2380
 
2358
2381
  if (nestedVar) {
2359
2382
  const fallbackStr = getFallbackString(splitVars, nestedVar)
2360
- if (!this.opts.allowUnknownVariables) {
2383
+ if (!this.settings.allowUnknownVariables) {
2361
2384
  verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
2362
2385
  }
2363
2386
 
@@ -2372,6 +2395,38 @@ class Configorama {
2372
2395
  }
2373
2396
  }
2374
2397
 
2398
+ // If allowUnresolvedVariables and there are fallbacks, use the fallback
2399
+ if (this.settings.allowUnresolvedVariables && splitVars.length > 1) {
2400
+ const nextFallback = splitVars[1].trim()
2401
+ // Strip trailing variable suffix (handles }, }}, >, ]], etc.)
2402
+ const nextFallbackClean = nextFallback.replace(this.varSuffixPattern, '')
2403
+ const isQuotedString = /^['"].*['"]$/.test(nextFallbackClean)
2404
+ const isNumeric = /^-?\d+(\.\d+)?$/.test(nextFallbackClean)
2405
+ if (isQuotedString || isNumeric) {
2406
+ const strValue = nextFallbackClean.replace(/^['"]|['"]$/g, '')
2407
+ // Convert to number if it's a numeric fallback
2408
+ /** @type {string|number} */
2409
+ const staticValue = isNumeric ? Number(strValue) : strValue
2410
+ return {
2411
+ value: staticValue,
2412
+ path: valueObject.path,
2413
+ originalSource: valueObject.originalSource,
2414
+ resolutionHistory: valueObject.resolutionHistory || [],
2415
+ }
2416
+ }
2417
+ // Next fallback is another variable
2418
+ const remainingContent = splitVars.slice(1).join(', ').replace(this.varSuffixPattern, '')
2419
+ const remainingFallbacks = this.varPrefix + remainingContent + this.varSuffix
2420
+ return {
2421
+ value: remainingFallbacks,
2422
+ path: valueObject.path,
2423
+ originalSource: valueObject.originalSource,
2424
+ resolutionHistory: valueObject.resolutionHistory || [],
2425
+ __internal_only_flag: true,
2426
+ caller: 'allowUnresolvedVariables-fallback',
2427
+ }
2428
+ }
2429
+
2375
2430
  const currentPath = valueObject.path.join('.')
2376
2431
 
2377
2432
  const errorMessage = `
@@ -2394,7 +2449,7 @@ Missing Value ${missingValue} - ${matchedString}
2394
2449
  )
2395
2450
 
2396
2451
  // Double processing needed for `${eval(${self:three} > ${self:four})}`
2397
- if (prop.startsWith('${')) {
2452
+ if (prop.startsWith(this.varPrefix)) {
2398
2453
  prop = cleanVariable(prop, this.variableSyntax, true, `populateVariable string ${this.callCount}`)
2399
2454
  }
2400
2455
 
@@ -2442,7 +2497,7 @@ Missing Value ${missingValue} - ${matchedString}
2442
2497
  !prop.match(this.variableSyntax)
2443
2498
  &&
2444
2499
  /* Not file or text refs */
2445
- !prop.match(fileRefSyntax)
2500
+ !prop.match(fileRefSyntax)
2446
2501
  && !prop.match(textRefSyntax)
2447
2502
  /* Not eval refs */
2448
2503
  && !prop.match(getValueFromEval.match)
@@ -2531,8 +2586,10 @@ Missing Value ${missingValue} - ${matchedString}
2531
2586
  /**
2532
2587
  * Resolve the given variable string that expresses a series of fallback values in case the
2533
2588
  * initial values are not valid, resolving each variable and resolving to the first valid value.
2534
- * @param variableStringsString The overwrite string of variables to populate and choose from.
2535
- * @returns {Promise.<TResult>|*} A promise resolving to the first validly populating variable
2589
+ * @param variableStrings The overwrite string of variables to populate and choose from.
2590
+ * @param valueObject The value object
2591
+ * @param originalVar The original variable string
2592
+ * @returns {Promise<any>} A promise resolving to the first validly populating variable
2536
2593
  * in the given variable strings string.
2537
2594
  */
2538
2595
  overwrite(variableStrings, valueObject, originalVar) {
@@ -2595,7 +2652,7 @@ Missing Value ${missingValue} - ${matchedString}
2595
2652
 
2596
2653
  if (deepProperties > 0) {
2597
2654
  // Reconstruct a minimal variable string with deep refs, not the full outer string
2598
- const reconstructed = '${' + deepVariableParts.join(', ') + '}'
2655
+ const reconstructed = this.varPrefix + deepVariableParts.join(', ') + this.varSuffix
2599
2656
  return Promise.resolve(reconstructed)
2600
2657
  }
2601
2658
  return Promise.resolve(extractedValues.find(isValidValue)) // resolve first valid value, else undefined
@@ -2608,7 +2665,10 @@ Missing Value ${missingValue} - ${matchedString}
2608
2665
  /**
2609
2666
  * Given any variable string, return the value it should be populated with.
2610
2667
  * @param variableString The variable string to retrieve a value for.
2611
- * @returns {Promise.<TResult>|*} A promise resolving to the given variables value.
2668
+ * @param valueObject The value object
2669
+ * @param caller The caller name
2670
+ * @param originalVar The original variable string
2671
+ * @returns {Promise<any>} A promise resolving to the given variables value.
2612
2672
  */
2613
2673
  getValueFromSource(variableString, valueObject, caller, originalVar) {
2614
2674
  // console.log('getValueFromSrc caller', caller)
@@ -2694,7 +2754,7 @@ Missing Value ${missingValue} - ${matchedString}
2694
2754
  .map((f) => {
2695
2755
  return trim(f)
2696
2756
  // TODO refactor this. This is a temp fix for filters with nested vars.
2697
- .replace(/}$/, '')
2757
+ .replace(this.varSuffixPattern, '')
2698
2758
  })
2699
2759
  // console.log('filters to run', _filter)
2700
2760
 
@@ -2707,6 +2767,7 @@ Missing Value ${missingValue} - ${matchedString}
2707
2767
  variableString = trim(t[0])
2708
2768
  }
2709
2769
 
2770
+ /** @type {Function|undefined} */
2710
2771
  let resolverFunction
2711
2772
  let resolverType
2712
2773
  /* Loop over variables and set getterFunction when match found. */
@@ -2803,12 +2864,22 @@ Missing Value ${missingValue} - ${matchedString}
2803
2864
  // console.log('nestedVars', nestedVars)
2804
2865
  const noNestedVars = nestedVars.length < 2
2805
2866
 
2806
- if (this.opts.allowUnknownFileRefs && variableString.match(fileRefSyntax)) {
2867
+ if (this.settings.allowUnknownFileRefs && variableString.match(fileRefSyntax)) {
2807
2868
  // Encode the unknown file variable to pass through resolution
2808
2869
  return Promise.resolve(encodeUnknown(propertyString))
2809
2870
  }
2810
2871
 
2811
- if (this.opts.allowUnresolvedVariables) {
2872
+ if (this.settings.allowUnresolvedVariables) {
2873
+ // Check if outer expression has fallbacks we can use
2874
+ // valueCount[0] is the primary var, valueCount[1+] are fallbacks
2875
+ if (valueCount.length > 1) {
2876
+ const primaryVar = valueCount[0]
2877
+ // If the unresolvable variableString is used INSIDE the primary var,
2878
+ // return undefined to trigger the outer fallback mechanism
2879
+ if (primaryVar.includes(variableString)) {
2880
+ return Promise.resolve(undefined)
2881
+ }
2882
+ }
2812
2883
  // Encode unresolved variable to pass through resolution
2813
2884
  return Promise.resolve(encodeUnknown(propertyString))
2814
2885
  }
@@ -2885,7 +2956,7 @@ Missing Value ${missingValue} - ${matchedString}
2885
2956
  if (typeof val === 'string' && val.match(/deep:/)) {
2886
2957
  // TODO refactor the deep filter logic here. match | filter | filter..
2887
2958
  const allFilters = propertyString
2888
- .replace(/}$/, '')
2959
+ .replace(this.varSuffixPattern, '')
2889
2960
  .split('|')
2890
2961
  .reduce((acc, currentFilter, i) => {
2891
2962
  if (i === 0) {
@@ -2895,7 +2966,7 @@ Missing Value ${missingValue} - ${matchedString}
2895
2966
  return acc
2896
2967
  }, '')
2897
2968
  // add filters to deep references if filter is used
2898
- const deepValueWithFilters = newHasFilter[1] ? val.replace(/}$/, ` ${allFilters}}`) : val
2969
+ const deepValueWithFilters = newHasFilter[1] ? val.replace(this.varSuffixPattern, ` ${allFilters}${this.varSuffix}`) : val
2899
2970
  // console.log('deepValueWithFilters', deepValueWithFilters)
2900
2971
  // console.log('RESOLVER RETURN newValue 4', deepValueWithFilters)
2901
2972
  return Promise.resolve(deepValueWithFilters)
@@ -2975,7 +3046,7 @@ Missing Value ${missingValue} - ${matchedString}
2975
3046
  // console.log('nestedVar', nestedVar)
2976
3047
 
2977
3048
  if (nestedVar) {
2978
- if (!this.opts.allowUnknownVariables) {
3049
+ if (!this.settings.allowUnknownVariables) {
2979
3050
  verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
2980
3051
  }
2981
3052
  const fallbackStr = getFallbackString(split, nestedVar)
@@ -3053,7 +3124,7 @@ Missing Value ${missingValue} - ${matchedString}
3053
3124
  let allowSpecialCase = false
3054
3125
  /* handle special cases for cloudformation ${Sub} values */
3055
3126
  if (this.originalConfig && key.endsWith('Fn::Sub')) {
3056
- if (this.opts.verifySubReferences) {
3127
+ if (this.settings.verifySubReferences) {
3057
3128
  const params = this.originalConfig.Parameters || (this.originalConfig.resources || {}).Parameters
3058
3129
  const resources = this.originalConfig.Resources || (this.originalConfig.resources || {}).Resources
3059
3130
  /* Cloudformation Resource References */
@@ -3078,7 +3149,7 @@ Missing Value ${missingValue} - ${matchedString}
3078
3149
 
3079
3150
 
3080
3151
  /* Pass through unknown variables */
3081
- if (this.opts.allowUnknownVariables || allowSpecialCase) {
3152
+ if (this.settings.allowUnknownVariables || allowSpecialCase) {
3082
3153
  // console.log('allowUnknownVars propertyString', propertyString)
3083
3154
  const varMatches = propertyString.match(this.variableSyntax)
3084
3155
  let allowUnknownVars = propertyString
@@ -3136,7 +3207,7 @@ Missing Value ${missingValue} - ${matchedString}
3136
3207
  variableSyntax: this.variableSyntax,
3137
3208
  variablesKnownTypes: this.variablesKnownTypes,
3138
3209
  variableTypes: this.variableTypes,
3139
- opts: this.opts,
3210
+ opts: this.settings,
3140
3211
  originalConfig: this.originalConfig,
3141
3212
  config: this.config,
3142
3213
  getDeeperValue: this.getDeeperValue.bind(this),
@@ -3294,7 +3365,7 @@ Missing Value ${missingValue} - ${matchedString}
3294
3365
  })
3295
3366
  }
3296
3367
  runFunction(variableString) {
3297
- // console.log('runFunction', variableString)
3368
+ console.log('runFunction', variableString)
3298
3369
  /* If json object value return it */
3299
3370
  if (variableString.match(/^\s*{/) && variableString.match(/}\s*$/)) {
3300
3371
  return variableString
@@ -3326,7 +3397,9 @@ Missing Value ${missingValue} - ${matchedString}
3326
3397
  // TODO check for camelCase version. | toUpperCase messes with function name
3327
3398
  const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
3328
3399
 
3329
- if (!theFunction) throw new Error(`Function "${functionName}" not found`)
3400
+ if (!theFunction) {
3401
+ throw new Error(`Function "${functionName}" not found`)
3402
+ }
3330
3403
 
3331
3404
  const funcValue = theFunction(...argsToPass)
3332
3405
  // console.log('funcValue', funcValue)
@@ -1,8 +1,18 @@
1
+ /**
2
+ * @typedef {Object} ParserFunction
3
+ * @property {Function} parse - Parse string content into object
4
+ * @property {Function} stringify - Convert object to string format
5
+ */
6
+
1
7
  const json = require('./json5')
2
8
  const toml = require('./toml')
3
9
  const yaml = require('./yaml')
4
10
  const ini = require('./ini')
5
11
 
12
+ /**
13
+ * Collection of format parsers for different config file types
14
+ * @type {Object.<string, ParserFunction>}
15
+ */
6
16
  module.exports = {
7
17
  json: json,
8
18
  toml: toml,