configorama 0.6.7 → 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 +780 -190
- 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
|
|
|
@@ -889,6 +964,214 @@ class Configorama {
|
|
|
889
964
|
})
|
|
890
965
|
})
|
|
891
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
|
+
}
|
|
892
1175
|
runFunction(variableString) {
|
|
893
1176
|
// console.log('runFunction', variableString)
|
|
894
1177
|
/* If json object value return it */
|
|
@@ -918,7 +1201,6 @@ class Configorama {
|
|
|
918
1201
|
argsToPass = formatFunctionArgs(splitter)
|
|
919
1202
|
}
|
|
920
1203
|
// console.log('argsToPass runFunction', argsToPass)
|
|
921
|
-
|
|
922
1204
|
// TODO check for camelCase version. | toUpperCase messes with function name
|
|
923
1205
|
const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
|
|
924
1206
|
|
|
@@ -1016,6 +1298,15 @@ class Configorama {
|
|
|
1016
1298
|
}
|
|
1017
1299
|
}
|
|
1018
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
|
+
|
|
1019
1310
|
if (originalValue && isString(originalValue)) {
|
|
1020
1311
|
const varString = cleanVariable(originalValue, this.variableSyntax, true, `getProperties ${this.callCount}`)
|
|
1021
1312
|
if (varString.match(fileRefSyntax)) {
|
|
@@ -1027,7 +1318,6 @@ class Configorama {
|
|
|
1027
1318
|
}
|
|
1028
1319
|
return results
|
|
1029
1320
|
}
|
|
1030
|
-
|
|
1031
1321
|
/**
|
|
1032
1322
|
* @typedef {TerminalProperty} TerminalPropertyPopulated
|
|
1033
1323
|
* @property {Object} populated The populated value of the value at the path
|
|
@@ -1044,11 +1334,9 @@ class Configorama {
|
|
|
1044
1334
|
// Initial check if value has variable string in it
|
|
1045
1335
|
return isString(property.value) && property.value.match(this.variableSyntax)
|
|
1046
1336
|
})
|
|
1047
|
-
|
|
1048
1337
|
/*
|
|
1049
1338
|
console.log(`variables at call count ${this.callCount}`, variables)
|
|
1050
1339
|
/** */
|
|
1051
|
-
|
|
1052
1340
|
/* Exclude git messages from being processed */
|
|
1053
1341
|
// Was failing on git msgs like "xyz cron:pattern to cron(pattern) for improved clarity"
|
|
1054
1342
|
if (this.callCount > 1) {
|
|
@@ -1060,7 +1348,6 @@ class Configorama {
|
|
|
1060
1348
|
return true
|
|
1061
1349
|
})
|
|
1062
1350
|
}
|
|
1063
|
-
|
|
1064
1351
|
return map(variables, (valueObject) => {
|
|
1065
1352
|
// console.log('valueObject', valueObject)
|
|
1066
1353
|
return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
|
|
@@ -1163,16 +1450,127 @@ class Configorama {
|
|
|
1163
1450
|
*/
|
|
1164
1451
|
renderMatches(valueObject, matches, results) {
|
|
1165
1452
|
/*
|
|
1453
|
+
console.log('valueObject', valueObject)
|
|
1166
1454
|
console.log('RENDER', matches)
|
|
1167
1455
|
console.log('RESULTS', results)
|
|
1168
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
|
+
|
|
1169
1567
|
let result = valueObject.value
|
|
1170
1568
|
for (let i = 0; i < matches.length; i += 1) {
|
|
1171
1569
|
this.warnIfNotFound(matches[i].variable, results[i])
|
|
1172
1570
|
// console.log('Render MATCHES', results[i])
|
|
1173
1571
|
let valueToPop = results[i]
|
|
1174
1572
|
// TODO refactor this. __internal_only_flag needed to stop clash with sync/async file resolution
|
|
1175
|
-
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)) {
|
|
1176
1574
|
valueToPop = results[i].value
|
|
1177
1575
|
}
|
|
1178
1576
|
result = this.populateVariable(valueObject, matches[i].match, valueToPop)
|
|
@@ -1216,7 +1614,10 @@ class Configorama {
|
|
|
1216
1614
|
.then((result) => {
|
|
1217
1615
|
// console.log('renderMatches result', result)
|
|
1218
1616
|
if (root && isArray(matches)) {
|
|
1219
|
-
return this.populateValue({
|
|
1617
|
+
return this.populateValue({
|
|
1618
|
+
value: result.value,
|
|
1619
|
+
resolutionHistory: result.resolutionHistory || valueObject.resolutionHistory || []
|
|
1620
|
+
}, root, 'self populateValue')
|
|
1220
1621
|
}
|
|
1221
1622
|
return result
|
|
1222
1623
|
})
|
|
@@ -1254,11 +1655,11 @@ class Configorama {
|
|
|
1254
1655
|
|
|
1255
1656
|
const parts = splitByComma(variable, this.variableSyntax)
|
|
1256
1657
|
if (DEBUG) {
|
|
1257
|
-
console.log('parts', parts)
|
|
1258
|
-
console.log('parts variable:', variable)
|
|
1259
|
-
console.log('parts originalVar:', originalVar)
|
|
1260
|
-
console.log('parts
|
|
1261
|
-
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)
|
|
1262
1663
|
console.log('-----')
|
|
1263
1664
|
}
|
|
1264
1665
|
if (parts.length <= 1) {
|
|
@@ -1279,16 +1680,20 @@ class Configorama {
|
|
|
1279
1680
|
populateVariable(valueObject, matchedString, valueToPopulate) {
|
|
1280
1681
|
let property = valueObject.value
|
|
1281
1682
|
// console.log('init property', property)
|
|
1282
|
-
|
|
1683
|
+
|
|
1283
1684
|
if (DEBUG) {
|
|
1284
1685
|
console.log('────────START populateVar──────────────')
|
|
1285
|
-
console.log('
|
|
1286
|
-
console.log('
|
|
1287
|
-
console.log(
|
|
1288
|
-
console.log(`
|
|
1289
|
-
console.log(`
|
|
1290
|
-
console.log(
|
|
1291
|
-
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
|
+
}
|
|
1292
1697
|
}
|
|
1293
1698
|
|
|
1294
1699
|
const originalSrc = valueObject.originalSource || ''
|
|
@@ -1307,9 +1712,35 @@ class Configorama {
|
|
|
1307
1712
|
if (DEBUG_TYPE) console.log('DEBUG_TYPE total replacement')
|
|
1308
1713
|
const v = valueObject.value || ''
|
|
1309
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
|
+
}
|
|
1310
1740
|
|
|
1311
1741
|
/* Handle ${self:custom.ref, ''} with deep values */
|
|
1312
1742
|
if (v.match(deepRefSyntax) && originalSrc.match(this.variableSyntax) && !v.match(/deep\:(\d*)\..*}$/)) {
|
|
1743
|
+
// console.log('deep ref syntax')
|
|
1313
1744
|
// console.log('deep var', this.deep)
|
|
1314
1745
|
// console.log('originalSrc', originalSrc)
|
|
1315
1746
|
// console.log('value', v)
|
|
@@ -1412,7 +1843,12 @@ class Configorama {
|
|
|
1412
1843
|
missingValue = this.deep[i]
|
|
1413
1844
|
}
|
|
1414
1845
|
|
|
1415
|
-
const cleanVar = cleanVariable(
|
|
1846
|
+
const cleanVar = cleanVariable(
|
|
1847
|
+
property,
|
|
1848
|
+
this.variableSyntax,
|
|
1849
|
+
true,
|
|
1850
|
+
`populateVariable fallback ${this.callCount}`
|
|
1851
|
+
)
|
|
1416
1852
|
const cleanVarNoFilters = cleanVar.split('|')[0]
|
|
1417
1853
|
const splitVars = splitByComma(cleanVarNoFilters)
|
|
1418
1854
|
const nestedVar = findNestedVariable(splitVars, valueObject.originalSource)
|
|
@@ -1427,6 +1863,7 @@ class Configorama {
|
|
|
1427
1863
|
value: fallbackStr,
|
|
1428
1864
|
path: valueObject.path,
|
|
1429
1865
|
originalSource: valueObject.originalSource,
|
|
1866
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1430
1867
|
// set __internal_only_flag to note this is object we make not a resolved value
|
|
1431
1868
|
__internal_only_flag: true,
|
|
1432
1869
|
caller: 'nestedVar',
|
|
@@ -1478,6 +1915,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1478
1915
|
value: finalProp, // prop to fix nested ¯\_(ツ)_/¯
|
|
1479
1916
|
path: valueObject.path,
|
|
1480
1917
|
originalSource: valueObject.originalSource,
|
|
1918
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1481
1919
|
// set __internal_only_flag to note this is object we make not a resolved value
|
|
1482
1920
|
// __internal_only_flag: true
|
|
1483
1921
|
}
|
|
@@ -1496,13 +1934,15 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1496
1934
|
}
|
|
1497
1935
|
}
|
|
1498
1936
|
*/
|
|
1499
|
-
// Does not match file refs with nested vars + args
|
|
1500
|
-
// @TODO fix this for eval refs
|
|
1501
|
-
// console.log('prop', prop)
|
|
1502
|
-
// console.log('func', func)
|
|
1503
1937
|
|
|
1504
1938
|
if (
|
|
1939
|
+
/* Not another variable reference */
|
|
1940
|
+
!prop.match(this.variableSyntax)
|
|
1941
|
+
&&
|
|
1942
|
+
/* Not file or text refs */
|
|
1505
1943
|
!prop.match(fileRefSyntax)
|
|
1944
|
+
&& !prop.match(textRefSyntax)
|
|
1945
|
+
/* Not eval refs */
|
|
1506
1946
|
&& !prop.match(getValueFromEval.match)
|
|
1507
1947
|
// AND is not multiline value
|
|
1508
1948
|
&& (func && prop.split('\n').length < 3)) {
|
|
@@ -1523,14 +1963,21 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1523
1963
|
|
|
1524
1964
|
// console.log('foundFilters', foundFilters)
|
|
1525
1965
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
typeof valueToPopulate === 'string' &&
|
|
1531
|
-
!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 &&
|
|
1532
1973
|
!property.match(this.variableSyntax)
|
|
1533
1974
|
) {
|
|
1975
|
+
runFilters = true
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
/* Apply filters if found */
|
|
1979
|
+
//console.log('> property', property)
|
|
1980
|
+
if (runFilters) {
|
|
1534
1981
|
// If filter cache exists we need to remove filter that have already been run
|
|
1535
1982
|
if (this.filterCache[valueObject.path]) {
|
|
1536
1983
|
foundFilters = foundFilters.filter((filter) => {
|
|
@@ -1555,6 +2002,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1555
2002
|
value: property,
|
|
1556
2003
|
path: valueObject.path,
|
|
1557
2004
|
originalSource: valueObject.originalSource,
|
|
2005
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1558
2006
|
__internal_only_flag: true, // set __internal_only_flag to note this is object we make not a resolved value
|
|
1559
2007
|
caller: 'end',
|
|
1560
2008
|
count: this.callCount,
|
|
@@ -1596,15 +2044,22 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1596
2044
|
// console.log('propertyString', typeof propertyString)
|
|
1597
2045
|
const variableValues = variableStrings.map((variableString) => {
|
|
1598
2046
|
// This runs on nested variable resolution
|
|
1599
|
-
return this.getValueFromSource(variableString, valueObject, 'overwrite')
|
|
2047
|
+
return this.getValueFromSource(variableString, valueObject, 'overwrite', valueObject.originalSource)
|
|
1600
2048
|
})
|
|
1601
|
-
|
|
2049
|
+
|
|
1602
2050
|
// console.log('variableValues', variableValues)
|
|
1603
2051
|
return Promise.all(variableValues).then((values) => {
|
|
1604
2052
|
let deepPropertyStr = propertyString
|
|
1605
2053
|
let deepProperties = 0
|
|
1606
2054
|
// console.log('overwrite values', valuesToUse)
|
|
1607
|
-
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) => {
|
|
1608
2063
|
// console.log('───────────────────────────────> value', value)
|
|
1609
2064
|
if (isString(value) && value.match(this.variableSyntax)) {
|
|
1610
2065
|
deepProperties += 1
|
|
@@ -1620,7 +2075,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1620
2075
|
})
|
|
1621
2076
|
return deepProperties > 0
|
|
1622
2077
|
? Promise.resolve(deepPropertyStr) // return deep variable replacement of original
|
|
1623
|
-
: Promise.resolve(
|
|
2078
|
+
: Promise.resolve(extractedValues.find(isValidValue)) // resolve first valid value, else undefined
|
|
1624
2079
|
})
|
|
1625
2080
|
}
|
|
1626
2081
|
/**
|
|
@@ -1632,6 +2087,25 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1632
2087
|
// console.log('getValueFromSrc caller', caller)
|
|
1633
2088
|
const propertyString = valueObject.value
|
|
1634
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
|
+
|
|
1635
2109
|
// console.log('getValueFromSrc propertyString', propertyString)
|
|
1636
2110
|
// console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
|
|
1637
2111
|
if (this.tracker.contains(variableString)) {
|
|
@@ -1642,11 +2116,12 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1642
2116
|
let newHasFilter
|
|
1643
2117
|
// Else lookup value from various sources
|
|
1644
2118
|
if (DEBUG) {
|
|
1645
|
-
console.log(`>>>>> getValueFromSrc()
|
|
1646
|
-
console.log('
|
|
1647
|
-
console.log('
|
|
1648
|
-
console.log('
|
|
1649
|
-
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)
|
|
1650
2125
|
console.log('-----')
|
|
1651
2126
|
}
|
|
1652
2127
|
|
|
@@ -1674,6 +2149,8 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1674
2149
|
})
|
|
1675
2150
|
.map((f) => {
|
|
1676
2151
|
return trim(f)
|
|
2152
|
+
// TODO refactor this. This is a temp fix for filters with nested vars.
|
|
2153
|
+
.replace(/}$/, '')
|
|
1677
2154
|
})
|
|
1678
2155
|
// console.log('filters to run', _filter)
|
|
1679
2156
|
|
|
@@ -1730,6 +2207,21 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1730
2207
|
valueObject,
|
|
1731
2208
|
|
|
1732
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
|
+
|
|
1733
2225
|
// console.log('VALUE', val)
|
|
1734
2226
|
if (
|
|
1735
2227
|
val === null ||
|
|
@@ -1763,7 +2255,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1763
2255
|
// console.log('valueCount', valueCount)
|
|
1764
2256
|
// TODO throw on empty values?
|
|
1765
2257
|
// No fallback value found AND this is undefined, throw error
|
|
1766
|
-
const nestedVars = findNestedVariables(propertyString, this.variableSyntax)
|
|
2258
|
+
const nestedVars = findNestedVariables(propertyString, this.variableSyntax, this.variablesKnownTypes)
|
|
1767
2259
|
// console.log('nestedVars', nestedVars)
|
|
1768
2260
|
const noNestedVars = nestedVars.length < 2
|
|
1769
2261
|
if (valueCount.length === 1 && noNestedVars) {
|
|
@@ -1792,7 +2284,19 @@ Unable to resolve configuration variable
|
|
|
1792
2284
|
if (!newHasFilter) {
|
|
1793
2285
|
// console.log('no newHasFilter', val, valueObject)
|
|
1794
2286
|
// console.log('> RESOLVER RETURN newValue 3', val, originalVar)
|
|
1795
|
-
|
|
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
|
+
})
|
|
1796
2300
|
}
|
|
1797
2301
|
|
|
1798
2302
|
const newUse = newHasFilter.reduce((acc, currentFilter, i) => {
|
|
@@ -1805,6 +2309,8 @@ Unable to resolve configuration variable
|
|
|
1805
2309
|
// args: argsToPass
|
|
1806
2310
|
})
|
|
1807
2311
|
}, [])
|
|
2312
|
+
// console.log('pathValue', pathValue)
|
|
2313
|
+
// console.log('propertyString', propertyString)
|
|
1808
2314
|
// console.log('newUse', newUse)
|
|
1809
2315
|
|
|
1810
2316
|
if (typeof val === 'string' && val.match(/deep:/)) {
|
|
@@ -1841,7 +2347,19 @@ Unable to resolve configuration variable
|
|
|
1841
2347
|
}, val)
|
|
1842
2348
|
// console.log('> RESOLVER RETURN newValue', newValue)
|
|
1843
2349
|
// console.log('> RESOLVER RETURN newValue 5', newValue)
|
|
1844
|
-
|
|
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
|
+
})
|
|
1845
2363
|
})
|
|
1846
2364
|
|
|
1847
2365
|
// console.log('valuePromise', valuePromise)
|
|
@@ -1851,12 +2369,20 @@ Unable to resolve configuration variable
|
|
|
1851
2369
|
return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter, promiseKey)
|
|
1852
2370
|
}
|
|
1853
2371
|
|
|
2372
|
+
// console.log('fall thru variableString', variableString)
|
|
2373
|
+
|
|
1854
2374
|
/* fall through case with self refs */
|
|
1855
2375
|
if (variableString) {
|
|
1856
2376
|
// console.log('before clean propertyString', propertyString, variableString)
|
|
1857
|
-
const clean = cleanVariable(
|
|
2377
|
+
const clean = cleanVariable(
|
|
2378
|
+
propertyString,
|
|
2379
|
+
this.variableSyntax,
|
|
2380
|
+
true,
|
|
2381
|
+
`getValueFromSrc self ${this.callCount}`
|
|
2382
|
+
)
|
|
1858
2383
|
// TODO @DWELLS cleanVariable makes fallback values with spaces have no spaces
|
|
1859
2384
|
// console.log('AFTER cleanVariable', clean)
|
|
2385
|
+
// console.log(typeof clean)
|
|
1860
2386
|
const cleanClean = clean.split('|')[0]
|
|
1861
2387
|
// console.log('cleanCleanVariable', cleanClean)
|
|
1862
2388
|
if (funcRegex.exec(cleanClean)) {
|
|
@@ -1865,7 +2391,8 @@ Unable to resolve configuration variable
|
|
|
1865
2391
|
}
|
|
1866
2392
|
|
|
1867
2393
|
const split = splitByComma(cleanClean)
|
|
1868
|
-
|
|
2394
|
+
// console.log('split', split)
|
|
2395
|
+
// console.log('typeof split', typeof split)
|
|
1869
2396
|
// @TODO refactor this. USE FILTER [ 'commas', 'split("-"' ] is wrong
|
|
1870
2397
|
let fallbackValue
|
|
1871
2398
|
if (split.length === 2 || split.length === 3) {
|
|
@@ -1874,7 +2401,9 @@ Unable to resolve configuration variable
|
|
|
1874
2401
|
fallbackValue = split[0]
|
|
1875
2402
|
}
|
|
1876
2403
|
|
|
2404
|
+
// TODO this should be new in memory resolutionHistory probably?
|
|
1877
2405
|
const nestedVar = findNestedVariable(split, valueObject.originalSource)
|
|
2406
|
+
// console.log('nestedVar', nestedVar)
|
|
1878
2407
|
|
|
1879
2408
|
if (nestedVar) {
|
|
1880
2409
|
if (!this.opts.allowUnknownVars) {
|
|
@@ -1883,7 +2412,7 @@ Unable to resolve configuration variable
|
|
|
1883
2412
|
const fallbackStr = getFallbackString(split, nestedVar)
|
|
1884
2413
|
return this.getValueFromSource(variableString, {
|
|
1885
2414
|
value: fallbackStr,
|
|
1886
|
-
}, 'nestedVar')
|
|
2415
|
+
}, 'nestedVar', originalVar)
|
|
1887
2416
|
}
|
|
1888
2417
|
|
|
1889
2418
|
// TODO verify we need this still with file(file.js, param)
|
|
@@ -1895,7 +2424,7 @@ Unable to resolve configuration variable
|
|
|
1895
2424
|
// recurse on fallback and check again
|
|
1896
2425
|
return this.getValueFromSource(`${variableString})`, {
|
|
1897
2426
|
value: propertyString,
|
|
1898
|
-
}, 'cleanClean.match(fileRefSyntax)')
|
|
2427
|
+
}, 'cleanClean.match(fileRefSyntax)', originalVar)
|
|
1899
2428
|
}
|
|
1900
2429
|
}
|
|
1901
2430
|
// const fallbackValue = split[1]
|
|
@@ -1907,7 +2436,10 @@ Unable to resolve configuration variable
|
|
|
1907
2436
|
const valuePromise = Promise.resolve(fallbackValue)
|
|
1908
2437
|
return this.tracker.add(fallbackValue, valuePromise, propertyString, newHasFilter)
|
|
1909
2438
|
}
|
|
1910
|
-
|
|
2439
|
+
/*
|
|
2440
|
+
console.log('what is fallbackValue', fallbackValue)
|
|
2441
|
+
console.log('typeof fallbackValue', typeof fallbackValue)
|
|
2442
|
+
/** */
|
|
1911
2443
|
// has fallback but needs deeper lookup. Call getValueFromSrc again
|
|
1912
2444
|
if (fallbackValue) {
|
|
1913
2445
|
if (DEBUG) console.log('fallbackValue', fallbackValue)
|
|
@@ -1915,11 +2447,21 @@ Unable to resolve configuration variable
|
|
|
1915
2447
|
// recurse on fallback and check again
|
|
1916
2448
|
return this.getValueFromSource(
|
|
1917
2449
|
fallbackValue,
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2450
|
+
valueObject,
|
|
2451
|
+
// Object.assign({}, valueObject, { value: propertyString }),
|
|
2452
|
+
// {
|
|
2453
|
+
// value: propertyString,
|
|
2454
|
+
// path: valueObject.path,
|
|
2455
|
+
// originalSource: valueObject.originalSource,
|
|
2456
|
+
// ahh:true
|
|
2457
|
+
// },
|
|
1921
2458
|
'fallbackValue',
|
|
1922
|
-
|
|
2459
|
+
originalVar,
|
|
2460
|
+
).then((res) => {
|
|
2461
|
+
// console.log('res', res)
|
|
2462
|
+
// console.log('typeof res', typeof res)
|
|
2463
|
+
return res
|
|
2464
|
+
})
|
|
1923
2465
|
}
|
|
1924
2466
|
}
|
|
1925
2467
|
|
|
@@ -1932,7 +2474,9 @@ Unable to resolve configuration variable
|
|
|
1932
2474
|
]
|
|
1933
2475
|
|
|
1934
2476
|
// Default value used for self variable
|
|
1935
|
-
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(/,/)) {
|
|
1936
2480
|
errorMessage.push('\n Default values for self referenced values are not allowed')
|
|
1937
2481
|
errorMessage.push(`\n Fix the ${propertyString} variable`)
|
|
1938
2482
|
}
|
|
@@ -1940,23 +2484,30 @@ Unable to resolve configuration variable
|
|
|
1940
2484
|
let allowSpecialCase = false
|
|
1941
2485
|
/* handle special cases for cloudformation ${Sub} values */
|
|
1942
2486
|
if (this.originalConfig && key.endsWith('Fn::Sub')) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
|
1955
2504
|
allowSpecialCase = true
|
|
1956
2505
|
}
|
|
1957
2506
|
}
|
|
1958
2507
|
/* Todo handle stage variables */
|
|
1959
2508
|
|
|
2509
|
+
|
|
2510
|
+
|
|
1960
2511
|
/* Pass through unknown variables */
|
|
1961
2512
|
if (this.opts.allowUnknownVars || allowSpecialCase) {
|
|
1962
2513
|
// console.log('allowUnknownVars propertyString', propertyString)
|
|
@@ -2061,7 +2612,10 @@ Unable to resolve configuration variable
|
|
|
2061
2612
|
} else if (resolvedPath.match(/\.\//)) {
|
|
2062
2613
|
// TODO test higher parent refs
|
|
2063
2614
|
const cleanName = path.basename(resolvedPath)
|
|
2064
|
-
|
|
2615
|
+
const findUpResult = findUp.sync(cleanName, { cwd: this.configPath })
|
|
2616
|
+
if (findUpResult) {
|
|
2617
|
+
fullFilePath = findUpResult
|
|
2618
|
+
}
|
|
2065
2619
|
}
|
|
2066
2620
|
|
|
2067
2621
|
let fileExtension = resolvedPath.split('.')
|
|
@@ -2070,18 +2624,39 @@ Unable to resolve configuration variable
|
|
|
2070
2624
|
|
|
2071
2625
|
// Validate file exists
|
|
2072
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
|
|
2073
2644
|
// console.log('NO FILE FOUND', fullFilePath)
|
|
2074
2645
|
// console.log('variableString', variableString)
|
|
2075
|
-
|
|
2076
|
-
|
|
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.
|
|
2077
2651
|
|
|
2078
2652
|
File not found ${fullFilePath}
|
|
2079
2653
|
|
|
2080
2654
|
Default fallback value will be used if provided.
|
|
2081
|
-
${logLines}
|
|
2082
|
-
`
|
|
2083
2655
|
|
|
2084
|
-
|
|
2656
|
+
${JSON.stringify(options.context, null, 2)}`,
|
|
2657
|
+
})
|
|
2658
|
+
console.log(errorMsg)
|
|
2659
|
+
}
|
|
2085
2660
|
// TODO maybe reject. YAML does not allow for null/undefined values
|
|
2086
2661
|
// return Promise.reject(new Error(errorMsg))
|
|
2087
2662
|
return Promise.resolve(undefined)
|
|
@@ -2099,6 +2674,7 @@ ${logLines}
|
|
|
2099
2674
|
|
|
2100
2675
|
// Process JS files
|
|
2101
2676
|
if (fileExtension === 'js' || fileExtension === 'cjs') {
|
|
2677
|
+
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
2102
2678
|
const jsFile = require(fullFilePath)
|
|
2103
2679
|
let returnValueFunction = jsFile
|
|
2104
2680
|
// TODO change how exported functions are referenced
|
|
@@ -2198,6 +2774,7 @@ Check if your TypeScript is returning the correct data.`
|
|
|
2198
2774
|
}
|
|
2199
2775
|
|
|
2200
2776
|
if (fileExtension === 'mjs' || fileExtension === 'esm') {
|
|
2777
|
+
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
2201
2778
|
const { executeESMFile } = require('./parsers/esm')
|
|
2202
2779
|
let returnValueFunction
|
|
2203
2780
|
const variableArray = variableString.split(':')
|
|
@@ -2269,9 +2846,10 @@ Check if your ESM is returning the correct data.`
|
|
|
2269
2846
|
// console.log('deep', variableString)
|
|
2270
2847
|
// console.log('matchedFileString', matchedFileString)
|
|
2271
2848
|
let deepProperties = variableString.replace(matchedFileString, '')
|
|
2849
|
+
// TODO 2025-11-12 add file.path.support instead of just :
|
|
2272
2850
|
if (deepProperties.substring(0, 1) !== ':') {
|
|
2273
2851
|
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}" sub properties
|
|
2274
|
-
Please use ":" to reference sub properties`
|
|
2852
|
+
Please use ":" to reference sub properties. ${deepProperties}`
|
|
2275
2853
|
return Promise.reject(new Error(errorMessage))
|
|
2276
2854
|
}
|
|
2277
2855
|
deepProperties = deepProperties.slice(1).split('.')
|
|
@@ -2298,7 +2876,7 @@ Please use ":" to reference sub properties`
|
|
|
2298
2876
|
return Promise.resolve(valueToPopulate)
|
|
2299
2877
|
}
|
|
2300
2878
|
}
|
|
2301
|
-
console.log('fall thru', valueToPopulate)
|
|
2879
|
+
// console.log('fall thru', valueToPopulate)
|
|
2302
2880
|
return Promise.resolve(valueToPopulate)
|
|
2303
2881
|
}
|
|
2304
2882
|
getVariableFromDeep(variableString) {
|
|
@@ -2310,15 +2888,22 @@ Please use ":" to reference sub properties`
|
|
|
2310
2888
|
/** */
|
|
2311
2889
|
return this.deep[index]
|
|
2312
2890
|
}
|
|
2313
|
-
getValueFromDeep(variableString) {
|
|
2891
|
+
getValueFromDeep(variableString, pathValue) {
|
|
2314
2892
|
const variable = this.getVariableFromDeep(variableString)
|
|
2315
2893
|
const deepRef = variableString.replace(deepPrefixReplacePattern, '')
|
|
2316
2894
|
/*
|
|
2317
2895
|
console.log("GET getValueFromDeep", variableString)
|
|
2318
|
-
console.log('deepRef', deepRef)
|
|
2896
|
+
console.log('deepRef', (deepRef) ? deepRef : '- no deepRef')
|
|
2319
2897
|
console.log('getValueFromDeep variable', variable)
|
|
2320
2898
|
/** */
|
|
2321
|
-
|
|
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')
|
|
2322
2907
|
if (deepRef.length) {
|
|
2323
2908
|
// if there is a deep reference remaining
|
|
2324
2909
|
ret = ret.then((result) => {
|
|
@@ -2342,7 +2927,12 @@ Please use ":" to reference sub properties`
|
|
|
2342
2927
|
}
|
|
2343
2928
|
// console.log("makeDeepVariable SET INDEX", index)
|
|
2344
2929
|
const variableContainer = variable.match(this.variableSyntax)[0]
|
|
2345
|
-
const variableString = cleanVariable(
|
|
2930
|
+
const variableString = cleanVariable(
|
|
2931
|
+
variableContainer,
|
|
2932
|
+
this.variableSyntax,
|
|
2933
|
+
true,
|
|
2934
|
+
`makeDeepVariable ${this.callCount}`
|
|
2935
|
+
)
|
|
2346
2936
|
const deepVar = variableContainer.replace(variableString, `deep:${index}`)
|
|
2347
2937
|
/*
|
|
2348
2938
|
console.log('MAKE DEEP', variable, caller)
|