configorama 0.6.7 → 0.6.9
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 +785 -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,17 @@ 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
|
-
|
|
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
|
-
})
|
|
634
|
+
// Use collectVariableMetadata to get variable info (DRY - don't duplicate logic)
|
|
635
|
+
const metadata = this.collectVariableMetadata()
|
|
636
|
+
/*
|
|
637
|
+
deepLog('metadata', metadata)
|
|
638
|
+
process.exit(1)
|
|
639
|
+
/** */
|
|
640
|
+
|
|
641
|
+
const variableData = metadata.variables
|
|
642
|
+
const varKeys = Object.keys(variableData)
|
|
590
643
|
|
|
591
|
-
if (!
|
|
644
|
+
if (!varKeys.length) {
|
|
592
645
|
logHeader('No Variables Found in Config')
|
|
593
646
|
if (this.configFilePath) {
|
|
594
647
|
console.log(`File: ${this.configFilePath}`)
|
|
@@ -611,10 +664,7 @@ class Configorama {
|
|
|
611
664
|
console.log()
|
|
612
665
|
}
|
|
613
666
|
|
|
614
|
-
|
|
615
|
-
const finalFoundVariables = [...new Set(foundVariables)]
|
|
616
|
-
if (finalFoundVariables.length > 0) {
|
|
617
|
-
const varKeys = Object.keys(variableData)
|
|
667
|
+
if (varKeys.length > 0) {
|
|
618
668
|
const fileName = this.configFilePath ? ` in ${this.configFilePath}` : ''
|
|
619
669
|
|
|
620
670
|
logHeader(`Found ${varKeys.length} Variables${fileName}`)
|
|
@@ -626,9 +676,34 @@ class Configorama {
|
|
|
626
676
|
const longestKey = varKeys.reduce((acc, k) => {
|
|
627
677
|
return Math.max(acc, k.length)
|
|
628
678
|
}, 0)
|
|
679
|
+
// Count all references including nested ones within other variables
|
|
680
|
+
const countAllReferences = (targetVariable) => {
|
|
681
|
+
// Start with direct references
|
|
682
|
+
let count = variableData[targetVariable].length
|
|
683
|
+
|
|
684
|
+
// Check all other variables for nested references to this variable
|
|
685
|
+
varKeys.forEach((otherKey) => {
|
|
686
|
+
if (otherKey === targetVariable) return
|
|
687
|
+
|
|
688
|
+
variableData[otherKey].forEach((instance) => {
|
|
689
|
+
if (instance.resolveDetails) {
|
|
690
|
+
instance.resolveDetails.forEach((detail) => {
|
|
691
|
+
// Check if this resolveDetail references our target variable
|
|
692
|
+
if (detail.fullMatch === targetVariable) {
|
|
693
|
+
count++
|
|
694
|
+
}
|
|
695
|
+
})
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
return count
|
|
701
|
+
}
|
|
702
|
+
|
|
629
703
|
console.log(varKeys.map((k) => {
|
|
630
|
-
const
|
|
631
|
-
|
|
704
|
+
const refCount = countAllReferences(k)
|
|
705
|
+
const placesWord = refCount > 1 ? 'places' : 'place'
|
|
706
|
+
return `- ${k.padEnd(longestKey).padEnd(longestKey + 10)} referenced ${refCount} ${placesWord}`
|
|
632
707
|
}).join('\n'))
|
|
633
708
|
console.log()
|
|
634
709
|
}
|
|
@@ -697,7 +772,9 @@ class Configorama {
|
|
|
697
772
|
firstInstance.defaultValue = truncatedString
|
|
698
773
|
} else {
|
|
699
774
|
deepLog('Missing default var', firstInstance)
|
|
700
|
-
throw new Error(
|
|
775
|
+
throw new Error(
|
|
776
|
+
`Variable misconfiguration at ${firstInstance.variable}\n\n"${hasDotPropOrSelf[0].variable}" resolves to undefined value.\n`
|
|
777
|
+
)
|
|
701
778
|
}
|
|
702
779
|
}
|
|
703
780
|
//this.originalConfig[key] = undefined
|
|
@@ -802,6 +879,7 @@ class Configorama {
|
|
|
802
879
|
return this.populateObjectImpl(this.config).finally(() => {
|
|
803
880
|
// TODO populate function values here?
|
|
804
881
|
// console.log('Final Config', this.config)
|
|
882
|
+
// console.log(this.deep)
|
|
805
883
|
const transform = this.runFunction.bind(this)
|
|
806
884
|
const varSyntax = this.variableSyntax
|
|
807
885
|
const leaves = this.leaves
|
|
@@ -812,7 +890,9 @@ class Configorama {
|
|
|
812
890
|
/* Pass through unknown variables */
|
|
813
891
|
if (!configoramaOpts.allowUndefinedValues && typeof rawValue === 'undefined') {
|
|
814
892
|
const configValuePath = this.path.join('.')
|
|
893
|
+
/*
|
|
815
894
|
console.log(this.path)
|
|
895
|
+
/** */
|
|
816
896
|
const ogValue = dotProp.get(originalConfig, configValuePath)
|
|
817
897
|
const varDisplay = ogValue ? `"${ogValue}" variable` : 'variable'
|
|
818
898
|
|
|
@@ -889,6 +969,214 @@ class Configorama {
|
|
|
889
969
|
})
|
|
890
970
|
})
|
|
891
971
|
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Collect metadata about all variables found in the configuration
|
|
975
|
+
* @returns {object} Metadata object containing variables, fileRefs, and summary
|
|
976
|
+
*/
|
|
977
|
+
collectVariableMetadata() {
|
|
978
|
+
const variableSyntax = this.variableSyntax
|
|
979
|
+
const variablesKnownTypes = this.variablesKnownTypes
|
|
980
|
+
const foundVariables = []
|
|
981
|
+
const variableData = {}
|
|
982
|
+
const fileRefs = []
|
|
983
|
+
const fileGlobPatterns = []
|
|
984
|
+
let matchCount = 1
|
|
985
|
+
|
|
986
|
+
traverse(this.originalConfig).forEach(function (rawValue) {
|
|
987
|
+
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
988
|
+
const configValuePath = this.path.join('.')
|
|
989
|
+
/* Skip Fn::Sub variables */
|
|
990
|
+
if (configValuePath.endsWith('Fn::Sub')) {
|
|
991
|
+
return
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const nested = findNestedVariables(rawValue, variableSyntax, variablesKnownTypes, configValuePath)
|
|
995
|
+
|
|
996
|
+
const lastItem = nested[nested.length - 1]
|
|
997
|
+
const lastKeyPath = this.path[this.path.length - 1]
|
|
998
|
+
const itemKey = (lastKeyPath.match(/[\d+]$/)) ? `${this.path[this.path.length - 2]}[${lastKeyPath}]` : lastKeyPath
|
|
999
|
+
const key = lastItem.fullMatch
|
|
1000
|
+
const varData = {
|
|
1001
|
+
path: configValuePath,
|
|
1002
|
+
key: itemKey,
|
|
1003
|
+
value: rawValue,
|
|
1004
|
+
variable: lastItem.fullMatch,
|
|
1005
|
+
isRequired: false,
|
|
1006
|
+
defaultValue: undefined,
|
|
1007
|
+
matchIndex: matchCount++,
|
|
1008
|
+
resolveOrder: [],
|
|
1009
|
+
resolveDetails: nested,
|
|
1010
|
+
}
|
|
1011
|
+
let defaultValueIsVar = false
|
|
1012
|
+
|
|
1013
|
+
function calculateResolveOrder(item) {
|
|
1014
|
+
if (item && item.fallbackValues) {
|
|
1015
|
+
let hasResolvedFallback
|
|
1016
|
+
const order = ([item.valueBeforeFallback]).concat(item.fallbackValues.map((f, i) => {
|
|
1017
|
+
if (f.fallbackValues) {
|
|
1018
|
+
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
1019
|
+
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
1020
|
+
hasResolvedFallback = nestedResolvedFallback
|
|
1021
|
+
}
|
|
1022
|
+
return nestedOrder
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
1026
|
+
hasResolvedFallback = f.stringValue
|
|
1027
|
+
}
|
|
1028
|
+
if (f.isResolvedFallback) {
|
|
1029
|
+
hasResolvedFallback = f.stringValue
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (!hasResolvedFallback && f.isVariable) {
|
|
1033
|
+
defaultValueIsVar = f
|
|
1034
|
+
}
|
|
1035
|
+
return `${f.stringValue || f.variable}${f.isResolvedFallback ? ' (Resolved default fallback)' : ''}`
|
|
1036
|
+
})).flat()
|
|
1037
|
+
|
|
1038
|
+
return [order, hasResolvedFallback]
|
|
1039
|
+
}
|
|
1040
|
+
return [[item.variable], undefined]
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const [resolveOrder, hasResolvedFallback] = calculateResolveOrder(lastItem)
|
|
1044
|
+
varData.resolveOrder = resolveOrder
|
|
1045
|
+
|
|
1046
|
+
if (defaultValueIsVar) {
|
|
1047
|
+
varData.defaultValueIsVar = defaultValueIsVar
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (typeof hasResolvedFallback !== 'undefined') {
|
|
1051
|
+
varData.defaultValue = hasResolvedFallback
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (typeof varData.defaultValue === 'undefined') {
|
|
1055
|
+
varData.isRequired = true
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (varData.resolveOrder.length > 1) {
|
|
1059
|
+
varData.hasFallback = true
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Extract file references
|
|
1063
|
+
nested.forEach((detail) => {
|
|
1064
|
+
if (detail.varType &&
|
|
1065
|
+
(detail.varType.startsWith('file(') || detail.varType.startsWith('text('))
|
|
1066
|
+
) {
|
|
1067
|
+
const fileMatch = detail.varType.match(/^(?:file|text)\((.*?)\)/)
|
|
1068
|
+
if (fileMatch && fileMatch[1]) {
|
|
1069
|
+
let fileContent = fileMatch[1].trim()
|
|
1070
|
+
|
|
1071
|
+
// Split by comma to separate file path from parameters/fallback values
|
|
1072
|
+
const parts = splitCsv(fileContent)
|
|
1073
|
+
let filePath = parts[0].trim()
|
|
1074
|
+
|
|
1075
|
+
// Remove quotes if present
|
|
1076
|
+
filePath = filePath.replace(/^['"]|['"]$/g, '')
|
|
1077
|
+
|
|
1078
|
+
// Normalize path: ensure relative paths start with ./
|
|
1079
|
+
let normalizedPath = filePath
|
|
1080
|
+
if (
|
|
1081
|
+
!filePath.startsWith('./') &&
|
|
1082
|
+
!filePath.startsWith('../') &&
|
|
1083
|
+
!filePath.startsWith('/') &&
|
|
1084
|
+
!filePath.startsWith('~')
|
|
1085
|
+
) {
|
|
1086
|
+
normalizedPath = './' + filePath
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// file .//
|
|
1090
|
+
if (normalizedPath.startsWith('.//')) {
|
|
1091
|
+
normalizedPath = normalizedPath.replace('.//', './')
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Handle variables in file paths - just record the pattern
|
|
1095
|
+
if (!fileRefs.includes(normalizedPath)) {
|
|
1096
|
+
fileRefs.push(normalizedPath)
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Check if path contains variables and create glob pattern
|
|
1100
|
+
if (normalizedPath.match(variableSyntax)) {
|
|
1101
|
+
// Replace variable syntax ${...} with * for glob pattern
|
|
1102
|
+
const globPattern = normalizedPath.replace(variableSyntax, '*')
|
|
1103
|
+
if (!fileGlobPatterns.includes(globPattern)) {
|
|
1104
|
+
fileGlobPatterns.push(globPattern)
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
variableData[key] = (variableData[key] || []).concat(varData)
|
|
1112
|
+
foundVariables.push(rawValue)
|
|
1113
|
+
}
|
|
1114
|
+
})
|
|
1115
|
+
|
|
1116
|
+
// Make foundVariables array unique
|
|
1117
|
+
const finalFoundVariables = [...new Set(foundVariables)]
|
|
1118
|
+
const varKeys = Object.keys(variableData)
|
|
1119
|
+
|
|
1120
|
+
// Calculate summary using same logic as CLI display
|
|
1121
|
+
let requiredCount = 0
|
|
1122
|
+
let withDefaultsCount = 0
|
|
1123
|
+
varKeys.forEach((key) => {
|
|
1124
|
+
const instances = variableData[key]
|
|
1125
|
+
const firstInstance = instances[0]
|
|
1126
|
+
|
|
1127
|
+
// Check if truly required using same logic as display code
|
|
1128
|
+
let isTrulyRequired = false
|
|
1129
|
+
if (typeof firstInstance.defaultValue === 'undefined') {
|
|
1130
|
+
// Check for self-references that resolve to config values
|
|
1131
|
+
let dotPropArr = []
|
|
1132
|
+
if (firstInstance.defaultValueIsVar && (
|
|
1133
|
+
firstInstance.defaultValueIsVar.varType === 'self:' ||
|
|
1134
|
+
firstInstance.defaultValueIsVar.varType === 'dot.prop'
|
|
1135
|
+
)) {
|
|
1136
|
+
dotPropArr = [firstInstance.defaultValueIsVar]
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const hasDotPropOrSelf = instances.reduce((acc, v) => {
|
|
1140
|
+
const dotProp = v.resolveDetails.find((d) => d.varType === 'dot.prop')
|
|
1141
|
+
if (dotProp) {
|
|
1142
|
+
acc.push(dotProp)
|
|
1143
|
+
}
|
|
1144
|
+
if (v.resolveDetails && v.resolveDetails.length === 1 && v.resolveDetails[0].varType === 'self:') {
|
|
1145
|
+
acc.push(v.resolveDetails[0])
|
|
1146
|
+
}
|
|
1147
|
+
return acc
|
|
1148
|
+
}, dotPropArr)
|
|
1149
|
+
|
|
1150
|
+
if (!hasDotPropOrSelf.length) {
|
|
1151
|
+
isTrulyRequired = true
|
|
1152
|
+
} else {
|
|
1153
|
+
// Check if the self-reference resolves to a value
|
|
1154
|
+
const cleanPath = hasDotPropOrSelf[0].variable.replace('self:', '')
|
|
1155
|
+
const dotPropValue = dotProp.get(this.originalConfig, cleanPath)
|
|
1156
|
+
if (typeof dotPropValue === 'undefined') {
|
|
1157
|
+
isTrulyRequired = true
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (isTrulyRequired) {
|
|
1163
|
+
requiredCount++
|
|
1164
|
+
} else {
|
|
1165
|
+
withDefaultsCount++
|
|
1166
|
+
}
|
|
1167
|
+
})
|
|
1168
|
+
|
|
1169
|
+
return {
|
|
1170
|
+
variables: variableData,
|
|
1171
|
+
summary: {
|
|
1172
|
+
totalVariables: varKeys.length,
|
|
1173
|
+
requiredVariables: requiredCount,
|
|
1174
|
+
variablesWithDefaults: withDefaultsCount
|
|
1175
|
+
},
|
|
1176
|
+
fileRefs: fileRefs,
|
|
1177
|
+
fileGlobPatterns: fileGlobPatterns,
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
892
1180
|
runFunction(variableString) {
|
|
893
1181
|
// console.log('runFunction', variableString)
|
|
894
1182
|
/* If json object value return it */
|
|
@@ -918,7 +1206,6 @@ class Configorama {
|
|
|
918
1206
|
argsToPass = formatFunctionArgs(splitter)
|
|
919
1207
|
}
|
|
920
1208
|
// console.log('argsToPass runFunction', argsToPass)
|
|
921
|
-
|
|
922
1209
|
// TODO check for camelCase version. | toUpperCase messes with function name
|
|
923
1210
|
const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
|
|
924
1211
|
|
|
@@ -1016,6 +1303,15 @@ class Configorama {
|
|
|
1016
1303
|
}
|
|
1017
1304
|
}
|
|
1018
1305
|
leaf.originalSource = originalValue
|
|
1306
|
+
|
|
1307
|
+
// Check if we have existing resolution history from previous iterations
|
|
1308
|
+
const pathKey = thePath
|
|
1309
|
+
if (this.resolutionTracking[pathKey] && this.resolutionTracking[pathKey].resolutionHistory) {
|
|
1310
|
+
leaf.resolutionHistory = this.resolutionTracking[pathKey].resolutionHistory
|
|
1311
|
+
} else {
|
|
1312
|
+
leaf.resolutionHistory = []
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1019
1315
|
if (originalValue && isString(originalValue)) {
|
|
1020
1316
|
const varString = cleanVariable(originalValue, this.variableSyntax, true, `getProperties ${this.callCount}`)
|
|
1021
1317
|
if (varString.match(fileRefSyntax)) {
|
|
@@ -1027,7 +1323,6 @@ class Configorama {
|
|
|
1027
1323
|
}
|
|
1028
1324
|
return results
|
|
1029
1325
|
}
|
|
1030
|
-
|
|
1031
1326
|
/**
|
|
1032
1327
|
* @typedef {TerminalProperty} TerminalPropertyPopulated
|
|
1033
1328
|
* @property {Object} populated The populated value of the value at the path
|
|
@@ -1044,11 +1339,9 @@ class Configorama {
|
|
|
1044
1339
|
// Initial check if value has variable string in it
|
|
1045
1340
|
return isString(property.value) && property.value.match(this.variableSyntax)
|
|
1046
1341
|
})
|
|
1047
|
-
|
|
1048
1342
|
/*
|
|
1049
1343
|
console.log(`variables at call count ${this.callCount}`, variables)
|
|
1050
1344
|
/** */
|
|
1051
|
-
|
|
1052
1345
|
/* Exclude git messages from being processed */
|
|
1053
1346
|
// Was failing on git msgs like "xyz cron:pattern to cron(pattern) for improved clarity"
|
|
1054
1347
|
if (this.callCount > 1) {
|
|
@@ -1060,7 +1353,6 @@ class Configorama {
|
|
|
1060
1353
|
return true
|
|
1061
1354
|
})
|
|
1062
1355
|
}
|
|
1063
|
-
|
|
1064
1356
|
return map(variables, (valueObject) => {
|
|
1065
1357
|
// console.log('valueObject', valueObject)
|
|
1066
1358
|
return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
|
|
@@ -1163,16 +1455,127 @@ class Configorama {
|
|
|
1163
1455
|
*/
|
|
1164
1456
|
renderMatches(valueObject, matches, results) {
|
|
1165
1457
|
/*
|
|
1458
|
+
console.log('valueObject', valueObject)
|
|
1166
1459
|
console.log('RENDER', matches)
|
|
1167
1460
|
console.log('RESULTS', results)
|
|
1168
1461
|
/** */
|
|
1462
|
+
|
|
1463
|
+
/* Attach data to valueObject for parent details */
|
|
1464
|
+
if (matches.length === 1) {
|
|
1465
|
+
valueObject.currentVarDetails = matches[0]
|
|
1466
|
+
valueObject.currentVarDetails.result = results[0]
|
|
1467
|
+
|
|
1468
|
+
// Extract metadata from result if present
|
|
1469
|
+
let actualResult = results[0]
|
|
1470
|
+
let resolverType = undefined
|
|
1471
|
+
if (results[0] && typeof results[0] === 'object') {
|
|
1472
|
+
if (results[0].__internal_metadata) {
|
|
1473
|
+
actualResult = results[0].value
|
|
1474
|
+
resolverType = results[0].__resolverType
|
|
1475
|
+
} else if (results[0].__internal_only_flag) {
|
|
1476
|
+
// Don't extract value from __internal_only_flag objects, but grab resolverType if present
|
|
1477
|
+
actualResult = results[0]
|
|
1478
|
+
resolverType = results[0].__resolverType
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
// valueObject.currentVarDetails.varType = results[0].__resolverType
|
|
1482
|
+
|
|
1483
|
+
// Track resolution history
|
|
1484
|
+
if (!valueObject.resolutionHistory) {
|
|
1485
|
+
valueObject.resolutionHistory = []
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// Extract clean result to avoid circular references
|
|
1489
|
+
// For __internal_only_flag objects (like deep resolver results), extract the value
|
|
1490
|
+
// For real data objects (like file contents), keep them as-is
|
|
1491
|
+
let cleanResult = actualResult
|
|
1492
|
+
if (actualResult && typeof actualResult === 'object' && actualResult.__internal_only_flag) {
|
|
1493
|
+
cleanResult = actualResult.value
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
const historyEntry = {
|
|
1497
|
+
match: matches[0].match,
|
|
1498
|
+
variable: matches[0].variable,
|
|
1499
|
+
result: cleanResult,
|
|
1500
|
+
resultType: typeof cleanResult,
|
|
1501
|
+
valueBeforeResolution: valueObject.value,
|
|
1502
|
+
}
|
|
1503
|
+
if (resolverType) {
|
|
1504
|
+
historyEntry.varType = resolverType
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// Check if variable has fallback values (comma-separated)
|
|
1508
|
+
const variableParts = splitByComma(matches[0].variable)
|
|
1509
|
+
if (variableParts.length > 1) {
|
|
1510
|
+
historyEntry.hasFallback = true
|
|
1511
|
+
historyEntry.valueBeforeFallback = variableParts[0]
|
|
1512
|
+
historyEntry.fallbackValues = variableParts.slice(1).map((fallback) => {
|
|
1513
|
+
const trimmedFallback = fallback.trim()
|
|
1514
|
+
// Check if it's a variable reference
|
|
1515
|
+
const isVariable = trimmedFallback.match(this.variableSyntax) || trimmedFallback.match(this.variablesKnownTypes)
|
|
1516
|
+
const fallbackData = {
|
|
1517
|
+
isVariable: !!isVariable,
|
|
1518
|
+
fullMatch: trimmedFallback,
|
|
1519
|
+
variable: trimmedFallback,
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// If it's a literal string/number, parse it
|
|
1523
|
+
if (!isVariable) {
|
|
1524
|
+
// Check if it's a quoted string
|
|
1525
|
+
if (/^["'].*["']$/.test(trimmedFallback)) {
|
|
1526
|
+
fallbackData.stringValue = trimmedFallback.replace(/^["']|["']$/g, '')
|
|
1527
|
+
fallbackData.isResolvedFallback = true
|
|
1528
|
+
} else if (/^-?\d+(\.\d+)?$/.test(trimmedFallback)) {
|
|
1529
|
+
// It's a number
|
|
1530
|
+
fallbackData.numberValue = parseFloat(trimmedFallback)
|
|
1531
|
+
fallbackData.isResolvedFallback = true
|
|
1532
|
+
} else {
|
|
1533
|
+
fallbackData.stringValue = trimmedFallback
|
|
1534
|
+
fallbackData.isResolvedFallback = true
|
|
1535
|
+
}
|
|
1536
|
+
} else {
|
|
1537
|
+
// Extract varType from variable references
|
|
1538
|
+
const varTypeMatch = trimmedFallback.match(this.variablesKnownTypes)
|
|
1539
|
+
if (varTypeMatch && varTypeMatch[1]) {
|
|
1540
|
+
fallbackData.varType = varTypeMatch[1]
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
return fallbackData
|
|
1545
|
+
})
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Only add to history if not a duplicate (same match + variable)
|
|
1549
|
+
const isDuplicate = valueObject.resolutionHistory.some(entry =>
|
|
1550
|
+
entry.match === historyEntry.match &&
|
|
1551
|
+
entry.variable === historyEntry.variable
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
if (!isDuplicate) {
|
|
1555
|
+
valueObject.resolutionHistory.push(historyEntry)
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Save resolution history to tracking map for persistence across iterations
|
|
1559
|
+
if (valueObject.path && valueObject.path.length) {
|
|
1560
|
+
const pathKey = valueObject.path.join('.')
|
|
1561
|
+
if (!this.resolutionTracking[pathKey]) {
|
|
1562
|
+
this.resolutionTracking[pathKey] = {
|
|
1563
|
+
path: pathKey,
|
|
1564
|
+
originalPropertyString: valueObject.originalSource,
|
|
1565
|
+
calls: []
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
this.resolutionTracking[pathKey].resolutionHistory = valueObject.resolutionHistory
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1169
1572
|
let result = valueObject.value
|
|
1170
1573
|
for (let i = 0; i < matches.length; i += 1) {
|
|
1171
1574
|
this.warnIfNotFound(matches[i].variable, results[i])
|
|
1172
1575
|
// console.log('Render MATCHES', results[i])
|
|
1173
1576
|
let valueToPop = results[i]
|
|
1174
1577
|
// 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) {
|
|
1578
|
+
if (results[i] && typeof results[i] === 'object' && (results[i].__internal_only_flag || results[i].__internal_metadata)) {
|
|
1176
1579
|
valueToPop = results[i].value
|
|
1177
1580
|
}
|
|
1178
1581
|
result = this.populateVariable(valueObject, matches[i].match, valueToPop)
|
|
@@ -1216,7 +1619,10 @@ class Configorama {
|
|
|
1216
1619
|
.then((result) => {
|
|
1217
1620
|
// console.log('renderMatches result', result)
|
|
1218
1621
|
if (root && isArray(matches)) {
|
|
1219
|
-
return this.populateValue({
|
|
1622
|
+
return this.populateValue({
|
|
1623
|
+
value: result.value,
|
|
1624
|
+
resolutionHistory: result.resolutionHistory || valueObject.resolutionHistory || []
|
|
1625
|
+
}, root, 'self populateValue')
|
|
1220
1626
|
}
|
|
1221
1627
|
return result
|
|
1222
1628
|
})
|
|
@@ -1254,11 +1660,11 @@ class Configorama {
|
|
|
1254
1660
|
|
|
1255
1661
|
const parts = splitByComma(variable, this.variableSyntax)
|
|
1256
1662
|
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)
|
|
1663
|
+
console.log('splitAndGet parts', parts)
|
|
1664
|
+
console.log('splitAndGet parts variable:', variable)
|
|
1665
|
+
console.log('splitAndGet parts originalVar:', originalVar)
|
|
1666
|
+
console.log('splitAndGet parts current valueObject:', valueObject)
|
|
1667
|
+
console.log('splitAndGet All parts:', parts)
|
|
1262
1668
|
console.log('-----')
|
|
1263
1669
|
}
|
|
1264
1670
|
if (parts.length <= 1) {
|
|
@@ -1279,16 +1685,20 @@ class Configorama {
|
|
|
1279
1685
|
populateVariable(valueObject, matchedString, valueToPopulate) {
|
|
1280
1686
|
let property = valueObject.value
|
|
1281
1687
|
// console.log('init property', property)
|
|
1282
|
-
|
|
1688
|
+
|
|
1283
1689
|
if (DEBUG) {
|
|
1284
1690
|
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('
|
|
1691
|
+
console.log('populateVariable: valueObject', valueObject)
|
|
1692
|
+
console.log('populateVariable: valueToPopulate', valueToPopulate)
|
|
1693
|
+
console.log('populateVariable: typeof valueToPopulate', typeof valueToPopulate)
|
|
1694
|
+
console.log(`populateVariable: path "${valueObject.path}"`)
|
|
1695
|
+
console.log(`populateVariable: value \`${valueObject.value}\``)
|
|
1696
|
+
console.log(`populateVariable: originalSource \`${valueObject.originalSource}\``)
|
|
1697
|
+
console.log('populateVariable: property', property)
|
|
1698
|
+
console.log('populateVariable: matchedString', matchedString)
|
|
1699
|
+
if (valueObject.resolutionHistory && valueObject.resolutionHistory.length > 0) {
|
|
1700
|
+
console.log('populateVariable: resolutionHistory', JSON.stringify(valueObject.resolutionHistory, null, 2))
|
|
1701
|
+
}
|
|
1292
1702
|
}
|
|
1293
1703
|
|
|
1294
1704
|
const originalSrc = valueObject.originalSource || ''
|
|
@@ -1307,9 +1717,35 @@ class Configorama {
|
|
|
1307
1717
|
if (DEBUG_TYPE) console.log('DEBUG_TYPE total replacement')
|
|
1308
1718
|
const v = valueObject.value || ''
|
|
1309
1719
|
property = valueToPopulate
|
|
1720
|
+
// console.log('hasFilters', hasFilters)
|
|
1721
|
+
// console.log('valueToPopulate', valueToPopulate)
|
|
1722
|
+
/* Check resolution history for parent details */
|
|
1723
|
+
if (valueObject.resolutionHistory && valueObject.resolutionHistory.length) {
|
|
1724
|
+
const currentDetails = valueObject.resolutionHistory[valueObject.resolutionHistory.length - 1]
|
|
1725
|
+
|
|
1726
|
+
// get 2nd to last item in resolution history
|
|
1727
|
+
const parentDetails = valueObject.resolutionHistory[valueObject.resolutionHistory.length - 2]
|
|
1728
|
+
/*
|
|
1729
|
+
console.log('currentDetails', currentDetails)
|
|
1730
|
+
console.log('parentDetails', parentDetails)
|
|
1731
|
+
/** */
|
|
1732
|
+
|
|
1733
|
+
/* Convert a fallback number to string */
|
|
1734
|
+
if (currentDetails &&
|
|
1735
|
+
currentDetails.resultType === 'number' &&
|
|
1736
|
+
parentDetails && parentDetails.resultType === 'string' &&
|
|
1737
|
+
parentDetails.result.match(/^\d+$/) && parentDetails.varType === 'env'
|
|
1738
|
+
) {
|
|
1739
|
+
if (Number(parentDetails.result) === currentDetails.result) {
|
|
1740
|
+
property = String(valueToPopulate)
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
}
|
|
1310
1745
|
|
|
1311
1746
|
/* Handle ${self:custom.ref, ''} with deep values */
|
|
1312
1747
|
if (v.match(deepRefSyntax) && originalSrc.match(this.variableSyntax) && !v.match(/deep\:(\d*)\..*}$/)) {
|
|
1748
|
+
// console.log('deep ref syntax')
|
|
1313
1749
|
// console.log('deep var', this.deep)
|
|
1314
1750
|
// console.log('originalSrc', originalSrc)
|
|
1315
1751
|
// console.log('value', v)
|
|
@@ -1412,7 +1848,12 @@ class Configorama {
|
|
|
1412
1848
|
missingValue = this.deep[i]
|
|
1413
1849
|
}
|
|
1414
1850
|
|
|
1415
|
-
const cleanVar = cleanVariable(
|
|
1851
|
+
const cleanVar = cleanVariable(
|
|
1852
|
+
property,
|
|
1853
|
+
this.variableSyntax,
|
|
1854
|
+
true,
|
|
1855
|
+
`populateVariable fallback ${this.callCount}`
|
|
1856
|
+
)
|
|
1416
1857
|
const cleanVarNoFilters = cleanVar.split('|')[0]
|
|
1417
1858
|
const splitVars = splitByComma(cleanVarNoFilters)
|
|
1418
1859
|
const nestedVar = findNestedVariable(splitVars, valueObject.originalSource)
|
|
@@ -1427,6 +1868,7 @@ class Configorama {
|
|
|
1427
1868
|
value: fallbackStr,
|
|
1428
1869
|
path: valueObject.path,
|
|
1429
1870
|
originalSource: valueObject.originalSource,
|
|
1871
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1430
1872
|
// set __internal_only_flag to note this is object we make not a resolved value
|
|
1431
1873
|
__internal_only_flag: true,
|
|
1432
1874
|
caller: 'nestedVar',
|
|
@@ -1478,6 +1920,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1478
1920
|
value: finalProp, // prop to fix nested ¯\_(ツ)_/¯
|
|
1479
1921
|
path: valueObject.path,
|
|
1480
1922
|
originalSource: valueObject.originalSource,
|
|
1923
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1481
1924
|
// set __internal_only_flag to note this is object we make not a resolved value
|
|
1482
1925
|
// __internal_only_flag: true
|
|
1483
1926
|
}
|
|
@@ -1496,13 +1939,15 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1496
1939
|
}
|
|
1497
1940
|
}
|
|
1498
1941
|
*/
|
|
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
1942
|
|
|
1504
1943
|
if (
|
|
1944
|
+
/* Not another variable reference */
|
|
1945
|
+
!prop.match(this.variableSyntax)
|
|
1946
|
+
&&
|
|
1947
|
+
/* Not file or text refs */
|
|
1505
1948
|
!prop.match(fileRefSyntax)
|
|
1949
|
+
&& !prop.match(textRefSyntax)
|
|
1950
|
+
/* Not eval refs */
|
|
1506
1951
|
&& !prop.match(getValueFromEval.match)
|
|
1507
1952
|
// AND is not multiline value
|
|
1508
1953
|
&& (func && prop.split('\n').length < 3)) {
|
|
@@ -1523,14 +1968,21 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1523
1968
|
|
|
1524
1969
|
// console.log('foundFilters', foundFilters)
|
|
1525
1970
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
typeof valueToPopulate === 'string' &&
|
|
1531
|
-
!valueToPopulate.match(deepRefSyntax) &&
|
|
1971
|
+
let runFilters = false
|
|
1972
|
+
if (typeof valueToPopulate === 'number' && foundFilters.length) {
|
|
1973
|
+
runFilters = true
|
|
1974
|
+
} else if (
|
|
1975
|
+
typeof valueToPopulate === 'string' &&
|
|
1976
|
+
!valueToPopulate.match(deepRefSyntax) &&
|
|
1977
|
+
foundFilters.length &&
|
|
1532
1978
|
!property.match(this.variableSyntax)
|
|
1533
1979
|
) {
|
|
1980
|
+
runFilters = true
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
/* Apply filters if found */
|
|
1984
|
+
//console.log('> property', property)
|
|
1985
|
+
if (runFilters) {
|
|
1534
1986
|
// If filter cache exists we need to remove filter that have already been run
|
|
1535
1987
|
if (this.filterCache[valueObject.path]) {
|
|
1536
1988
|
foundFilters = foundFilters.filter((filter) => {
|
|
@@ -1555,6 +2007,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1555
2007
|
value: property,
|
|
1556
2008
|
path: valueObject.path,
|
|
1557
2009
|
originalSource: valueObject.originalSource,
|
|
2010
|
+
resolutionHistory: valueObject.resolutionHistory || [],
|
|
1558
2011
|
__internal_only_flag: true, // set __internal_only_flag to note this is object we make not a resolved value
|
|
1559
2012
|
caller: 'end',
|
|
1560
2013
|
count: this.callCount,
|
|
@@ -1596,15 +2049,22 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1596
2049
|
// console.log('propertyString', typeof propertyString)
|
|
1597
2050
|
const variableValues = variableStrings.map((variableString) => {
|
|
1598
2051
|
// This runs on nested variable resolution
|
|
1599
|
-
return this.getValueFromSource(variableString, valueObject, 'overwrite')
|
|
2052
|
+
return this.getValueFromSource(variableString, valueObject, 'overwrite', valueObject.originalSource)
|
|
1600
2053
|
})
|
|
1601
|
-
|
|
2054
|
+
|
|
1602
2055
|
// console.log('variableValues', variableValues)
|
|
1603
2056
|
return Promise.all(variableValues).then((values) => {
|
|
1604
2057
|
let deepPropertyStr = propertyString
|
|
1605
2058
|
let deepProperties = 0
|
|
1606
2059
|
// console.log('overwrite values', valuesToUse)
|
|
1607
|
-
values
|
|
2060
|
+
// Extract actual values from metadata objects
|
|
2061
|
+
const extractedValues = values.map((value) => {
|
|
2062
|
+
if (value && typeof value === 'object' && (value.__internal_only_flag || value.__internal_metadata)) {
|
|
2063
|
+
return value.value
|
|
2064
|
+
}
|
|
2065
|
+
return value
|
|
2066
|
+
})
|
|
2067
|
+
extractedValues.forEach((value, index) => {
|
|
1608
2068
|
// console.log('───────────────────────────────> value', value)
|
|
1609
2069
|
if (isString(value) && value.match(this.variableSyntax)) {
|
|
1610
2070
|
deepProperties += 1
|
|
@@ -1620,7 +2080,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1620
2080
|
})
|
|
1621
2081
|
return deepProperties > 0
|
|
1622
2082
|
? Promise.resolve(deepPropertyStr) // return deep variable replacement of original
|
|
1623
|
-
: Promise.resolve(
|
|
2083
|
+
: Promise.resolve(extractedValues.find(isValidValue)) // resolve first valid value, else undefined
|
|
1624
2084
|
})
|
|
1625
2085
|
}
|
|
1626
2086
|
/**
|
|
@@ -1632,6 +2092,25 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1632
2092
|
// console.log('getValueFromSrc caller', caller)
|
|
1633
2093
|
const propertyString = valueObject.value
|
|
1634
2094
|
const pathValue = valueObject.path
|
|
2095
|
+
|
|
2096
|
+
// Track every call to getValueFromSource for metadata
|
|
2097
|
+
if (pathValue && pathValue.length) {
|
|
2098
|
+
const pathKey = pathValue.join('.')
|
|
2099
|
+
if (!this.resolutionTracking[pathKey]) {
|
|
2100
|
+
this.resolutionTracking[pathKey] = {
|
|
2101
|
+
path: pathKey,
|
|
2102
|
+
originalPropertyString: propertyString,
|
|
2103
|
+
calls: []
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
this.resolutionTracking[pathKey].calls.push({
|
|
2108
|
+
variableString: variableString,
|
|
2109
|
+
propertyString: propertyString,
|
|
2110
|
+
caller: caller
|
|
2111
|
+
})
|
|
2112
|
+
}
|
|
2113
|
+
|
|
1635
2114
|
// console.log('getValueFromSrc propertyString', propertyString)
|
|
1636
2115
|
// console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
|
|
1637
2116
|
if (this.tracker.contains(variableString)) {
|
|
@@ -1642,11 +2121,12 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1642
2121
|
let newHasFilter
|
|
1643
2122
|
// Else lookup value from various sources
|
|
1644
2123
|
if (DEBUG) {
|
|
1645
|
-
console.log(`>>>>> getValueFromSrc()
|
|
1646
|
-
console.log('
|
|
1647
|
-
console.log('
|
|
1648
|
-
console.log('
|
|
1649
|
-
console.log('
|
|
2124
|
+
console.log(`>>>>> getValueFromSrc() caller - ${caller}`)
|
|
2125
|
+
console.log('getValueFromSource originalVar', originalVar)
|
|
2126
|
+
console.log('getValueFromSource variableString:', variableString)
|
|
2127
|
+
console.log('getValueFromSource propertyString:', propertyString)
|
|
2128
|
+
console.log('getValueFromSource pathValue:', valueObject.path)
|
|
2129
|
+
console.log('getValueFromSource valueObject:', valueObject)
|
|
1650
2130
|
console.log('-----')
|
|
1651
2131
|
}
|
|
1652
2132
|
|
|
@@ -1674,6 +2154,8 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1674
2154
|
})
|
|
1675
2155
|
.map((f) => {
|
|
1676
2156
|
return trim(f)
|
|
2157
|
+
// TODO refactor this. This is a temp fix for filters with nested vars.
|
|
2158
|
+
.replace(/}$/, '')
|
|
1677
2159
|
})
|
|
1678
2160
|
// console.log('filters to run', _filter)
|
|
1679
2161
|
|
|
@@ -1730,6 +2212,21 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1730
2212
|
valueObject,
|
|
1731
2213
|
|
|
1732
2214
|
).then((val) => {
|
|
2215
|
+
// Update the last call with the resolved value
|
|
2216
|
+
if (pathValue && pathValue.length) {
|
|
2217
|
+
const pathKey = pathValue.join('.')
|
|
2218
|
+
if (this.resolutionTracking[pathKey] && this.resolutionTracking[pathKey].calls.length) {
|
|
2219
|
+
// Find the most recent call for this variableString
|
|
2220
|
+
for (let i = this.resolutionTracking[pathKey].calls.length - 1; i >= 0; i--) {
|
|
2221
|
+
if (this.resolutionTracking[pathKey].calls[i].variableString === variableString) {
|
|
2222
|
+
this.resolutionTracking[pathKey].calls[i].resolvedValue = val
|
|
2223
|
+
this.resolutionTracking[pathKey].calls[i].resolverType = resolverType
|
|
2224
|
+
break
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
1733
2230
|
// console.log('VALUE', val)
|
|
1734
2231
|
if (
|
|
1735
2232
|
val === null ||
|
|
@@ -1763,7 +2260,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1763
2260
|
// console.log('valueCount', valueCount)
|
|
1764
2261
|
// TODO throw on empty values?
|
|
1765
2262
|
// No fallback value found AND this is undefined, throw error
|
|
1766
|
-
const nestedVars = findNestedVariables(propertyString, this.variableSyntax)
|
|
2263
|
+
const nestedVars = findNestedVariables(propertyString, this.variableSyntax, this.variablesKnownTypes)
|
|
1767
2264
|
// console.log('nestedVars', nestedVars)
|
|
1768
2265
|
const noNestedVars = nestedVars.length < 2
|
|
1769
2266
|
if (valueCount.length === 1 && noNestedVars) {
|
|
@@ -1792,7 +2289,19 @@ Unable to resolve configuration variable
|
|
|
1792
2289
|
if (!newHasFilter) {
|
|
1793
2290
|
// console.log('no newHasFilter', val, valueObject)
|
|
1794
2291
|
// console.log('> RESOLVER RETURN newValue 3', val, originalVar)
|
|
1795
|
-
|
|
2292
|
+
// Wrap value with resolverType metadata for resolution tracking
|
|
2293
|
+
// But don't wrap if it's already an internal flag object
|
|
2294
|
+
if (val && typeof val === 'object' && val.__internal_only_flag) {
|
|
2295
|
+
// Attach resolverType to existing internal object
|
|
2296
|
+
val.__resolverType = resolverType
|
|
2297
|
+
return Promise.resolve(val)
|
|
2298
|
+
}
|
|
2299
|
+
return Promise.resolve({
|
|
2300
|
+
value: val,
|
|
2301
|
+
__resolverType: resolverType,
|
|
2302
|
+
__variableString: variableString,
|
|
2303
|
+
__internal_metadata: true
|
|
2304
|
+
})
|
|
1796
2305
|
}
|
|
1797
2306
|
|
|
1798
2307
|
const newUse = newHasFilter.reduce((acc, currentFilter, i) => {
|
|
@@ -1805,6 +2314,8 @@ Unable to resolve configuration variable
|
|
|
1805
2314
|
// args: argsToPass
|
|
1806
2315
|
})
|
|
1807
2316
|
}, [])
|
|
2317
|
+
// console.log('pathValue', pathValue)
|
|
2318
|
+
// console.log('propertyString', propertyString)
|
|
1808
2319
|
// console.log('newUse', newUse)
|
|
1809
2320
|
|
|
1810
2321
|
if (typeof val === 'string' && val.match(/deep:/)) {
|
|
@@ -1841,7 +2352,19 @@ Unable to resolve configuration variable
|
|
|
1841
2352
|
}, val)
|
|
1842
2353
|
// console.log('> RESOLVER RETURN newValue', newValue)
|
|
1843
2354
|
// console.log('> RESOLVER RETURN newValue 5', newValue)
|
|
1844
|
-
|
|
2355
|
+
// Wrap value with resolverType metadata for resolution tracking
|
|
2356
|
+
// But don't wrap if it's already an internal flag object
|
|
2357
|
+
if (newValue && typeof newValue === 'object' && newValue.__internal_only_flag) {
|
|
2358
|
+
// Attach resolverType to existing internal object
|
|
2359
|
+
newValue.__resolverType = resolverType
|
|
2360
|
+
return Promise.resolve(newValue)
|
|
2361
|
+
}
|
|
2362
|
+
return Promise.resolve({
|
|
2363
|
+
value: newValue,
|
|
2364
|
+
__resolverType: resolverType,
|
|
2365
|
+
__variableString: variableString,
|
|
2366
|
+
__internal_metadata: true
|
|
2367
|
+
})
|
|
1845
2368
|
})
|
|
1846
2369
|
|
|
1847
2370
|
// console.log('valuePromise', valuePromise)
|
|
@@ -1851,12 +2374,20 @@ Unable to resolve configuration variable
|
|
|
1851
2374
|
return this.tracker.add(variableString, valuePromise, propertyString, newHasFilter, promiseKey)
|
|
1852
2375
|
}
|
|
1853
2376
|
|
|
2377
|
+
// console.log('fall thru variableString', variableString)
|
|
2378
|
+
|
|
1854
2379
|
/* fall through case with self refs */
|
|
1855
2380
|
if (variableString) {
|
|
1856
2381
|
// console.log('before clean propertyString', propertyString, variableString)
|
|
1857
|
-
const clean = cleanVariable(
|
|
2382
|
+
const clean = cleanVariable(
|
|
2383
|
+
propertyString,
|
|
2384
|
+
this.variableSyntax,
|
|
2385
|
+
true,
|
|
2386
|
+
`getValueFromSrc self ${this.callCount}`
|
|
2387
|
+
)
|
|
1858
2388
|
// TODO @DWELLS cleanVariable makes fallback values with spaces have no spaces
|
|
1859
2389
|
// console.log('AFTER cleanVariable', clean)
|
|
2390
|
+
// console.log(typeof clean)
|
|
1860
2391
|
const cleanClean = clean.split('|')[0]
|
|
1861
2392
|
// console.log('cleanCleanVariable', cleanClean)
|
|
1862
2393
|
if (funcRegex.exec(cleanClean)) {
|
|
@@ -1865,7 +2396,8 @@ Unable to resolve configuration variable
|
|
|
1865
2396
|
}
|
|
1866
2397
|
|
|
1867
2398
|
const split = splitByComma(cleanClean)
|
|
1868
|
-
|
|
2399
|
+
// console.log('split', split)
|
|
2400
|
+
// console.log('typeof split', typeof split)
|
|
1869
2401
|
// @TODO refactor this. USE FILTER [ 'commas', 'split("-"' ] is wrong
|
|
1870
2402
|
let fallbackValue
|
|
1871
2403
|
if (split.length === 2 || split.length === 3) {
|
|
@@ -1874,7 +2406,9 @@ Unable to resolve configuration variable
|
|
|
1874
2406
|
fallbackValue = split[0]
|
|
1875
2407
|
}
|
|
1876
2408
|
|
|
2409
|
+
// TODO this should be new in memory resolutionHistory probably?
|
|
1877
2410
|
const nestedVar = findNestedVariable(split, valueObject.originalSource)
|
|
2411
|
+
// console.log('nestedVar', nestedVar)
|
|
1878
2412
|
|
|
1879
2413
|
if (nestedVar) {
|
|
1880
2414
|
if (!this.opts.allowUnknownVars) {
|
|
@@ -1883,7 +2417,7 @@ Unable to resolve configuration variable
|
|
|
1883
2417
|
const fallbackStr = getFallbackString(split, nestedVar)
|
|
1884
2418
|
return this.getValueFromSource(variableString, {
|
|
1885
2419
|
value: fallbackStr,
|
|
1886
|
-
}, 'nestedVar')
|
|
2420
|
+
}, 'nestedVar', originalVar)
|
|
1887
2421
|
}
|
|
1888
2422
|
|
|
1889
2423
|
// TODO verify we need this still with file(file.js, param)
|
|
@@ -1895,7 +2429,7 @@ Unable to resolve configuration variable
|
|
|
1895
2429
|
// recurse on fallback and check again
|
|
1896
2430
|
return this.getValueFromSource(`${variableString})`, {
|
|
1897
2431
|
value: propertyString,
|
|
1898
|
-
}, 'cleanClean.match(fileRefSyntax)')
|
|
2432
|
+
}, 'cleanClean.match(fileRefSyntax)', originalVar)
|
|
1899
2433
|
}
|
|
1900
2434
|
}
|
|
1901
2435
|
// const fallbackValue = split[1]
|
|
@@ -1907,7 +2441,10 @@ Unable to resolve configuration variable
|
|
|
1907
2441
|
const valuePromise = Promise.resolve(fallbackValue)
|
|
1908
2442
|
return this.tracker.add(fallbackValue, valuePromise, propertyString, newHasFilter)
|
|
1909
2443
|
}
|
|
1910
|
-
|
|
2444
|
+
/*
|
|
2445
|
+
console.log('what is fallbackValue', fallbackValue)
|
|
2446
|
+
console.log('typeof fallbackValue', typeof fallbackValue)
|
|
2447
|
+
/** */
|
|
1911
2448
|
// has fallback but needs deeper lookup. Call getValueFromSrc again
|
|
1912
2449
|
if (fallbackValue) {
|
|
1913
2450
|
if (DEBUG) console.log('fallbackValue', fallbackValue)
|
|
@@ -1915,11 +2452,21 @@ Unable to resolve configuration variable
|
|
|
1915
2452
|
// recurse on fallback and check again
|
|
1916
2453
|
return this.getValueFromSource(
|
|
1917
2454
|
fallbackValue,
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2455
|
+
valueObject,
|
|
2456
|
+
// Object.assign({}, valueObject, { value: propertyString }),
|
|
2457
|
+
// {
|
|
2458
|
+
// value: propertyString,
|
|
2459
|
+
// path: valueObject.path,
|
|
2460
|
+
// originalSource: valueObject.originalSource,
|
|
2461
|
+
// ahh:true
|
|
2462
|
+
// },
|
|
1921
2463
|
'fallbackValue',
|
|
1922
|
-
|
|
2464
|
+
originalVar,
|
|
2465
|
+
).then((res) => {
|
|
2466
|
+
// console.log('res', res)
|
|
2467
|
+
// console.log('typeof res', typeof res)
|
|
2468
|
+
return res
|
|
2469
|
+
})
|
|
1923
2470
|
}
|
|
1924
2471
|
}
|
|
1925
2472
|
|
|
@@ -1932,7 +2479,9 @@ Unable to resolve configuration variable
|
|
|
1932
2479
|
]
|
|
1933
2480
|
|
|
1934
2481
|
// Default value used for self variable
|
|
1935
|
-
if (
|
|
2482
|
+
// Only show this error if the variable itself (not a parent fallback) is a self-reference with a fallback
|
|
2483
|
+
const isSelfReference = !variableString.match(/^(env|opt|file|text|cron|eval|git):/)
|
|
2484
|
+
if (isSelfReference && variableString.match(/,/)) {
|
|
1936
2485
|
errorMessage.push('\n Default values for self referenced values are not allowed')
|
|
1937
2486
|
errorMessage.push(`\n Fix the ${propertyString} variable`)
|
|
1938
2487
|
}
|
|
@@ -1940,23 +2489,30 @@ Unable to resolve configuration variable
|
|
|
1940
2489
|
let allowSpecialCase = false
|
|
1941
2490
|
/* handle special cases for cloudformation ${Sub} values */
|
|
1942
2491
|
if (this.originalConfig && key.endsWith('Fn::Sub')) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2492
|
+
if (this.opts.verifySubReferences) {
|
|
2493
|
+
const params = this.originalConfig.Parameters || (this.originalConfig.resources || {}).Parameters
|
|
2494
|
+
const resources = this.originalConfig.Resources || (this.originalConfig.resources || {}).Resources
|
|
2495
|
+
/* Cloudformation Resource References */
|
|
2496
|
+
if (resources && resources[variableString]) {
|
|
2497
|
+
allowSpecialCase = true
|
|
2498
|
+
} else if (params && params[variableString]) {
|
|
2499
|
+
allowSpecialCase = true
|
|
2500
|
+
} else if (variableString === 'ApiGatewayRestApi') {
|
|
2501
|
+
// Allow for "hidden" cloudformation variables, set by sls framework
|
|
2502
|
+
allowSpecialCase = true
|
|
2503
|
+
} else if (variableString === 'HttpApi') {
|
|
2504
|
+
// Allow for "hidden" cloudformation variables, set by sls framework
|
|
2505
|
+
allowSpecialCase = true
|
|
2506
|
+
}
|
|
2507
|
+
} else {
|
|
2508
|
+
// Default let any sub references pass through
|
|
1955
2509
|
allowSpecialCase = true
|
|
1956
2510
|
}
|
|
1957
2511
|
}
|
|
1958
2512
|
/* Todo handle stage variables */
|
|
1959
2513
|
|
|
2514
|
+
|
|
2515
|
+
|
|
1960
2516
|
/* Pass through unknown variables */
|
|
1961
2517
|
if (this.opts.allowUnknownVars || allowSpecialCase) {
|
|
1962
2518
|
// console.log('allowUnknownVars propertyString', propertyString)
|
|
@@ -2061,7 +2617,10 @@ Unable to resolve configuration variable
|
|
|
2061
2617
|
} else if (resolvedPath.match(/\.\//)) {
|
|
2062
2618
|
// TODO test higher parent refs
|
|
2063
2619
|
const cleanName = path.basename(resolvedPath)
|
|
2064
|
-
|
|
2620
|
+
const findUpResult = findUp.sync(cleanName, { cwd: this.configPath })
|
|
2621
|
+
if (findUpResult) {
|
|
2622
|
+
fullFilePath = findUpResult
|
|
2623
|
+
}
|
|
2065
2624
|
}
|
|
2066
2625
|
|
|
2067
2626
|
let fileExtension = resolvedPath.split('.')
|
|
@@ -2070,18 +2629,39 @@ Unable to resolve configuration variable
|
|
|
2070
2629
|
|
|
2071
2630
|
// Validate file exists
|
|
2072
2631
|
if (!fs.existsSync(fullFilePath)) {
|
|
2632
|
+
const originalVar = options.context && options.context.originalSource
|
|
2633
|
+
|
|
2634
|
+
const findNestedResult = findNestedVariables(
|
|
2635
|
+
originalVar,
|
|
2636
|
+
this.variableSyntax,
|
|
2637
|
+
this.variablesKnownTypes,
|
|
2638
|
+
options.context.path
|
|
2639
|
+
)
|
|
2640
|
+
// console.log('findNestedResult', findNestedResult)
|
|
2641
|
+
let hasFallback = false
|
|
2642
|
+
if (findNestedResult) {
|
|
2643
|
+
const varDetails = findNestedResult[0]
|
|
2644
|
+
// console.log('varDetails', varDetails)
|
|
2645
|
+
hasFallback = varDetails.hasFallback
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// check if original var has fallback value
|
|
2073
2649
|
// console.log('NO FILE FOUND', fullFilePath)
|
|
2074
2650
|
// console.log('variableString', variableString)
|
|
2075
|
-
|
|
2076
|
-
|
|
2651
|
+
|
|
2652
|
+
if (!hasFallback) {
|
|
2653
|
+
const errorMsg = makeBox({
|
|
2654
|
+
title: `File Not Found in ${originalVar}`,
|
|
2655
|
+
text: `Variable ${variableString} cannot resolve due to missing file.
|
|
2077
2656
|
|
|
2078
2657
|
File not found ${fullFilePath}
|
|
2079
2658
|
|
|
2080
2659
|
Default fallback value will be used if provided.
|
|
2081
|
-
${logLines}
|
|
2082
|
-
`
|
|
2083
2660
|
|
|
2084
|
-
|
|
2661
|
+
${JSON.stringify(options.context, null, 2)}`,
|
|
2662
|
+
})
|
|
2663
|
+
console.log(errorMsg)
|
|
2664
|
+
}
|
|
2085
2665
|
// TODO maybe reject. YAML does not allow for null/undefined values
|
|
2086
2666
|
// return Promise.reject(new Error(errorMsg))
|
|
2087
2667
|
return Promise.resolve(undefined)
|
|
@@ -2099,6 +2679,7 @@ ${logLines}
|
|
|
2099
2679
|
|
|
2100
2680
|
// Process JS files
|
|
2101
2681
|
if (fileExtension === 'js' || fileExtension === 'cjs') {
|
|
2682
|
+
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
2102
2683
|
const jsFile = require(fullFilePath)
|
|
2103
2684
|
let returnValueFunction = jsFile
|
|
2104
2685
|
// TODO change how exported functions are referenced
|
|
@@ -2198,6 +2779,7 @@ Check if your TypeScript is returning the correct data.`
|
|
|
2198
2779
|
}
|
|
2199
2780
|
|
|
2200
2781
|
if (fileExtension === 'mjs' || fileExtension === 'esm') {
|
|
2782
|
+
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
2201
2783
|
const { executeESMFile } = require('./parsers/esm')
|
|
2202
2784
|
let returnValueFunction
|
|
2203
2785
|
const variableArray = variableString.split(':')
|
|
@@ -2269,9 +2851,10 @@ Check if your ESM is returning the correct data.`
|
|
|
2269
2851
|
// console.log('deep', variableString)
|
|
2270
2852
|
// console.log('matchedFileString', matchedFileString)
|
|
2271
2853
|
let deepProperties = variableString.replace(matchedFileString, '')
|
|
2854
|
+
// TODO 2025-11-12 add file.path.support instead of just :
|
|
2272
2855
|
if (deepProperties.substring(0, 1) !== ':') {
|
|
2273
2856
|
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}" sub properties
|
|
2274
|
-
Please use ":" to reference sub properties`
|
|
2857
|
+
Please use ":" to reference sub properties. ${deepProperties}`
|
|
2275
2858
|
return Promise.reject(new Error(errorMessage))
|
|
2276
2859
|
}
|
|
2277
2860
|
deepProperties = deepProperties.slice(1).split('.')
|
|
@@ -2298,7 +2881,7 @@ Please use ":" to reference sub properties`
|
|
|
2298
2881
|
return Promise.resolve(valueToPopulate)
|
|
2299
2882
|
}
|
|
2300
2883
|
}
|
|
2301
|
-
console.log('fall thru', valueToPopulate)
|
|
2884
|
+
// console.log('fall thru', valueToPopulate)
|
|
2302
2885
|
return Promise.resolve(valueToPopulate)
|
|
2303
2886
|
}
|
|
2304
2887
|
getVariableFromDeep(variableString) {
|
|
@@ -2310,15 +2893,22 @@ Please use ":" to reference sub properties`
|
|
|
2310
2893
|
/** */
|
|
2311
2894
|
return this.deep[index]
|
|
2312
2895
|
}
|
|
2313
|
-
getValueFromDeep(variableString) {
|
|
2896
|
+
getValueFromDeep(variableString, pathValue) {
|
|
2314
2897
|
const variable = this.getVariableFromDeep(variableString)
|
|
2315
2898
|
const deepRef = variableString.replace(deepPrefixReplacePattern, '')
|
|
2316
2899
|
/*
|
|
2317
2900
|
console.log("GET getValueFromDeep", variableString)
|
|
2318
|
-
console.log('deepRef', deepRef)
|
|
2901
|
+
console.log('deepRef', (deepRef) ? deepRef : '- no deepRef')
|
|
2319
2902
|
console.log('getValueFromDeep variable', variable)
|
|
2320
2903
|
/** */
|
|
2321
|
-
|
|
2904
|
+
// Preserve path and originalSource information from pathValue
|
|
2905
|
+
const valueObject = {
|
|
2906
|
+
value: variable,
|
|
2907
|
+
path: pathValue ? pathValue.path : undefined,
|
|
2908
|
+
originalSource: pathValue ? pathValue.originalSource : undefined,
|
|
2909
|
+
resolutionHistory: pathValue ? pathValue.resolutionHistory : []
|
|
2910
|
+
}
|
|
2911
|
+
let ret = this.populateValue(valueObject, undefined, 'getValueFromDeep')
|
|
2322
2912
|
if (deepRef.length) {
|
|
2323
2913
|
// if there is a deep reference remaining
|
|
2324
2914
|
ret = ret.then((result) => {
|
|
@@ -2342,7 +2932,12 @@ Please use ":" to reference sub properties`
|
|
|
2342
2932
|
}
|
|
2343
2933
|
// console.log("makeDeepVariable SET INDEX", index)
|
|
2344
2934
|
const variableContainer = variable.match(this.variableSyntax)[0]
|
|
2345
|
-
const variableString = cleanVariable(
|
|
2935
|
+
const variableString = cleanVariable(
|
|
2936
|
+
variableContainer,
|
|
2937
|
+
this.variableSyntax,
|
|
2938
|
+
true,
|
|
2939
|
+
`makeDeepVariable ${this.callCount}`
|
|
2940
|
+
)
|
|
2346
2941
|
const deepVar = variableContainer.replace(variableString, `deep:${index}`)
|
|
2347
2942
|
/*
|
|
2348
2943
|
console.log('MAKE DEEP', variable, caller)
|