configorama 0.6.6 → 0.6.8
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/package.json +1 -1
- package/src/index.js +19 -1
- package/src/main.js +788 -191
- package/src/resolvers/valueFromGit.js +21 -1
- package/src/sync.js +18 -3
- package/src/utils/enrichMetadata.js +229 -0
- package/src/utils/find-nested-variables.js +7 -4
- package/src/utils/find-nested-variables.test.js +43 -4
- package/src/utils/isValidValue.js +1 -1
- package/src/utils/splitByComma.js +33 -9
- package/src/utils/splitByComma.test.js +47 -2
package/src/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const os = require('os')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const fs = require('fs')
|
|
4
|
-
/*
|
|
4
|
+
/* // disable logs to find broken tests
|
|
5
5
|
console.log = () => {}
|
|
6
6
|
// process.exit(1)
|
|
7
7
|
/** */
|
|
@@ -91,7 +91,7 @@ const logLines = '────────────────────
|
|
|
91
91
|
let DEBUG = process.argv.includes('--debug') ? true : false
|
|
92
92
|
let VERBOSE = process.argv.includes('--verbose') ? true : false
|
|
93
93
|
// DEBUG = true
|
|
94
|
-
|
|
94
|
+
let DEBUG_TYPE = false
|
|
95
95
|
const ENABLE_FUNCTIONS = true
|
|
96
96
|
|
|
97
97
|
function combineRegexes(regexes) {
|
|
@@ -104,6 +104,136 @@ function combineRegexes(regexes) {
|
|
|
104
104
|
return new RegExp(`(${patterns.join('|')})`)
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Preprocess config to fix malformed fallback references
|
|
109
|
+
* @param {Object} configObject - The parsed configuration object
|
|
110
|
+
* @param {RegExp} variableSyntax - The variable syntax regex to use
|
|
111
|
+
* @returns {Object} The preprocessed configuration object
|
|
112
|
+
*/
|
|
113
|
+
function preProcess(configObject, variableSyntax) {
|
|
114
|
+
// Known reference prefixes that should be wrapped in ${}
|
|
115
|
+
const refPrefixes = ['self:', 'opt:', 'env:', 'file:', 'text:', 'deep:']
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Fix malformed fallback references in a string
|
|
119
|
+
* @param {string} str - String potentially containing variables
|
|
120
|
+
* @returns {string} String with fixed fallback references
|
|
121
|
+
*/
|
|
122
|
+
function fixFallbacksInString(str) {
|
|
123
|
+
if (typeof str !== 'string') return str
|
|
124
|
+
|
|
125
|
+
let result = str
|
|
126
|
+
let changed = true
|
|
127
|
+
|
|
128
|
+
// Keep iterating until no more changes (to handle nested variables)
|
|
129
|
+
while (changed) {
|
|
130
|
+
changed = false
|
|
131
|
+
|
|
132
|
+
// Find innermost ${...} blocks (ones that don't contain other ${)
|
|
133
|
+
let i = 0
|
|
134
|
+
while (i < result.length) {
|
|
135
|
+
if (result[i] === '$' && result[i + 1] === '{') {
|
|
136
|
+
const start = i
|
|
137
|
+
let braceCount = 1
|
|
138
|
+
let j = i + 2
|
|
139
|
+
|
|
140
|
+
// Find the matching closing brace by counting { and }
|
|
141
|
+
while (j < result.length && braceCount > 0) {
|
|
142
|
+
if (result[j] === '{') {
|
|
143
|
+
braceCount++
|
|
144
|
+
} else if (result[j] === '}') {
|
|
145
|
+
braceCount--
|
|
146
|
+
}
|
|
147
|
+
j++
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (braceCount === 0) {
|
|
151
|
+
const end = j
|
|
152
|
+
const match = result.substring(start, end)
|
|
153
|
+
const content = result.substring(start + 2, end - 1)
|
|
154
|
+
|
|
155
|
+
// Only process if there's a comma (indicating fallback syntax)
|
|
156
|
+
if (content.includes(',')) {
|
|
157
|
+
// Split by comma
|
|
158
|
+
const parts = splitByComma(content, variableSyntax)
|
|
159
|
+
|
|
160
|
+
if (parts.length > 1) {
|
|
161
|
+
// Check if the first part has nested ${} - if so, skip this (process inner ones first)
|
|
162
|
+
const firstPart = parts[0]
|
|
163
|
+
if (firstPart.includes('${')) {
|
|
164
|
+
i = start + 2 // Move past ${ to find inner variables
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check each part after the first (these are fallback values)
|
|
169
|
+
const fixed = parts.map((part, index) => {
|
|
170
|
+
if (index === 0) {
|
|
171
|
+
return part // Keep the main reference as-is
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const trimmed = part.trim()
|
|
175
|
+
|
|
176
|
+
// Check if this looks like a reference but is not wrapped
|
|
177
|
+
const looksLikeRef = refPrefixes.some(prefix => trimmed.startsWith(prefix))
|
|
178
|
+
const alreadyWrapped = trimmed.startsWith('${') && trimmed.endsWith('}')
|
|
179
|
+
|
|
180
|
+
if (looksLikeRef && !alreadyWrapped) {
|
|
181
|
+
return ` \${${trimmed}}`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return ` ${trimmed}`
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const replacement = `\${${fixed.join(',')}}`
|
|
188
|
+
if (replacement !== match) {
|
|
189
|
+
result = result.substring(0, start) + replacement + result.substring(end)
|
|
190
|
+
changed = true
|
|
191
|
+
break // Restart search from beginning
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
i = start + 2 // Move past ${ to continue searching for nested variables
|
|
197
|
+
} else {
|
|
198
|
+
i++
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
i++
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Recursively traverse and fix the config object
|
|
211
|
+
*/
|
|
212
|
+
function traverseAndFix(obj) {
|
|
213
|
+
if (typeof obj === 'string') {
|
|
214
|
+
return fixFallbacksInString(obj)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (Array.isArray(obj)) {
|
|
218
|
+
return obj.map(item => traverseAndFix(item))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (obj !== null && typeof obj === 'object') {
|
|
222
|
+
const result = {}
|
|
223
|
+
for (const key in obj) {
|
|
224
|
+
if (obj.hasOwnProperty(key)) {
|
|
225
|
+
result[key] = traverseAndFix(obj[key])
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return obj
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return traverseAndFix(configObject)
|
|
235
|
+
}
|
|
236
|
+
|
|
107
237
|
class Configorama {
|
|
108
238
|
constructor(fileOrObject, opts) {
|
|
109
239
|
/* attach sig events on async calls */
|
|
@@ -124,6 +254,9 @@ class Configorama {
|
|
|
124
254
|
|
|
125
255
|
this.foundVariables = []
|
|
126
256
|
|
|
257
|
+
// Track variable resolutions for metadata (keyed by path)
|
|
258
|
+
this.resolutionTracking = {}
|
|
259
|
+
|
|
127
260
|
const defaultSyntax = '\\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
|
|
128
261
|
|
|
129
262
|
const varSyntax = options.syntax || defaultSyntax
|
|
@@ -226,8 +359,7 @@ class Configorama {
|
|
|
226
359
|
prefix: 'file',
|
|
227
360
|
match: fileRefSyntax,
|
|
228
361
|
resolver: (varString, o, x, pathValue) => {
|
|
229
|
-
|
|
230
|
-
return this.getValueFromFile(varString)
|
|
362
|
+
return this.getValueFromFile(varString, { context: pathValue })
|
|
231
363
|
},
|
|
232
364
|
},
|
|
233
365
|
|
|
@@ -237,7 +369,7 @@ class Configorama {
|
|
|
237
369
|
prefix: 'text',
|
|
238
370
|
match: textRefSyntax,
|
|
239
371
|
resolver: (varString, o, x, pathValue) => {
|
|
240
|
-
return this.getValueFromFile(varString, { asRawText: true })
|
|
372
|
+
return this.getValueFromFile(varString, { asRawText: true, context: pathValue })
|
|
241
373
|
},
|
|
242
374
|
},
|
|
243
375
|
|
|
@@ -258,7 +390,7 @@ class Configorama {
|
|
|
258
390
|
match: deepRefSyntax,
|
|
259
391
|
resolver: (varString, o, x, pathValue) => {
|
|
260
392
|
// console.log('>>>>>getValueFromDeep', varString)
|
|
261
|
-
return this.getValueFromDeep(varString)
|
|
393
|
+
return this.getValueFromDeep(varString, pathValue)
|
|
262
394
|
},
|
|
263
395
|
},
|
|
264
396
|
// Numbers
|
|
@@ -463,7 +595,7 @@ class Configorama {
|
|
|
463
595
|
|
|
464
596
|
// If we have a file path but no config yet, parse it now
|
|
465
597
|
if (this.configFilePath && !this.config) {
|
|
466
|
-
|
|
598
|
+
let configObject = await parseFileContents(
|
|
467
599
|
this.originalString,
|
|
468
600
|
this.configFileType,
|
|
469
601
|
this.configFilePath,
|
|
@@ -474,6 +606,16 @@ class Configorama {
|
|
|
474
606
|
if (VERBOSE || showFoundVariables) {
|
|
475
607
|
this.configFileContents = fs.readFileSync(this.configFilePath, 'utf8')
|
|
476
608
|
}
|
|
609
|
+
/*
|
|
610
|
+
console.log('before preprocess', configObject)
|
|
611
|
+
/** */
|
|
612
|
+
/* Preprocess step here */
|
|
613
|
+
configObject = preProcess(configObject, this.variableSyntax)
|
|
614
|
+
/*
|
|
615
|
+
console.log('after preprocess', configObject)
|
|
616
|
+
/** */
|
|
617
|
+
//process.exit(1)
|
|
618
|
+
|
|
477
619
|
this.config = configObject
|
|
478
620
|
this.originalConfig = cloneDeep(configObject)
|
|
479
621
|
}
|
|
@@ -489,106 +631,14 @@ class Configorama {
|
|
|
489
631
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
490
632
|
|
|
491
633
|
if (VERBOSE || showFoundVariables) {
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const nested = findNestedVariables(rawValue, variableSyntax, variablesKnownTypes, configValuePath)
|
|
504
|
-
/*
|
|
505
|
-
console.log('traverse nested result', nested)
|
|
506
|
-
/** */
|
|
507
|
-
|
|
508
|
-
// console.log(`▷ Path: ${configValuePath}`)
|
|
509
|
-
// console.log('\n Key/value:')
|
|
510
|
-
// console.log(` ${configValuePath}: ${rawValue}`)
|
|
511
|
-
const lastItem = nested[nested.length - 1]
|
|
512
|
-
const lastKeyPath = this.path[this.path.length - 1]
|
|
513
|
-
const itemKey = (lastKeyPath.match(/[\d+]$/)) ? `${this.path[this.path.length - 2]}[${lastKeyPath}]` : lastKeyPath
|
|
514
|
-
const key = lastItem.fullMatch
|
|
515
|
-
const varData = {
|
|
516
|
-
path: configValuePath,
|
|
517
|
-
key: itemKey,
|
|
518
|
-
value: rawValue,
|
|
519
|
-
variable: lastItem.fullMatch,
|
|
520
|
-
isRequired: false,
|
|
521
|
-
defaultValue: undefined,
|
|
522
|
-
matchIndex: matchCount++,
|
|
523
|
-
// hasFallback: false,
|
|
524
|
-
resolveOrder: [],
|
|
525
|
-
resolveDetails: nested,
|
|
526
|
-
}
|
|
527
|
-
let defaultValueIsVar = false
|
|
528
|
-
function calculateResolveOrder(item) {
|
|
529
|
-
if (item && item.fallbackValues) {
|
|
530
|
-
let hasResolvedFallback
|
|
531
|
-
// console.log('item.fallbackValues', item.fallbackValues)
|
|
532
|
-
const order = ([item.valueBeforeFallback]).concat(item.fallbackValues.map((f, i) => {
|
|
533
|
-
// console.log('f', f)
|
|
534
|
-
if (f.fallbackValues) {
|
|
535
|
-
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
536
|
-
// console.log('nestedOrder', nestedOrder)
|
|
537
|
-
// console.log('nestedResolvedFallback', nestedResolvedFallback)
|
|
538
|
-
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
539
|
-
hasResolvedFallback = nestedResolvedFallback
|
|
540
|
-
}
|
|
541
|
-
return nestedOrder // Return just the order part
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
545
|
-
hasResolvedFallback = f.stringValue
|
|
546
|
-
}
|
|
547
|
-
if (f.isResolvedFallback) {
|
|
548
|
-
hasResolvedFallback = f.stringValue
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
if (!hasResolvedFallback && f.isVariable) {
|
|
552
|
-
defaultValueIsVar = f
|
|
553
|
-
}
|
|
554
|
-
// console.log('hasResolvedFallback', hasResolvedFallback)
|
|
555
|
-
return `${f.stringValue || f.variable}${f.isResolvedFallback ? ' (Resolved default fallback)' : ''}`
|
|
556
|
-
})).flat()
|
|
557
|
-
|
|
558
|
-
return [order, hasResolvedFallback]
|
|
559
|
-
}
|
|
560
|
-
return [[item.variable], undefined] // Return array instead of just the value
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const [resolveOrder, hasResolvedFallback] = calculateResolveOrder(lastItem)
|
|
564
|
-
varData.resolveOrder = resolveOrder
|
|
565
|
-
|
|
566
|
-
if (defaultValueIsVar) {
|
|
567
|
-
varData.defaultValueIsVar = defaultValueIsVar
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// console.log('hasResolvedFallback', hasResolvedFallback)
|
|
571
|
-
if (typeof hasResolvedFallback !== 'undefined') {
|
|
572
|
-
varData.defaultValue = hasResolvedFallback
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// console.log('varData.defaultValue', varData.defaultValue)
|
|
576
|
-
if (typeof varData.defaultValue === 'undefined') {
|
|
577
|
-
varData.isRequired = true
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
if (varData.resolveOrder.length > 1) {
|
|
581
|
-
varData.hasFallback = true
|
|
582
|
-
}
|
|
583
|
-
//console.log('varData', varData)
|
|
584
|
-
|
|
585
|
-
variableData[key] = (variableData[key] || []).concat(varData)
|
|
586
|
-
|
|
587
|
-
foundVariables.push(rawValue)
|
|
588
|
-
}
|
|
589
|
-
})
|
|
590
|
-
|
|
591
|
-
if (!foundVariables.length) {
|
|
634
|
+
// Use collectVariableMetadata to get variable info (DRY - don't duplicate logic)
|
|
635
|
+
const metadata = this.collectVariableMetadata()
|
|
636
|
+
deepLog('metadata', metadata)
|
|
637
|
+
process.exit(1)
|
|
638
|
+
const variableData = metadata.variables
|
|
639
|
+
const varKeys = Object.keys(variableData)
|
|
640
|
+
|
|
641
|
+
if (!varKeys.length) {
|
|
592
642
|
logHeader('No Variables Found in Config')
|
|
593
643
|
if (this.configFilePath) {
|
|
594
644
|
console.log(`File: ${this.configFilePath}`)
|
|
@@ -611,10 +661,7 @@ class Configorama {
|
|
|
611
661
|
console.log()
|
|
612
662
|
}
|
|
613
663
|
|
|
614
|
-
|
|
615
|
-
const finalFoundVariables = [...new Set(foundVariables)]
|
|
616
|
-
if (finalFoundVariables.length > 0) {
|
|
617
|
-
const varKeys = Object.keys(variableData)
|
|
664
|
+
if (varKeys.length > 0) {
|
|
618
665
|
const fileName = this.configFilePath ? ` in ${this.configFilePath}` : ''
|
|
619
666
|
|
|
620
667
|
logHeader(`Found ${varKeys.length} Variables${fileName}`)
|
|
@@ -626,9 +673,34 @@ class Configorama {
|
|
|
626
673
|
const longestKey = varKeys.reduce((acc, k) => {
|
|
627
674
|
return Math.max(acc, k.length)
|
|
628
675
|
}, 0)
|
|
676
|
+
// Count all references including nested ones within other variables
|
|
677
|
+
const countAllReferences = (targetVariable) => {
|
|
678
|
+
// Start with direct references
|
|
679
|
+
let count = variableData[targetVariable].length
|
|
680
|
+
|
|
681
|
+
// Check all other variables for nested references to this variable
|
|
682
|
+
varKeys.forEach((otherKey) => {
|
|
683
|
+
if (otherKey === targetVariable) return
|
|
684
|
+
|
|
685
|
+
variableData[otherKey].forEach((instance) => {
|
|
686
|
+
if (instance.resolveDetails) {
|
|
687
|
+
instance.resolveDetails.forEach((detail) => {
|
|
688
|
+
// Check if this resolveDetail references our target variable
|
|
689
|
+
if (detail.fullMatch === targetVariable) {
|
|
690
|
+
count++
|
|
691
|
+
}
|
|
692
|
+
})
|
|
693
|
+
}
|
|
694
|
+
})
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
return count
|
|
698
|
+
}
|
|
699
|
+
|
|
629
700
|
console.log(varKeys.map((k) => {
|
|
630
|
-
const
|
|
631
|
-
|
|
701
|
+
const refCount = countAllReferences(k)
|
|
702
|
+
const placesWord = refCount > 1 ? 'places' : 'place'
|
|
703
|
+
return `- ${k.padEnd(longestKey).padEnd(longestKey + 10)} referenced ${refCount} ${placesWord}`
|
|
632
704
|
}).join('\n'))
|
|
633
705
|
console.log()
|
|
634
706
|
}
|
|
@@ -802,6 +874,7 @@ class Configorama {
|
|
|
802
874
|
return this.populateObjectImpl(this.config).finally(() => {
|
|
803
875
|
// TODO populate function values here?
|
|
804
876
|
// console.log('Final Config', this.config)
|
|
877
|
+
// console.log(this.deep)
|
|
805
878
|
const transform = this.runFunction.bind(this)
|
|
806
879
|
const varSyntax = this.variableSyntax
|
|
807
880
|
const leaves = this.leaves
|
|
@@ -812,7 +885,9 @@ class Configorama {
|
|
|
812
885
|
/* Pass through unknown variables */
|
|
813
886
|
if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
|
|
814
887
|
const configValuePath = this.path.join('.')
|
|
888
|
+
/*
|
|
815
889
|
console.log(this.path)
|
|
890
|
+
/** */
|
|
816
891
|
const ogValue = dotProp.get(originalConfig, configValuePath)
|
|
817
892
|
const varDisplay = ogValue ? `"${ogValue}" variable` : 'variable'
|
|
818
893
|
|
|
@@ -829,6 +904,7 @@ class Configorama {
|
|
|
829
904
|
throw new Error(errorMessage)
|
|
830
905
|
}
|
|
831
906
|
if (typeof rawValue === 'string') {
|
|
907
|
+
// console.log('rawValue', rawValue)
|
|
832
908
|
/* Process inline functions like merge() */
|
|
833
909
|
if (ENABLE_FUNCTIONS && rawValue.match(/> function /)) {
|
|
834
910
|
// console.log('RAW FUNCTION', rawFunction)
|
|
@@ -888,7 +964,216 @@ class Configorama {
|
|
|
888
964
|
})
|
|
889
965
|
})
|
|
890
966
|
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Collect metadata about all variables found in the configuration
|
|
970
|
+
* @returns {object} Metadata object containing variables, fileRefs, and summary
|
|
971
|
+
*/
|
|
972
|
+
collectVariableMetadata() {
|
|
973
|
+
const variableSyntax = this.variableSyntax
|
|
974
|
+
const variablesKnownTypes = this.variablesKnownTypes
|
|
975
|
+
const foundVariables = []
|
|
976
|
+
const variableData = {}
|
|
977
|
+
const fileRefs = []
|
|
978
|
+
const fileGlobPatterns = []
|
|
979
|
+
let matchCount = 1
|
|
980
|
+
|
|
981
|
+
traverse(this.originalConfig).forEach(function (rawValue) {
|
|
982
|
+
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
983
|
+
const configValuePath = this.path.join('.')
|
|
984
|
+
/* Skip Fn::Sub variables */
|
|
985
|
+
if (configValuePath.endsWith('Fn::Sub')) {
|
|
986
|
+
return
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const nested = findNestedVariables(rawValue, variableSyntax, variablesKnownTypes, configValuePath)
|
|
990
|
+
|
|
991
|
+
const lastItem = nested[nested.length - 1]
|
|
992
|
+
const lastKeyPath = this.path[this.path.length - 1]
|
|
993
|
+
const itemKey = (lastKeyPath.match(/[\d+]$/)) ? `${this.path[this.path.length - 2]}[${lastKeyPath}]` : lastKeyPath
|
|
994
|
+
const key = lastItem.fullMatch
|
|
995
|
+
const varData = {
|
|
996
|
+
path: configValuePath,
|
|
997
|
+
key: itemKey,
|
|
998
|
+
value: rawValue,
|
|
999
|
+
variable: lastItem.fullMatch,
|
|
1000
|
+
isRequired: false,
|
|
1001
|
+
defaultValue: undefined,
|
|
1002
|
+
matchIndex: matchCount++,
|
|
1003
|
+
resolveOrder: [],
|
|
1004
|
+
resolveDetails: nested,
|
|
1005
|
+
}
|
|
1006
|
+
let defaultValueIsVar = false
|
|
1007
|
+
|
|
1008
|
+
function calculateResolveOrder(item) {
|
|
1009
|
+
if (item && item.fallbackValues) {
|
|
1010
|
+
let hasResolvedFallback
|
|
1011
|
+
const order = ([item.valueBeforeFallback]).concat(item.fallbackValues.map((f, i) => {
|
|
1012
|
+
if (f.fallbackValues) {
|
|
1013
|
+
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
1014
|
+
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
1015
|
+
hasResolvedFallback = nestedResolvedFallback
|
|
1016
|
+
}
|
|
1017
|
+
return nestedOrder
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
1021
|
+
hasResolvedFallback = f.stringValue
|
|
1022
|
+
}
|
|
1023
|
+
if (f.isResolvedFallback) {
|
|
1024
|
+
hasResolvedFallback = f.stringValue
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (!hasResolvedFallback && f.isVariable) {
|
|
1028
|
+
defaultValueIsVar = f
|
|
1029
|
+
}
|
|
1030
|
+
return `${f.stringValue || f.variable}${f.isResolvedFallback ? ' (Resolved default fallback)' : ''}`
|
|
1031
|
+
})).flat()
|
|
1032
|
+
|
|
1033
|
+
return [order, hasResolvedFallback]
|
|
1034
|
+
}
|
|
1035
|
+
return [[item.variable], undefined]
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const [resolveOrder, hasResolvedFallback] = calculateResolveOrder(lastItem)
|
|
1039
|
+
varData.resolveOrder = resolveOrder
|
|
1040
|
+
|
|
1041
|
+
if (defaultValueIsVar) {
|
|
1042
|
+
varData.defaultValueIsVar = defaultValueIsVar
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (typeof hasResolvedFallback !== 'undefined') {
|
|
1046
|
+
varData.defaultValue = hasResolvedFallback
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (typeof varData.defaultValue === 'undefined') {
|
|
1050
|
+
varData.isRequired = true
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (varData.resolveOrder.length > 1) {
|
|
1054
|
+
varData.hasFallback = true
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Extract file references
|
|
1058
|
+
nested.forEach((detail) => {
|
|
1059
|
+
if (detail.varType &&
|
|
1060
|
+
(detail.varType.startsWith('file(') || detail.varType.startsWith('text('))
|
|
1061
|
+
) {
|
|
1062
|
+
const fileMatch = detail.varType.match(/^(?:file|text)\((.*?)\)/)
|
|
1063
|
+
if (fileMatch && fileMatch[1]) {
|
|
1064
|
+
let fileContent = fileMatch[1].trim()
|
|
1065
|
+
|
|
1066
|
+
// Split by comma to separate file path from parameters/fallback values
|
|
1067
|
+
const parts = splitCsv(fileContent)
|
|
1068
|
+
let filePath = parts[0].trim()
|
|
1069
|
+
|
|
1070
|
+
// Remove quotes if present
|
|
1071
|
+
filePath = filePath.replace(/^['"]|['"]$/g, '')
|
|
1072
|
+
|
|
1073
|
+
// Normalize path: ensure relative paths start with ./
|
|
1074
|
+
let normalizedPath = filePath
|
|
1075
|
+
if (
|
|
1076
|
+
!filePath.startsWith('./') &&
|
|
1077
|
+
!filePath.startsWith('../') &&
|
|
1078
|
+
!filePath.startsWith('/') &&
|
|
1079
|
+
!filePath.startsWith('~')
|
|
1080
|
+
) {
|
|
1081
|
+
normalizedPath = './' + filePath
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// file .//
|
|
1085
|
+
if (normalizedPath.startsWith('.//')) {
|
|
1086
|
+
normalizedPath = normalizedPath.replace('.//', './')
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Handle variables in file paths - just record the pattern
|
|
1090
|
+
if (!fileRefs.includes(normalizedPath)) {
|
|
1091
|
+
fileRefs.push(normalizedPath)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Check if path contains variables and create glob pattern
|
|
1095
|
+
if (normalizedPath.match(variableSyntax)) {
|
|
1096
|
+
// Replace variable syntax ${...} with * for glob pattern
|
|
1097
|
+
const globPattern = normalizedPath.replace(variableSyntax, '*')
|
|
1098
|
+
if (!fileGlobPatterns.includes(globPattern)) {
|
|
1099
|
+
fileGlobPatterns.push(globPattern)
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
variableData[key] = (variableData[key] || []).concat(varData)
|
|
1107
|
+
foundVariables.push(rawValue)
|
|
1108
|
+
}
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
// Make foundVariables array unique
|
|
1112
|
+
const finalFoundVariables = [...new Set(foundVariables)]
|
|
1113
|
+
const varKeys = Object.keys(variableData)
|
|
1114
|
+
|
|
1115
|
+
// Calculate summary using same logic as CLI display
|
|
1116
|
+
let requiredCount = 0
|
|
1117
|
+
let withDefaultsCount = 0
|
|
1118
|
+
varKeys.forEach((key) => {
|
|
1119
|
+
const instances = variableData[key]
|
|
1120
|
+
const firstInstance = instances[0]
|
|
1121
|
+
|
|
1122
|
+
// Check if truly required using same logic as display code
|
|
1123
|
+
let isTrulyRequired = false
|
|
1124
|
+
if (typeof firstInstance.defaultValue === 'undefined') {
|
|
1125
|
+
// Check for self-references that resolve to config values
|
|
1126
|
+
let dotPropArr = []
|
|
1127
|
+
if (firstInstance.defaultValueIsVar && (
|
|
1128
|
+
firstInstance.defaultValueIsVar.varType === 'self:' ||
|
|
1129
|
+
firstInstance.defaultValueIsVar.varType === 'dot.prop'
|
|
1130
|
+
)) {
|
|
1131
|
+
dotPropArr = [firstInstance.defaultValueIsVar]
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const hasDotPropOrSelf = instances.reduce((acc, v) => {
|
|
1135
|
+
const dotProp = v.resolveDetails.find((d) => d.varType === 'dot.prop')
|
|
1136
|
+
if (dotProp) {
|
|
1137
|
+
acc.push(dotProp)
|
|
1138
|
+
}
|
|
1139
|
+
if (v.resolveDetails && v.resolveDetails.length === 1 && v.resolveDetails[0].varType === 'self:') {
|
|
1140
|
+
acc.push(v.resolveDetails[0])
|
|
1141
|
+
}
|
|
1142
|
+
return acc
|
|
1143
|
+
}, dotPropArr)
|
|
1144
|
+
|
|
1145
|
+
if (!hasDotPropOrSelf.length) {
|
|
1146
|
+
isTrulyRequired = true
|
|
1147
|
+
} else {
|
|
1148
|
+
// Check if the self-reference resolves to a value
|
|
1149
|
+
const cleanPath = hasDotPropOrSelf[0].variable.replace('self:', '')
|
|
1150
|
+
const dotPropValue = dotProp.get(this.originalConfig, cleanPath)
|
|
1151
|
+
if (typeof dotPropValue === 'undefined') {
|
|
1152
|
+
isTrulyRequired = true
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
if (isTrulyRequired) {
|
|
1158
|
+
requiredCount++
|
|
1159
|
+
} else {
|
|
1160
|
+
withDefaultsCount++
|
|
1161
|
+
}
|
|
1162
|
+
})
|
|
1163
|
+
|
|
1164
|
+
return {
|
|
1165
|
+
variables: variableData,
|
|
1166
|
+
summary: {
|
|
1167
|
+
totalVariables: varKeys.length,
|
|
1168
|
+
requiredVariables: requiredCount,
|
|
1169
|
+
variablesWithDefaults: withDefaultsCount
|
|
1170
|
+
},
|
|
1171
|
+
fileRefs: fileRefs,
|
|
1172
|
+
fileGlobPatterns: fileGlobPatterns,
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
891
1175
|
runFunction(variableString) {
|
|
1176
|
+
// console.log('runFunction', variableString)
|
|
892
1177
|
/* If json object value return it */
|
|
893
1178
|
if (variableString.match(/^\s*{/) && variableString.match(/}\s*$/)) {
|
|
894
1179
|
return variableString
|
|
@@ -916,7 +1201,6 @@ class Configorama {
|
|
|
916
1201
|
argsToPass = formatFunctionArgs(splitter)
|
|
917
1202
|
}
|
|
918
1203
|
// console.log('argsToPass runFunction', argsToPass)
|
|
919
|
-
|
|
920
1204
|
// TODO check for camelCase version. | toUpperCase messes with function name
|
|
921
1205
|
const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
|
|
922
1206
|
|
|
@@ -1014,6 +1298,15 @@ class Configorama {
|
|
|
1014
1298
|
}
|
|
1015
1299
|
}
|
|
1016
1300
|
leaf.originalSource = originalValue
|
|
1301
|
+
|
|
1302
|
+
// Check if we have existing resolution history from previous iterations
|
|
1303
|
+
const pathKey = thePath
|
|
1304
|
+
if (this.resolutionTracking[pathKey] && this.resolutionTracking[pathKey].resolutionHistory) {
|
|
1305
|
+
leaf.resolutionHistory = this.resolutionTracking[pathKey].resolutionHistory
|
|
1306
|
+
} else {
|
|
1307
|
+
leaf.resolutionHistory = []
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1017
1310
|
if (originalValue && isString(originalValue)) {
|
|
1018
1311
|
const varString = cleanVariable(originalValue, this.variableSyntax, true, `getProperties ${this.callCount}`)
|
|
1019
1312
|
if (varString.match(fileRefSyntax)) {
|
|
@@ -1025,7 +1318,6 @@ class Configorama {
|
|
|
1025
1318
|
}
|
|
1026
1319
|
return results
|
|
1027
1320
|
}
|
|
1028
|
-
|
|
1029
1321
|
/**
|
|
1030
1322
|
* @typedef {TerminalProperty} TerminalPropertyPopulated
|
|
1031
1323
|
* @property {Object} populated The populated value of the value at the path
|
|
@@ -1042,11 +1334,9 @@ class Configorama {
|
|
|
1042
1334
|
// Initial check if value has variable string in it
|
|
1043
1335
|
return isString(property.value) && property.value.match(this.variableSyntax)
|
|
1044
1336
|
})
|
|
1045
|
-
|
|
1046
1337
|
/*
|
|
1047
1338
|
console.log(`variables at call count ${this.callCount}`, variables)
|
|
1048
1339
|
/** */
|
|
1049
|
-
|
|
1050
1340
|
/* Exclude git messages from being processed */
|
|
1051
1341
|
// Was failing on git msgs like "xyz cron:pattern to cron(pattern) for improved clarity"
|
|
1052
1342
|
if (this.callCount > 1) {
|
|
@@ -1058,7 +1348,6 @@ class Configorama {
|
|
|
1058
1348
|
return true
|
|
1059
1349
|
})
|
|
1060
1350
|
}
|
|
1061
|
-
|
|
1062
1351
|
return map(variables, (valueObject) => {
|
|
1063
1352
|
// console.log('valueObject', valueObject)
|
|
1064
1353
|
return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
|
|
@@ -1161,16 +1450,127 @@ class Configorama {
|
|
|
1161
1450
|
*/
|
|
1162
1451
|
renderMatches(valueObject, matches, results) {
|
|
1163
1452
|
/*
|
|
1453
|
+
console.log('valueObject', valueObject)
|
|
1164
1454
|
console.log('RENDER', matches)
|
|
1165
1455
|
console.log('RESULTS', results)
|
|
1166
1456
|
/** */
|
|
1457
|
+
|
|
1458
|
+
/* Attach data to valueObject for parent details */
|
|
1459
|
+
if (matches.length === 1) {
|
|
1460
|
+
valueObject.currentVarDetails = matches[0]
|
|
1461
|
+
valueObject.currentVarDetails.result = results[0]
|
|
1462
|
+
|
|
1463
|
+
// Extract metadata from result if present
|
|
1464
|
+
let actualResult = results[0]
|
|
1465
|
+
let resolverType = undefined
|
|
1466
|
+
if (results[0] && typeof results[0] === 'object') {
|
|
1467
|
+
if (results[0].__internal_metadata) {
|
|
1468
|
+
actualResult = results[0].value
|
|
1469
|
+
resolverType = results[0].__resolverType
|
|
1470
|
+
} else if (results[0].__internal_only_flag) {
|
|
1471
|
+
// Don't extract value from __internal_only_flag objects, but grab resolverType if present
|
|
1472
|
+
actualResult = results[0]
|
|
1473
|
+
resolverType = results[0].__resolverType
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
// valueObject.currentVarDetails.varType = results[0].__resolverType
|
|
1477
|
+
|
|
1478
|
+
// Track resolution history
|
|
1479
|
+
if (!valueObject.resolutionHistory) {
|
|
1480
|
+
valueObject.resolutionHistory = []
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// Extract clean result to avoid circular references
|
|
1484
|
+
// For __internal_only_flag objects (like deep resolver results), extract the value
|
|
1485
|
+
// For real data objects (like file contents), keep them as-is
|
|
1486
|
+
let cleanResult = actualResult
|
|
1487
|
+
if (actualResult && typeof actualResult === 'object' && actualResult.__internal_only_flag) {
|
|
1488
|
+
cleanResult = actualResult.value
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
const historyEntry = {
|
|
1492
|
+
match: matches[0].match,
|
|
1493
|
+
variable: matches[0].variable,
|
|
1494
|
+
result: cleanResult,
|
|
1495
|
+
resultType: typeof cleanResult,
|
|
1496
|
+
valueBeforeResolution: valueObject.value,
|
|
1497
|
+
}
|
|
1498
|
+
if (resolverType) {
|
|
1499
|
+
historyEntry.varType = resolverType
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// Check if variable has fallback values (comma-separated)
|
|
1503
|
+
const variableParts = splitByComma(matches[0].variable)
|
|
1504
|
+
if (variableParts.length > 1) {
|
|
1505
|
+
historyEntry.hasFallback = true
|
|
1506
|
+
historyEntry.valueBeforeFallback = variableParts[0]
|
|
1507
|
+
historyEntry.fallbackValues = variableParts.slice(1).map((fallback) => {
|
|
1508
|
+
const trimmedFallback = fallback.trim()
|
|
1509
|
+
// Check if it's a variable reference
|
|
1510
|
+
const isVariable = trimmedFallback.match(this.variableSyntax) || trimmedFallback.match(this.variablesKnownTypes)
|
|
1511
|
+
const fallbackData = {
|
|
1512
|
+
isVariable: !!isVariable,
|
|
1513
|
+
fullMatch: trimmedFallback,
|
|
1514
|
+
variable: trimmedFallback,
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// If it's a literal string/number, parse it
|
|
1518
|
+
if (!isVariable) {
|
|
1519
|
+
// Check if it's a quoted string
|
|
1520
|
+
if (/^["'].*["']$/.test(trimmedFallback)) {
|
|
1521
|
+
fallbackData.stringValue = trimmedFallback.replace(/^["']|["']$/g, '')
|
|
1522
|
+
fallbackData.isResolvedFallback = true
|
|
1523
|
+
} else if (/^-?\d+(\.\d+)?$/.test(trimmedFallback)) {
|
|
1524
|
+
// It's a number
|
|
1525
|
+
fallbackData.numberValue = parseFloat(trimmedFallback)
|
|
1526
|
+
fallbackData.isResolvedFallback = true
|
|
1527
|
+
} else {
|
|
1528
|
+
fallbackData.stringValue = trimmedFallback
|
|
1529
|
+
fallbackData.isResolvedFallback = true
|
|
1530
|
+
}
|
|
1531
|
+
} else {
|
|
1532
|
+
// Extract varType from variable references
|
|
1533
|
+
const varTypeMatch = trimmedFallback.match(this.variablesKnownTypes)
|
|
1534
|
+
if (varTypeMatch && varTypeMatch[1]) {
|
|
1535
|
+
fallbackData.varType = varTypeMatch[1]
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
return fallbackData
|
|
1540
|
+
})
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// Only add to history if not a duplicate (same match + variable)
|
|
1544
|
+
const isDuplicate = valueObject.resolutionHistory.some(entry =>
|
|
1545
|
+
entry.match === historyEntry.match &&
|
|
1546
|
+
entry.variable === historyEntry.variable
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
if (!isDuplicate) {
|
|
1550
|
+
valueObject.resolutionHistory.push(historyEntry)
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Save resolution history to tracking map for persistence across iterations
|
|
1554
|
+
if (valueObject.path && valueObject.path.length) {
|
|
1555
|
+
const pathKey = valueObject.path.join('.')
|
|
1556
|
+
if (!this.resolutionTracking[pathKey]) {
|
|
1557
|
+
this.resolutionTracking[pathKey] = {
|
|
1558
|
+
path: pathKey,
|
|
1559
|
+
originalPropertyString: valueObject.originalSource,
|
|
1560
|
+
calls: []
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
this.resolutionTracking[pathKey].resolutionHistory = valueObject.resolutionHistory
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1167
1567
|
let result = valueObject.value
|
|
1168
1568
|
for (let i = 0; i < matches.length; i += 1) {
|
|
1169
1569
|
this.warnIfNotFound(matches[i].variable, results[i])
|
|
1170
1570
|
// console.log('Render MATCHES', results[i])
|
|
1171
1571
|
let valueToPop = results[i]
|
|
1172
1572
|
// TODO refactor this. __internal_only_flag needed to stop clash with sync/async file resolution
|
|
1173
|
-
if (results[i] && typeof results[i] === 'object' && results[i].__internal_only_flag) {
|
|
1573
|
+
if (results[i] && typeof results[i] === 'object' && (results[i].__internal_only_flag || results[i].__internal_metadata)) {
|
|
1174
1574
|
valueToPop = results[i].value
|
|
1175
1575
|
}
|
|
1176
1576
|
result = this.populateVariable(valueObject, matches[i].match, valueToPop)
|
|
@@ -1214,7 +1614,10 @@ class Configorama {
|
|
|
1214
1614
|
.then((result) => {
|
|
1215
1615
|
// console.log('renderMatches result', result)
|
|
1216
1616
|
if (root && isArray(matches)) {
|
|
1217
|
-
return this.populateValue({
|
|
1617
|
+
return this.populateValue({
|
|
1618
|
+
value: result.value,
|
|
1619
|
+
resolutionHistory: result.resolutionHistory || valueObject.resolutionHistory || []
|
|
1620
|
+
}, root, 'self populateValue')
|
|
1218
1621
|
}
|
|
1219
1622
|
return result
|
|
1220
1623
|
})
|
|
@@ -1252,11 +1655,11 @@ class Configorama {
|
|
|
1252
1655
|
|
|
1253
1656
|
const parts = splitByComma(variable, this.variableSyntax)
|
|
1254
1657
|
if (DEBUG) {
|
|
1255
|
-
console.log('parts', parts)
|
|
1256
|
-
console.log('parts variable:', variable)
|
|
1257
|
-
console.log('parts originalVar:', originalVar)
|
|
1258
|
-
console.log('parts
|
|
1259
|
-
console.log('All parts:', parts)
|
|
1658
|
+
console.log('splitAndGet parts', parts)
|
|
1659
|
+
console.log('splitAndGet parts variable:', variable)
|
|
1660
|
+
console.log('splitAndGet parts originalVar:', originalVar)
|
|
1661
|
+
console.log('splitAndGet parts current valueObject:', valueObject)
|
|
1662
|
+
console.log('splitAndGet All parts:', parts)
|
|
1260
1663
|
console.log('-----')
|
|
1261
1664
|
}
|
|
1262
1665
|
if (parts.length <= 1) {
|
|
@@ -1277,16 +1680,20 @@ class Configorama {
|
|
|
1277
1680
|
populateVariable(valueObject, matchedString, valueToPopulate) {
|
|
1278
1681
|
let property = valueObject.value
|
|
1279
1682
|
// console.log('init property', property)
|
|
1280
|
-
|
|
1683
|
+
|
|
1281
1684
|
if (DEBUG) {
|
|
1282
1685
|
console.log('────────START populateVar──────────────')
|
|
1283
|
-
console.log('
|
|
1284
|
-
console.log('
|
|
1285
|
-
console.log(
|
|
1286
|
-
console.log(`
|
|
1287
|
-
console.log(`
|
|
1288
|
-
console.log(
|
|
1289
|
-
console.log('
|
|
1686
|
+
console.log('populateVariable: valueObject', valueObject)
|
|
1687
|
+
console.log('populateVariable: valueToPopulate', valueToPopulate)
|
|
1688
|
+
console.log('populateVariable: typeof valueToPopulate', typeof valueToPopulate)
|
|
1689
|
+
console.log(`populateVariable: path "${valueObject.path}"`)
|
|
1690
|
+
console.log(`populateVariable: value \`${valueObject.value}\``)
|
|
1691
|
+
console.log(`populateVariable: originalSource \`${valueObject.originalSource}\``)
|
|
1692
|
+
console.log('populateVariable: property', property)
|
|
1693
|
+
console.log('populateVariable: matchedString', matchedString)
|
|
1694
|
+
if (valueObject.resolutionHistory && valueObject.resolutionHistory.length > 0) {
|
|
1695
|
+
console.log('populateVariable: resolutionHistory', JSON.stringify(valueObject.resolutionHistory, null, 2))
|
|
1696
|
+
}
|
|
1290
1697
|
}
|
|
1291
1698
|
|
|
1292
1699
|
const originalSrc = valueObject.originalSource || ''
|
|
@@ -1305,9 +1712,35 @@ class Configorama {
|
|
|
1305
1712
|
if (DEBUG_TYPE) console.log('DEBUG_TYPE total replacement')
|
|
1306
1713
|
const v = valueObject.value || ''
|
|
1307
1714
|
property = valueToPopulate
|
|
1715
|
+
// console.log('hasFilters', hasFilters)
|
|
1716
|
+
// console.log('valueToPopulate', valueToPopulate)
|
|
1717
|
+
/* Check resolution history for parent details */
|
|
1718
|
+
if (valueObject.resolutionHistory && valueObject.resolutionHistory.length) {
|
|
1719
|
+
const currentDetails = valueObject.resolutionHistory[valueObject.resolutionHistory.length - 1]
|
|
1720
|
+
|
|
1721
|
+
// get 2nd to last item in resolution history
|
|
1722
|
+
const parentDetails = valueObject.resolutionHistory[valueObject.resolutionHistory.length - 2]
|
|
1723
|
+
/*
|
|
1724
|
+
console.log('currentDetails', currentDetails)
|
|
1725
|
+
console.log('parentDetails', parentDetails)
|
|
1726
|
+
/** */
|
|
1727
|
+
|
|
1728
|
+
/* Convert a fallback number to string */
|
|
1729
|
+
if (currentDetails &&
|
|
1730
|
+
currentDetails.resultType === 'number' &&
|
|
1731
|
+
parentDetails && parentDetails.resultType === 'string' &&
|
|
1732
|
+
parentDetails.result.match(/^\d+$/) && parentDetails.varType === 'env'
|
|
1733
|
+
) {
|
|
1734
|
+
if (Number(parentDetails.result) === currentDetails.result) {
|
|
1735
|
+
property = String(valueToPopulate)
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
}
|
|
1308
1740
|
|
|
1309
1741
|
/* Handle ${self:custom.ref, ''} with deep values */
|
|
1310
1742
|
if (v.match(deepRefSyntax) && originalSrc.match(this.variableSyntax) && !v.match(/deep\:(\d*)\..*}$/)) {
|
|
1743
|
+
// console.log('deep ref syntax')
|
|
1311
1744
|
// console.log('deep var', this.deep)
|
|
1312
1745
|
// console.log('originalSrc', originalSrc)
|
|
1313
1746
|
// console.log('value', v)
|
|
@@ -1410,7 +1843,12 @@ class Configorama {
|
|
|
1410
1843
|
missingValue = this.deep[i]
|
|
1411
1844
|
}
|
|
1412
1845
|
|
|
1413
|
-
const cleanVar = cleanVariable(
|
|
1846
|
+
const cleanVar = cleanVariable(
|
|
1847
|
+
property,
|
|
1848
|
+
this.variableSyntax,
|
|
1849
|
+
true,
|
|
1850
|
+
`populateVariable fallback ${this.callCount}`
|
|
1851
|
+
)
|
|
1414
1852
|
const cleanVarNoFilters = cleanVar.split('|')[0]
|
|
1415
1853
|
const splitVars = splitByComma(cleanVarNoFilters)
|
|
1416
1854
|
const nestedVar = findNestedVariable(splitVars, valueObject.originalSource)
|
|
@@ -1425,6 +1863,7 @@ class Configorama {
|
|
|
1425
1863
|
value: fallbackStr,
|
|
1426
1864
|
path: valueObject.path,
|
|
1427
1865
|
originalSource: valueObject.originalSource,
|
|
1866
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1428
1867
|
// set __internal_only_flag to note this is object we make not a resolved value
|
|
1429
1868
|
__internal_only_flag: true,
|
|
1430
1869
|
caller: 'nestedVar',
|
|
@@ -1476,6 +1915,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1476
1915
|
value: finalProp, // prop to fix nested ¯\_(ツ)_/¯
|
|
1477
1916
|
path: valueObject.path,
|
|
1478
1917
|
originalSource: valueObject.originalSource,
|
|
1918
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1479
1919
|
// set __internal_only_flag to note this is object we make not a resolved value
|
|
1480
1920
|
// __internal_only_flag: true
|
|
1481
1921
|
}
|
|
@@ -1494,11 +1934,18 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1494
1934
|
}
|
|
1495
1935
|
}
|
|
1496
1936
|
*/
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1937
|
+
|
|
1938
|
+
if (
|
|
1939
|
+
/* Not another variable reference */
|
|
1940
|
+
!prop.match(this.variableSyntax)
|
|
1941
|
+
&&
|
|
1942
|
+
/* Not file or text refs */
|
|
1943
|
+
!prop.match(fileRefSyntax)
|
|
1944
|
+
&& !prop.match(textRefSyntax)
|
|
1945
|
+
/* Not eval refs */
|
|
1946
|
+
&& !prop.match(getValueFromEval.match)
|
|
1947
|
+
// AND is not multiline value
|
|
1948
|
+
&& (func && prop.split('\n').length < 3)) {
|
|
1502
1949
|
// console.log('IS FUNCTION')
|
|
1503
1950
|
/* if matches function signature like ${merge('foo', 'bar')}
|
|
1504
1951
|
rewrite the variable to run the function after inputs resolved
|
|
@@ -1516,14 +1963,21 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1516
1963
|
|
|
1517
1964
|
// console.log('foundFilters', foundFilters)
|
|
1518
1965
|
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
typeof valueToPopulate === 'string' &&
|
|
1524
|
-
!valueToPopulate.match(deepRefSyntax) &&
|
|
1966
|
+
let runFilters = false
|
|
1967
|
+
if (typeof valueToPopulate === 'number' && foundFilters.length) {
|
|
1968
|
+
runFilters = true
|
|
1969
|
+
} else if (
|
|
1970
|
+
typeof valueToPopulate === 'string' &&
|
|
1971
|
+
!valueToPopulate.match(deepRefSyntax) &&
|
|
1972
|
+
foundFilters.length &&
|
|
1525
1973
|
!property.match(this.variableSyntax)
|
|
1526
1974
|
) {
|
|
1975
|
+
runFilters = true
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
/* Apply filters if found */
|
|
1979
|
+
//console.log('> property', property)
|
|
1980
|
+
if (runFilters) {
|
|
1527
1981
|
// If filter cache exists we need to remove filter that have already been run
|
|
1528
1982
|
if (this.filterCache[valueObject.path]) {
|
|
1529
1983
|
foundFilters = foundFilters.filter((filter) => {
|
|
@@ -1548,6 +2002,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1548
2002
|
value: property,
|
|
1549
2003
|
path: valueObject.path,
|
|
1550
2004
|
originalSource: valueObject.originalSource,
|
|
2005
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1551
2006
|
__internal_only_flag: true, // set __internal_only_flag to note this is object we make not a resolved value
|
|
1552
2007
|
caller: 'end',
|
|
1553
2008
|
count: this.callCount,
|
|
@@ -1589,15 +2044,22 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1589
2044
|
// console.log('propertyString', typeof propertyString)
|
|
1590
2045
|
const variableValues = variableStrings.map((variableString) => {
|
|
1591
2046
|
// This runs on nested variable resolution
|
|
1592
|
-
return this.getValueFromSource(variableString, valueObject, 'overwrite')
|
|
2047
|
+
return this.getValueFromSource(variableString, valueObject, 'overwrite', valueObject.originalSource)
|
|
1593
2048
|
})
|
|
1594
|
-
|
|
2049
|
+
|
|
1595
2050
|
// console.log('variableValues', variableValues)
|
|
1596
2051
|
return Promise.all(variableValues).then((values) => {
|
|
1597
2052
|
let deepPropertyStr = propertyString
|
|
1598
2053
|
let deepProperties = 0
|
|
1599
2054
|
// console.log('overwrite values', valuesToUse)
|
|
1600
|
-
values
|
|
2055
|
+
// Extract actual values from metadata objects
|
|
2056
|
+
const extractedValues = values.map((value) => {
|
|
2057
|
+
if (value && typeof value === 'object' && (value.__internal_only_flag || value.__internal_metadata)) {
|
|
2058
|
+
return value.value
|
|
2059
|
+
}
|
|
2060
|
+
return value
|
|
2061
|
+
})
|
|
2062
|
+
extractedValues.forEach((value, index) => {
|
|
1601
2063
|
// console.log('───────────────────────────────> value', value)
|
|
1602
2064
|
if (isString(value) && value.match(this.variableSyntax)) {
|
|
1603
2065
|
deepProperties += 1
|
|
@@ -1613,7 +2075,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1613
2075
|
})
|
|
1614
2076
|
return deepProperties > 0
|
|
1615
2077
|
? Promise.resolve(deepPropertyStr) // return deep variable replacement of original
|
|
1616
|
-
: Promise.resolve(
|
|
2078
|
+
: Promise.resolve(extractedValues.find(isValidValue)) // resolve first valid value, else undefined
|
|
1617
2079
|
})
|
|
1618
2080
|
}
|
|
1619
2081
|
/**
|
|
@@ -1625,6 +2087,25 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1625
2087
|
// console.log('getValueFromSrc caller', caller)
|
|
1626
2088
|
const propertyString = valueObject.value
|
|
1627
2089
|
const pathValue = valueObject.path
|
|
2090
|
+
|
|
2091
|
+
// Track every call to getValueFromSource for metadata
|
|
2092
|
+
if (pathValue && pathValue.length) {
|
|
2093
|
+
const pathKey = pathValue.join('.')
|
|
2094
|
+
if (!this.resolutionTracking[pathKey]) {
|
|
2095
|
+
this.resolutionTracking[pathKey] = {
|
|
2096
|
+
path: pathKey,
|
|
2097
|
+
originalPropertyString: propertyString,
|
|
2098
|
+
calls: []
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
this.resolutionTracking[pathKey].calls.push({
|
|
2103
|
+
variableString: variableString,
|
|
2104
|
+
propertyString: propertyString,
|
|
2105
|
+
caller: caller
|
|
2106
|
+
})
|
|
2107
|
+
}
|
|
2108
|
+
|
|
1628
2109
|
// console.log('getValueFromSrc propertyString', propertyString)
|
|
1629
2110
|
// console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
|
|
1630
2111
|
if (this.tracker.contains(variableString)) {
|
|
@@ -1635,11 +2116,12 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1635
2116
|
let newHasFilter
|
|
1636
2117
|
// Else lookup value from various sources
|
|
1637
2118
|
if (DEBUG) {
|
|
1638
|
-
console.log(`>>>>> getValueFromSrc()
|
|
1639
|
-
console.log('
|
|
1640
|
-
console.log('
|
|
1641
|
-
console.log('
|
|
1642
|
-
console.log('
|
|
2119
|
+
console.log(`>>>>> getValueFromSrc() caller - ${caller}`)
|
|
2120
|
+
console.log('getValueFromSource originalVar', originalVar)
|
|
2121
|
+
console.log('getValueFromSource variableString:', variableString)
|
|
2122
|
+
console.log('getValueFromSource propertyString:', propertyString)
|
|
2123
|
+
console.log('getValueFromSource pathValue:', valueObject.path)
|
|
2124
|
+
console.log('getValueFromSource valueObject:', valueObject)
|
|
1643
2125
|
console.log('-----')
|
|
1644
2126
|
}
|
|
1645
2127
|
|
|
@@ -1667,6 +2149,8 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1667
2149
|
})
|
|
1668
2150
|
.map((f) => {
|
|
1669
2151
|
return trim(f)
|
|
2152
|
+
// TODO refactor this. This is a temp fix for filters with nested vars.
|
|
2153
|
+
.replace(/}$/, '')
|
|
1670
2154
|
})
|
|
1671
2155
|
// console.log('filters to run', _filter)
|
|
1672
2156
|
|
|
@@ -1723,6 +2207,21 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1723
2207
|
valueObject,
|
|
1724
2208
|
|
|
1725
2209
|
).then((val) => {
|
|
2210
|
+
// Update the last call with the resolved value
|
|
2211
|
+
if (pathValue && pathValue.length) {
|
|
2212
|
+
const pathKey = pathValue.join('.')
|
|
2213
|
+
if (this.resolutionTracking[pathKey] && this.resolutionTracking[pathKey].calls.length) {
|
|
2214
|
+
// Find the most recent call for this variableString
|
|
2215
|
+
for (let i = this.resolutionTracking[pathKey].calls.length - 1; i >= 0; i--) {
|
|
2216
|
+
if (this.resolutionTracking[pathKey].calls[i].variableString === variableString) {
|
|
2217
|
+
this.resolutionTracking[pathKey].calls[i].resolvedValue = val
|
|
2218
|
+
this.resolutionTracking[pathKey].calls[i].resolverType = resolverType
|
|
2219
|
+
break
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
|
|
1726
2225
|
// console.log('VALUE', val)
|
|
1727
2226
|
if (
|
|
1728
2227
|
val === null ||
|
|
@@ -1756,7 +2255,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1756
2255
|
// console.log('valueCount', valueCount)
|
|
1757
2256
|
// TODO throw on empty values?
|
|
1758
2257
|
// No fallback value found AND this is undefined, throw error
|
|
1759
|
-
const nestedVars = findNestedVariables(propertyString, this.variableSyntax)
|
|
2258
|
+
const nestedVars = findNestedVariables(propertyString, this.variableSyntax, this.variablesKnownTypes)
|
|
1760
2259
|
// console.log('nestedVars', nestedVars)
|
|
1761
2260
|
const noNestedVars = nestedVars.length < 2
|
|
1762
2261
|
if (valueCount.length === 1 && noNestedVars) {
|
|
@@ -1785,7 +2284,19 @@ Unable to resolve configuration variable
|
|
|
1785
2284
|
if (!newHasFilter) {
|
|
1786
2285
|
// console.log('no newHasFilter', val, valueObject)
|
|
1787
2286
|
// console.log('> RESOLVER RETURN newValue 3', val, originalVar)
|
|
1788
|
-
|
|
2287
|
+
// Wrap value with resolverType metadata for resolution tracking
|
|
2288
|
+
// But don't wrap if it's already an internal flag object
|
|
2289
|
+
if (val && typeof val === 'object' && val.__internal_only_flag) {
|
|
2290
|
+
// Attach resolverType to existing internal object
|
|
2291
|
+
val.__resolverType = resolverType
|
|
2292
|
+
return Promise.resolve(val)
|
|
2293
|
+
}
|
|
2294
|
+
return Promise.resolve({
|
|
2295
|
+
value: val,
|
|
2296
|
+
__resolverType: resolverType,
|
|
2297
|
+
__variableString: variableString,
|
|
2298
|
+
__internal_metadata: true
|
|
2299
|
+
})
|
|
1789
2300
|
}
|
|
1790
2301
|
|
|
1791
2302
|
const newUse = newHasFilter.reduce((acc, currentFilter, i) => {
|
|
@@ -1798,6 +2309,8 @@ Unable to resolve configuration variable
|
|
|
1798
2309
|
// args: argsToPass
|
|
1799
2310
|
})
|
|
1800
2311
|
}, [])
|
|
2312
|
+
// console.log('pathValue', pathValue)
|
|
2313
|
+
// console.log('propertyString', propertyString)
|
|
1801
2314
|
// console.log('newUse', newUse)
|
|
1802
2315
|
|
|
1803
2316
|
if (typeof val === 'string' && val.match(/deep:/)) {
|
|
@@ -1834,7 +2347,19 @@ Unable to resolve configuration variable
|
|
|
1834
2347
|
}, val)
|
|
1835
2348
|
// console.log('> RESOLVER RETURN newValue', newValue)
|
|
1836
2349
|
// console.log('> RESOLVER RETURN newValue 5', newValue)
|
|
1837
|
-
|
|
2350
|
+
// Wrap value with resolverType metadata for resolution tracking
|
|
2351
|
+
// But don't wrap if it's already an internal flag object
|
|
2352
|
+
if (newValue && typeof newValue === 'object' && newValue.__internal_only_flag) {
|
|
2353
|
+
// Attach resolverType to existing internal object
|
|
2354
|
+
newValue.__resolverType = resolverType
|
|
2355
|
+
return Promise.resolve(newValue)
|
|
2356
|
+
}
|
|
2357
|
+
return Promise.resolve({
|
|
2358
|
+
value: newValue,
|
|
2359
|
+
__resolverType: resolverType,
|
|
2360
|
+
__variableString: variableString,
|
|
2361
|
+
__internal_metadata: true
|
|
2362
|
+
})
|
|
1838
2363
|
})
|
|
1839
2364
|
|
|
1840
2365
|
// console.log('valuePromise', valuePromise)
|
|
@@ -1844,12 +2369,20 @@ Unable to resolve configuration variable
|
|
|
1844
2369
|
return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter, promiseKey)
|
|
1845
2370
|
}
|
|
1846
2371
|
|
|
2372
|
+
// console.log('fall thru variableString', variableString)
|
|
2373
|
+
|
|
1847
2374
|
/* fall through case with self refs */
|
|
1848
2375
|
if (variableString) {
|
|
1849
2376
|
// console.log('before clean propertyString', propertyString, variableString)
|
|
1850
|
-
const clean = cleanVariable(
|
|
2377
|
+
const clean = cleanVariable(
|
|
2378
|
+
propertyString,
|
|
2379
|
+
this.variableSyntax,
|
|
2380
|
+
true,
|
|
2381
|
+
`getValueFromSrc self ${this.callCount}`
|
|
2382
|
+
)
|
|
1851
2383
|
// TODO @DWELLS cleanVariable makes fallback values with spaces have no spaces
|
|
1852
2384
|
// console.log('AFTER cleanVariable', clean)
|
|
2385
|
+
// console.log(typeof clean)
|
|
1853
2386
|
const cleanClean = clean.split('|')[0]
|
|
1854
2387
|
// console.log('cleanCleanVariable', cleanClean)
|
|
1855
2388
|
if (funcRegex.exec(cleanClean)) {
|
|
@@ -1858,7 +2391,8 @@ Unable to resolve configuration variable
|
|
|
1858
2391
|
}
|
|
1859
2392
|
|
|
1860
2393
|
const split = splitByComma(cleanClean)
|
|
1861
|
-
|
|
2394
|
+
// console.log('split', split)
|
|
2395
|
+
// console.log('typeof split', typeof split)
|
|
1862
2396
|
// @TODO refactor this. USE FILTER [ 'commas', 'split("-"' ] is wrong
|
|
1863
2397
|
let fallbackValue
|
|
1864
2398
|
if (split.length === 2 || split.length === 3) {
|
|
@@ -1867,7 +2401,9 @@ Unable to resolve configuration variable
|
|
|
1867
2401
|
fallbackValue = split[0]
|
|
1868
2402
|
}
|
|
1869
2403
|
|
|
2404
|
+
// TODO this should be new in memory resolutionHistory probably?
|
|
1870
2405
|
const nestedVar = findNestedVariable(split, valueObject.originalSource)
|
|
2406
|
+
// console.log('nestedVar', nestedVar)
|
|
1871
2407
|
|
|
1872
2408
|
if (nestedVar) {
|
|
1873
2409
|
if (!this.opts.allowUnknownVars) {
|
|
@@ -1876,7 +2412,7 @@ Unable to resolve configuration variable
|
|
|
1876
2412
|
const fallbackStr = getFallbackString(split, nestedVar)
|
|
1877
2413
|
return this.getValueFromSource(variableString, {
|
|
1878
2414
|
value: fallbackStr,
|
|
1879
|
-
}, 'nestedVar')
|
|
2415
|
+
}, 'nestedVar', originalVar)
|
|
1880
2416
|
}
|
|
1881
2417
|
|
|
1882
2418
|
// TODO verify we need this still with file(file.js, param)
|
|
@@ -1888,7 +2424,7 @@ Unable to resolve configuration variable
|
|
|
1888
2424
|
// recurse on fallback and check again
|
|
1889
2425
|
return this.getValueFromSource(`${variableString})`, {
|
|
1890
2426
|
value: propertyString,
|
|
1891
|
-
}, 'cleanClean.match(fileRefSyntax)')
|
|
2427
|
+
}, 'cleanClean.match(fileRefSyntax)', originalVar)
|
|
1892
2428
|
}
|
|
1893
2429
|
}
|
|
1894
2430
|
// const fallbackValue = split[1]
|
|
@@ -1900,7 +2436,10 @@ Unable to resolve configuration variable
|
|
|
1900
2436
|
const valuePromise = Promise.resolve(fallbackValue)
|
|
1901
2437
|
return this.tracker.add(fallbackValue, valuePromise, propertyString, newHasFilter)
|
|
1902
2438
|
}
|
|
1903
|
-
|
|
2439
|
+
/*
|
|
2440
|
+
console.log('what is fallbackValue', fallbackValue)
|
|
2441
|
+
console.log('typeof fallbackValue', typeof fallbackValue)
|
|
2442
|
+
/** */
|
|
1904
2443
|
// has fallback but needs deeper lookup. Call getValueFromSrc again
|
|
1905
2444
|
if (fallbackValue) {
|
|
1906
2445
|
if (DEBUG) console.log('fallbackValue', fallbackValue)
|
|
@@ -1908,11 +2447,21 @@ Unable to resolve configuration variable
|
|
|
1908
2447
|
// recurse on fallback and check again
|
|
1909
2448
|
return this.getValueFromSource(
|
|
1910
2449
|
fallbackValue,
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
2450
|
+
valueObject,
|
|
2451
|
+
// Object.assign({}, valueObject, { value: propertyString }),
|
|
2452
|
+
// {
|
|
2453
|
+
// value: propertyString,
|
|
2454
|
+
// path: valueObject.path,
|
|
2455
|
+
// originalSource: valueObject.originalSource,
|
|
2456
|
+
// ahh:true
|
|
2457
|
+
// },
|
|
1914
2458
|
'fallbackValue',
|
|
1915
|
-
|
|
2459
|
+
originalVar,
|
|
2460
|
+
).then((res) => {
|
|
2461
|
+
// console.log('res', res)
|
|
2462
|
+
// console.log('typeof res', typeof res)
|
|
2463
|
+
return res
|
|
2464
|
+
})
|
|
1916
2465
|
}
|
|
1917
2466
|
}
|
|
1918
2467
|
|
|
@@ -1925,7 +2474,9 @@ Unable to resolve configuration variable
|
|
|
1925
2474
|
]
|
|
1926
2475
|
|
|
1927
2476
|
// Default value used for self variable
|
|
1928
|
-
if (
|
|
2477
|
+
// Only show this error if the variable itself (not a parent fallback) is a self-reference with a fallback
|
|
2478
|
+
const isSelfReference = !variableString.match(/^(env|opt|file|text|cron|eval|git):/)
|
|
2479
|
+
if (isSelfReference && variableString.match(/,/)) {
|
|
1929
2480
|
errorMessage.push('\n Default values for self referenced values are not allowed')
|
|
1930
2481
|
errorMessage.push(`\n Fix the ${propertyString} variable`)
|
|
1931
2482
|
}
|
|
@@ -1933,23 +2484,30 @@ Unable to resolve configuration variable
|
|
|
1933
2484
|
let allowSpecialCase = false
|
|
1934
2485
|
/* handle special cases for cloudformation ${Sub} values */
|
|
1935
2486
|
if (this.originalConfig && key.endsWith('Fn::Sub')) {
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2487
|
+
if (this.opts.verifySubReferences) {
|
|
2488
|
+
const params = this.originalConfig.Parameters || (this.originalConfig.resources || {}).Parameters
|
|
2489
|
+
const resources = this.originalConfig.Resources || (this.originalConfig.resources || {}).Resources
|
|
2490
|
+
/* Cloudformation Resource References */
|
|
2491
|
+
if (resources && resources[variableString]) {
|
|
2492
|
+
allowSpecialCase = true
|
|
2493
|
+
} else if (params && params[variableString]) {
|
|
2494
|
+
allowSpecialCase = true
|
|
2495
|
+
} else if (variableString === 'ApiGatewayRestApi') {
|
|
2496
|
+
// Allow for "hidden" cloudformation variables, set by sls framework
|
|
2497
|
+
allowSpecialCase = true
|
|
2498
|
+
} else if (variableString === 'HttpApi') {
|
|
2499
|
+
// Allow for "hidden" cloudformation variables, set by sls framework
|
|
2500
|
+
allowSpecialCase = true
|
|
2501
|
+
}
|
|
2502
|
+
} else {
|
|
2503
|
+
// Default let any sub references pass through
|
|
1948
2504
|
allowSpecialCase = true
|
|
1949
2505
|
}
|
|
1950
2506
|
}
|
|
1951
2507
|
/* Todo handle stage variables */
|
|
1952
2508
|
|
|
2509
|
+
|
|
2510
|
+
|
|
1953
2511
|
/* Pass through unknown variables */
|
|
1954
2512
|
if (this.opts.allowUnknownVars || allowSpecialCase) {
|
|
1955
2513
|
// console.log('allowUnknownVars propertyString', propertyString)
|
|
@@ -2054,7 +2612,10 @@ Unable to resolve configuration variable
|
|
|
2054
2612
|
} else if (resolvedPath.match(/\.\//)) {
|
|
2055
2613
|
// TODO test higher parent refs
|
|
2056
2614
|
const cleanName = path.basename(resolvedPath)
|
|
2057
|
-
|
|
2615
|
+
const findUpResult = findUp.sync(cleanName, { cwd: this.configPath })
|
|
2616
|
+
if (findUpResult) {
|
|
2617
|
+
fullFilePath = findUpResult
|
|
2618
|
+
}
|
|
2058
2619
|
}
|
|
2059
2620
|
|
|
2060
2621
|
let fileExtension = resolvedPath.split('.')
|
|
@@ -2063,18 +2624,39 @@ Unable to resolve configuration variable
|
|
|
2063
2624
|
|
|
2064
2625
|
// Validate file exists
|
|
2065
2626
|
if (!fs.existsSync(fullFilePath)) {
|
|
2627
|
+
const originalVar = options.context && options.context.originalSource
|
|
2628
|
+
|
|
2629
|
+
const findNestedResult = findNestedVariables(
|
|
2630
|
+
originalVar,
|
|
2631
|
+
this.variableSyntax,
|
|
2632
|
+
this.variablesKnownTypes,
|
|
2633
|
+
options.context.path
|
|
2634
|
+
)
|
|
2635
|
+
// console.log('findNestedResult', findNestedResult)
|
|
2636
|
+
let hasFallback = false
|
|
2637
|
+
if (findNestedResult) {
|
|
2638
|
+
const varDetails = findNestedResult[0]
|
|
2639
|
+
// console.log('varDetails', varDetails)
|
|
2640
|
+
hasFallback = varDetails.hasFallback
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// check if original var has fallback value
|
|
2066
2644
|
// console.log('NO FILE FOUND', fullFilePath)
|
|
2067
2645
|
// console.log('variableString', variableString)
|
|
2068
|
-
|
|
2069
|
-
|
|
2646
|
+
|
|
2647
|
+
if (!hasFallback) {
|
|
2648
|
+
const errorMsg = makeBox({
|
|
2649
|
+
title: `File Not Found in ${originalVar}`,
|
|
2650
|
+
text: `Variable ${variableString} cannot resolve due to missing file.
|
|
2070
2651
|
|
|
2071
2652
|
File not found ${fullFilePath}
|
|
2072
2653
|
|
|
2073
2654
|
Default fallback value will be used if provided.
|
|
2074
|
-
${logLines}
|
|
2075
|
-
`
|
|
2076
2655
|
|
|
2077
|
-
|
|
2656
|
+
${JSON.stringify(options.context, null, 2)}`,
|
|
2657
|
+
})
|
|
2658
|
+
console.log(errorMsg)
|
|
2659
|
+
}
|
|
2078
2660
|
// TODO maybe reject. YAML does not allow for null/undefined values
|
|
2079
2661
|
// return Promise.reject(new Error(errorMsg))
|
|
2080
2662
|
return Promise.resolve(undefined)
|
|
@@ -2092,6 +2674,7 @@ ${logLines}
|
|
|
2092
2674
|
|
|
2093
2675
|
// Process JS files
|
|
2094
2676
|
if (fileExtension === 'js' || fileExtension === 'cjs') {
|
|
2677
|
+
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
2095
2678
|
const jsFile = require(fullFilePath)
|
|
2096
2679
|
let returnValueFunction = jsFile
|
|
2097
2680
|
// TODO change how exported functions are referenced
|
|
@@ -2191,6 +2774,7 @@ Check if your TypeScript is returning the correct data.`
|
|
|
2191
2774
|
}
|
|
2192
2775
|
|
|
2193
2776
|
if (fileExtension === 'mjs' || fileExtension === 'esm') {
|
|
2777
|
+
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
2194
2778
|
const { executeESMFile } = require('./parsers/esm')
|
|
2195
2779
|
let returnValueFunction
|
|
2196
2780
|
const variableArray = variableString.split(':')
|
|
@@ -2262,9 +2846,10 @@ Check if your ESM is returning the correct data.`
|
|
|
2262
2846
|
// console.log('deep', variableString)
|
|
2263
2847
|
// console.log('matchedFileString', matchedFileString)
|
|
2264
2848
|
let deepProperties = variableString.replace(matchedFileString, '')
|
|
2849
|
+
// TODO 2025-11-12 add file.path.support instead of just :
|
|
2265
2850
|
if (deepProperties.substring(0, 1) !== ':') {
|
|
2266
2851
|
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}" sub properties
|
|
2267
|
-
Please use ":" to reference sub properties`
|
|
2852
|
+
Please use ":" to reference sub properties. ${deepProperties}`
|
|
2268
2853
|
return Promise.reject(new Error(errorMessage))
|
|
2269
2854
|
}
|
|
2270
2855
|
deepProperties = deepProperties.slice(1).split('.')
|
|
@@ -2291,7 +2876,7 @@ Please use ":" to reference sub properties`
|
|
|
2291
2876
|
return Promise.resolve(valueToPopulate)
|
|
2292
2877
|
}
|
|
2293
2878
|
}
|
|
2294
|
-
console.log('fall thru', valueToPopulate)
|
|
2879
|
+
// console.log('fall thru', valueToPopulate)
|
|
2295
2880
|
return Promise.resolve(valueToPopulate)
|
|
2296
2881
|
}
|
|
2297
2882
|
getVariableFromDeep(variableString) {
|
|
@@ -2303,15 +2888,22 @@ Please use ":" to reference sub properties`
|
|
|
2303
2888
|
/** */
|
|
2304
2889
|
return this.deep[index]
|
|
2305
2890
|
}
|
|
2306
|
-
getValueFromDeep(variableString) {
|
|
2891
|
+
getValueFromDeep(variableString, pathValue) {
|
|
2307
2892
|
const variable = this.getVariableFromDeep(variableString)
|
|
2308
2893
|
const deepRef = variableString.replace(deepPrefixReplacePattern, '')
|
|
2309
2894
|
/*
|
|
2310
2895
|
console.log("GET getValueFromDeep", variableString)
|
|
2311
|
-
console.log('deepRef', deepRef)
|
|
2896
|
+
console.log('deepRef', (deepRef) ? deepRef : '- no deepRef')
|
|
2312
2897
|
console.log('getValueFromDeep variable', variable)
|
|
2313
2898
|
/** */
|
|
2314
|
-
|
|
2899
|
+
// Preserve path and originalSource information from pathValue
|
|
2900
|
+
const valueObject = {
|
|
2901
|
+
value: variable,
|
|
2902
|
+
path: pathValue ? pathValue.path : undefined,
|
|
2903
|
+
originalSource: pathValue ? pathValue.originalSource : undefined,
|
|
2904
|
+
resolutionHistory: pathValue ? pathValue.resolutionHistory : []
|
|
2905
|
+
}
|
|
2906
|
+
let ret = this.populateValue(valueObject, undefined, 'getValueFromDeep')
|
|
2315
2907
|
if (deepRef.length) {
|
|
2316
2908
|
// if there is a deep reference remaining
|
|
2317
2909
|
ret = ret.then((result) => {
|
|
@@ -2335,7 +2927,12 @@ Please use ":" to reference sub properties`
|
|
|
2335
2927
|
}
|
|
2336
2928
|
// console.log("makeDeepVariable SET INDEX", index)
|
|
2337
2929
|
const variableContainer = variable.match(this.variableSyntax)[0]
|
|
2338
|
-
const variableString = cleanVariable(
|
|
2930
|
+
const variableString = cleanVariable(
|
|
2931
|
+
variableContainer,
|
|
2932
|
+
this.variableSyntax,
|
|
2933
|
+
true,
|
|
2934
|
+
`makeDeepVariable ${this.callCount}`
|
|
2935
|
+
)
|
|
2339
2936
|
const deepVar = variableContainer.replace(variableString, `deep:${index}`)
|
|
2340
2937
|
/*
|
|
2341
2938
|
console.log('MAKE DEEP', variable, caller)
|