configorama 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +6 -1
- package/package.json +4 -3
- package/src/main.js +85 -16
- package/src/resolvers/valueFromString.js +1 -0
- package/src/utils/find-nested-variables.js +117 -83
- package/src/utils/find-nested-variables.test.js +31 -11
- package/src/utils/x.js +173 -0
package/cli.js
CHANGED
|
@@ -92,8 +92,13 @@ if (options.dynamicArgs.verbose) {
|
|
|
92
92
|
'allow-undefined': allowUndefined,
|
|
93
93
|
...rest
|
|
94
94
|
} = dynamicArgs
|
|
95
|
+
|
|
95
96
|
console.log()
|
|
96
|
-
|
|
97
|
+
if (Object.keys(rest).length) {
|
|
98
|
+
deepLog(rest)
|
|
99
|
+
} else {
|
|
100
|
+
console.log('No flag options provided. Set flags like --flag value')
|
|
101
|
+
}
|
|
97
102
|
console.log()
|
|
98
103
|
}
|
|
99
104
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configorama",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Variable support for configuration files",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"docs": "node ./scripts/docs.js",
|
|
19
|
-
"test": "npm run
|
|
19
|
+
"test": "npm run test:lib && uvu tests \".*\\.test.js$\" ",
|
|
20
|
+
"test:tests": "uvu tests \".*\\.test.js$\" ",
|
|
20
21
|
"test:api": "uvu tests/api api.test.js",
|
|
21
|
-
"
|
|
22
|
+
"test:lib": "uvu src \".*\\.test.js$\"",
|
|
22
23
|
"watch": "watchlist tests -- npm test",
|
|
23
24
|
"publish": "git push origin && git push origin --tags",
|
|
24
25
|
"release:patch": "npm version patch && npm publish",
|
package/src/main.js
CHANGED
|
@@ -66,7 +66,7 @@ const deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/)
|
|
|
66
66
|
const deepIndexReplacePattern = new RegExp(/^deep:|(\.[^}]+)*$/g)
|
|
67
67
|
const deepIndexPattern = /deep\:(\d*)/
|
|
68
68
|
const deepPrefixReplacePattern = /(?:^deep:)\d+\.?/g
|
|
69
|
-
const fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
69
|
+
const fileRefSyntax = RegExp(/^file\((~?[\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
70
70
|
// TODO update file regex ^file\((~?[a-zA-Z0-9._\-\/, ]+?)\)
|
|
71
71
|
// To match file(asyncValue.js, lol) input params
|
|
72
72
|
const envRefSyntax = RegExp(/^env:/g)
|
|
@@ -84,6 +84,16 @@ let VERBOSE = process.argv.includes('--verbose') ? true : false
|
|
|
84
84
|
|
|
85
85
|
const ENABLE_FUNCTIONS = true
|
|
86
86
|
|
|
87
|
+
function combineRegexes(regexes) {
|
|
88
|
+
// Extract the pattern from each RegExp and join with OR operator
|
|
89
|
+
const patterns = regexes.map(regex => {
|
|
90
|
+
// Get source pattern string without flags
|
|
91
|
+
return regex.source
|
|
92
|
+
}).filter(Boolean)
|
|
93
|
+
// Join patterns with the OR operator and create new RegExp
|
|
94
|
+
return new RegExp(`(${patterns.join('|')})`)
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
class Configorama {
|
|
88
98
|
constructor(fileOrObject, opts) {
|
|
89
99
|
/* attach sig events on async calls */
|
|
@@ -101,18 +111,21 @@ class Configorama {
|
|
|
101
111
|
|
|
102
112
|
this.filterCache = {}
|
|
103
113
|
|
|
114
|
+
this.foundVariables = []
|
|
115
|
+
|
|
104
116
|
const defaultSyntax = '\\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._\'",|\\-\\/\\(\\)\\\\]+?)}'
|
|
105
117
|
|
|
106
|
-
const
|
|
118
|
+
const varSyntax = options.syntax || defaultSyntax
|
|
107
119
|
let varRegex
|
|
108
|
-
if (typeof
|
|
109
|
-
varRegex = new RegExp(
|
|
120
|
+
if (typeof varSyntax === 'string') {
|
|
121
|
+
varRegex = new RegExp(varSyntax, 'g')
|
|
110
122
|
// this.variableSyntax = /\${((?!AWS)([ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?|(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*))}/
|
|
111
|
-
} else if (
|
|
112
|
-
varRegex =
|
|
123
|
+
} else if (varSyntax instanceof RegExp) {
|
|
124
|
+
varRegex = varSyntax
|
|
113
125
|
}
|
|
114
126
|
// console.log('varRegex', varRegex)
|
|
115
|
-
|
|
127
|
+
const variableSyntax = varRegex
|
|
128
|
+
this.variableSyntax = variableSyntax
|
|
116
129
|
|
|
117
130
|
// Set initial config object to populate
|
|
118
131
|
if (typeof fileOrObject === 'object') {
|
|
@@ -137,13 +150,6 @@ class Configorama {
|
|
|
137
150
|
this.opts
|
|
138
151
|
)
|
|
139
152
|
|
|
140
|
-
if (VERBOSE) {
|
|
141
|
-
console.log('───────────── Input Config ──────────────────────')
|
|
142
|
-
console.log()
|
|
143
|
-
deepLog(configObject)
|
|
144
|
-
console.log()
|
|
145
|
-
}
|
|
146
|
-
|
|
147
153
|
this.configFilePath = fileOrObject
|
|
148
154
|
// set config objects
|
|
149
155
|
this.config = configObject
|
|
@@ -274,7 +280,13 @@ class Configorama {
|
|
|
274
280
|
/* attach self matcher last */
|
|
275
281
|
this.variableTypes = this.variableTypes.concat(fallThroughSelfMatcher)
|
|
276
282
|
|
|
277
|
-
|
|
283
|
+
// const variablesKnownTypes = new RegExp(`^(${this.variableTypes.map((v) => v.prefix || v.type).join('|')}):`)
|
|
284
|
+
const variablesKnownTypes = combineRegexes(this.variableTypes.filter((v) => v.type !== 'string').map((v) => v.match))
|
|
285
|
+
// console.log('variablesKnownTypes', variablesKnownTypes)
|
|
286
|
+
this.variablesKnownTypes = variablesKnownTypes
|
|
287
|
+
|
|
288
|
+
// this.allPatterns = combineRegexes(...this.variableTypes.map((v) => v.match))
|
|
289
|
+
// console.log('this.allPatterns', this.allPatterns)
|
|
278
290
|
// console.log('this.variablesKnownTypes', this.variablesKnownTypes)
|
|
279
291
|
// process.exit(1)
|
|
280
292
|
// Additional filters on values. ${thing | filterFunction}
|
|
@@ -391,6 +403,63 @@ class Configorama {
|
|
|
391
403
|
this.functions = Object.assign({}, this.functions, options.functions)
|
|
392
404
|
}
|
|
393
405
|
|
|
406
|
+
if (VERBOSE) {
|
|
407
|
+
console.log('───────────── Input Config ──────────────────────')
|
|
408
|
+
console.log()
|
|
409
|
+
deepLog(this.originalConfig)
|
|
410
|
+
console.log()
|
|
411
|
+
|
|
412
|
+
const foundVariables = []
|
|
413
|
+
let loggedHeader = false
|
|
414
|
+
traverse(this.originalConfig).forEach(function (rawValue) {
|
|
415
|
+
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
416
|
+
if (!loggedHeader) {
|
|
417
|
+
console.log('───────────── Variables Detected ──────────────────────')
|
|
418
|
+
console.log()
|
|
419
|
+
loggedHeader = true
|
|
420
|
+
}
|
|
421
|
+
const configValuePath = this.path.join('.')
|
|
422
|
+
const nested = findNestedVariables(rawValue, variableSyntax, variablesKnownTypes, configValuePath)
|
|
423
|
+
/*
|
|
424
|
+
console.log(nested)
|
|
425
|
+
/** */
|
|
426
|
+
|
|
427
|
+
console.log(`▷ Path: ${configValuePath}`)
|
|
428
|
+
console.log('\n Key/value:')
|
|
429
|
+
console.log(` ${configValuePath}: ${rawValue}`)
|
|
430
|
+
if (nested.length > 0) {
|
|
431
|
+
const nestedCount = nested.length - 1
|
|
432
|
+
console.log('\n Variable:')
|
|
433
|
+
console.log(` ${nested[nested.length - 1].fullMatch}`)
|
|
434
|
+
|
|
435
|
+
if (nestedCount) {
|
|
436
|
+
console.log(`\n Contains ${nestedCount} nested values.`)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// non mutate remove last
|
|
440
|
+
const removeLast = (nested.length > 1) ? nested.slice(0, -1) : nested
|
|
441
|
+
console.log()
|
|
442
|
+
removeLast.forEach((v) => {
|
|
443
|
+
if (v.hasFallback) {
|
|
444
|
+
console.log(' Resolve order:')
|
|
445
|
+
console.log(` 1. ${v.valueBeforeFallback}`)
|
|
446
|
+
v.fallbackValues.forEach((f, i) => {
|
|
447
|
+
console.log(` ${i + 2}. ${f.fullMatch}${f.isFallback ? ' (Fallback string value)' : ''}`)
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
console.log()
|
|
453
|
+
foundVariables.push(rawValue)
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
console.log(`───────────── Found ${foundVariables.length} Variables ──────────────────────`)
|
|
458
|
+
console.log()
|
|
459
|
+
deepLog(foundVariables)
|
|
460
|
+
console.log()
|
|
461
|
+
}
|
|
462
|
+
|
|
394
463
|
this.deep = []
|
|
395
464
|
this.callCount = 0
|
|
396
465
|
}
|
|
@@ -639,7 +708,7 @@ class Configorama {
|
|
|
639
708
|
// Initial check if value has variable string in it
|
|
640
709
|
return isString(property.value) && property.value.match(this.variableSyntax)
|
|
641
710
|
})
|
|
642
|
-
|
|
711
|
+
|
|
643
712
|
return map(variables, (valueObject) => {
|
|
644
713
|
// console.log('valueObject', valueObject)
|
|
645
714
|
return this.populateValue(valueObject, false, '_populateVariables').then((populated) => {
|
|
@@ -1,63 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* @returns {Array} Array of match objects containing full match and captured group
|
|
6
|
-
*/
|
|
7
|
-
function findNestedVariablesx(input, regex, debug = false) {
|
|
8
|
-
let str = input
|
|
9
|
-
let matches = []
|
|
10
|
-
let match
|
|
11
|
-
let iteration = 0
|
|
12
|
-
|
|
13
|
-
console.log('input', input)
|
|
14
|
-
|
|
15
|
-
if (debug) console.log(`Initial string: ${str}`)
|
|
16
|
-
|
|
17
|
-
// Process string until no more matches are found
|
|
18
|
-
while (true) {
|
|
19
|
-
iteration++
|
|
20
|
-
if (debug) console.log(`\nIteration ${iteration}:`)
|
|
21
|
-
|
|
22
|
-
// Reset regex index
|
|
23
|
-
regex.lastIndex = 0
|
|
24
|
-
|
|
25
|
-
// Find the next match
|
|
26
|
-
match = regex.exec(str)
|
|
27
|
-
if (!match) break
|
|
28
|
-
|
|
29
|
-
// Log match details if in debug mode
|
|
30
|
-
if (debug) {
|
|
31
|
-
console.log(`Match: ${match[0]}`)
|
|
32
|
-
console.log(`Captured group: ${match[1]}`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Store the match
|
|
36
|
-
matches.push({
|
|
37
|
-
fullMatch: match[0],
|
|
38
|
-
variable: match[1],
|
|
39
|
-
order: iteration
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
// Replace the match with placeholder
|
|
43
|
-
str = str.replace(regex, `__REPLACED_${iteration - 1}__`)
|
|
44
|
-
if (debug) console.log(`After replacement: ${str}`)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Replace the `__REPLACED_${iteration - 1}__` with the original match
|
|
48
|
-
matches = matches.map((match, index) => {
|
|
49
|
-
const indexOfReplaced = match.fullMatch.match(/__REPLACED_(\d+)__/)
|
|
50
|
-
if (indexOfReplaced) {
|
|
51
|
-
const replacedIndex = parseInt(indexOfReplaced[1])
|
|
52
|
-
match.fullMatch = match.fullMatch.replace(`__REPLACED_${replacedIndex}__`, matches[replacedIndex].variable)
|
|
53
|
-
match.variable = match.variable.replace(`__REPLACED_${replacedIndex}__`, matches[replacedIndex].variable)
|
|
54
|
-
}
|
|
55
|
-
return match
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
if (debug) console.log(`\nTotal matches found: ${matches.length}`)
|
|
59
|
-
return matches
|
|
60
|
-
}
|
|
1
|
+
const { splitByComma } = require('./splitByComma')
|
|
2
|
+
const trimQuotes = require('./trimSurroundingQuotes')
|
|
3
|
+
const FALLBACK_REGEX = /,\s*/
|
|
4
|
+
const VAR_MATCH_REGEX = /__VAR_\d+__/
|
|
61
5
|
|
|
62
6
|
/**
|
|
63
7
|
* Finds all nested variable interpolations in a string while preserving original syntax
|
|
@@ -77,10 +21,10 @@ function findNestedVariablesx(input, regex, debug = false) {
|
|
|
77
21
|
* @param {boolean} debug - Whether to print debug information
|
|
78
22
|
* @returns {Array} Array of match objects with fullMatch, variable, varString and other properties
|
|
79
23
|
*/
|
|
80
|
-
function findNestedVariables(input, regex, debug = false) {
|
|
24
|
+
function findNestedVariables(input, regex, variablesKnownTypes, location, debug = false) {
|
|
81
25
|
// Create a copy of the input for replacement tracking
|
|
82
|
-
let
|
|
83
|
-
// console.log('
|
|
26
|
+
let current = input
|
|
27
|
+
// console.log('current', current)
|
|
84
28
|
// Store matches with their positions in the original string
|
|
85
29
|
let matches = []
|
|
86
30
|
// Track original positions and replacements
|
|
@@ -99,7 +43,7 @@ function findNestedVariables(input, regex, debug = false) {
|
|
|
99
43
|
regex.lastIndex = 0
|
|
100
44
|
|
|
101
45
|
// Find the next match in the working string
|
|
102
|
-
match = regex.exec(
|
|
46
|
+
match = regex.exec(current)
|
|
103
47
|
if (!match) break
|
|
104
48
|
|
|
105
49
|
// Generate a unique placeholder
|
|
@@ -107,12 +51,15 @@ function findNestedVariables(input, regex, debug = false) {
|
|
|
107
51
|
|
|
108
52
|
// Store match details
|
|
109
53
|
const matchInfo = {
|
|
54
|
+
location,
|
|
55
|
+
value: input,
|
|
110
56
|
fullMatch: match[0],
|
|
111
|
-
variable: match[1],
|
|
112
|
-
|
|
57
|
+
variable: match[1].trim(),
|
|
58
|
+
varString: match[1],
|
|
59
|
+
resolveOrder: iteration,
|
|
113
60
|
start: match.index,
|
|
114
61
|
end: match.index + match[0].length,
|
|
115
|
-
placeholder
|
|
62
|
+
placeholder,
|
|
116
63
|
}
|
|
117
64
|
|
|
118
65
|
if (debug) {
|
|
@@ -132,29 +79,49 @@ function findNestedVariables(input, regex, debug = false) {
|
|
|
132
79
|
})
|
|
133
80
|
|
|
134
81
|
// Replace in working string (to find next match)
|
|
135
|
-
|
|
136
|
-
placeholder +
|
|
137
|
-
workingString.substring(match.index + match[0].length)
|
|
82
|
+
current = current.substring(0, match.index) + placeholder + current.substring(match.index + match[0].length)
|
|
138
83
|
|
|
139
|
-
if (debug) console.log(`After replacement: ${
|
|
84
|
+
if (debug) console.log(`After replacement: ${current}`)
|
|
140
85
|
}
|
|
141
86
|
|
|
142
87
|
if (debug) console.log(`\nTotal matches found: ${matches.length}`)
|
|
143
88
|
|
|
144
89
|
// We need to store varString - the variable string with placeholders
|
|
145
90
|
for (let i = 0; i < matches.length; i++) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
matches[i].varString
|
|
91
|
+
matches[i].varString = matches[i].variable
|
|
92
|
+
/* Save additional meta data about the variable */
|
|
93
|
+
// console.log('matches[i].varString', matches[i].varString)
|
|
94
|
+
if (variablesKnownTypes && variablesKnownTypes.test(matches[i].varString)) {
|
|
95
|
+
matches[i].varType = matches[i].varString.match(variablesKnownTypes)[1]
|
|
96
|
+
if (FALLBACK_REGEX.test(matches[i].varString)) {
|
|
97
|
+
const split = splitByComma(matches[i].varString, regex)
|
|
98
|
+
matches[i].hasFallback = true
|
|
99
|
+
|
|
100
|
+
matches[i].valueBeforeFallback = split[0]
|
|
101
|
+
// remove first element from split
|
|
102
|
+
matches[i].fallbackValues = split.slice(1).map((item) => {
|
|
103
|
+
// console.log('item', item)
|
|
104
|
+
const isVariable = variablesKnownTypes.test(item) || VAR_MATCH_REGEX.test(item)
|
|
105
|
+
const fallbackData = {
|
|
106
|
+
isVariable,
|
|
107
|
+
fullMatch: item,
|
|
108
|
+
variable: item
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!isVariable && typeof item === 'string') {
|
|
112
|
+
fallbackData.stringValue = trimQuotes(item)
|
|
113
|
+
fallbackData.isFallback = true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return fallbackData
|
|
117
|
+
})
|
|
118
|
+
}
|
|
152
119
|
}
|
|
153
120
|
}
|
|
154
121
|
|
|
155
122
|
// Second pass: Reconstruct each variable with original nested syntax
|
|
156
123
|
// We need to do this recursively to ensure all placeholders are replaced properly
|
|
157
|
-
function replaceAllPlaceholders(text, matchesArray) {
|
|
124
|
+
function replaceAllPlaceholders(text = '', matchesArray, key = 'fullMatch') {
|
|
158
125
|
let result = text
|
|
159
126
|
let needsAnotherPass = false
|
|
160
127
|
|
|
@@ -162,17 +129,14 @@ function findNestedVariables(input, regex, debug = false) {
|
|
|
162
129
|
for (let i = 0; i < matchesArray.length; i++) {
|
|
163
130
|
const m = matchesArray[i]
|
|
164
131
|
if (result.includes(m.placeholder)) {
|
|
165
|
-
result = result.replace(
|
|
166
|
-
new RegExp(m.placeholder, 'g'),
|
|
167
|
-
m.fullMatch
|
|
168
|
-
)
|
|
132
|
+
result = result.replace(new RegExp(m.placeholder, 'g'), m[key])
|
|
169
133
|
needsAnotherPass = true
|
|
170
134
|
}
|
|
171
135
|
}
|
|
172
136
|
|
|
173
137
|
// If we made replacements, we might need another pass to handle nested placeholders
|
|
174
138
|
if (needsAnotherPass) {
|
|
175
|
-
return replaceAllPlaceholders(result, matchesArray)
|
|
139
|
+
return replaceAllPlaceholders(result, matchesArray, key)
|
|
176
140
|
}
|
|
177
141
|
|
|
178
142
|
return result
|
|
@@ -186,10 +150,19 @@ function findNestedVariables(input, regex, debug = false) {
|
|
|
186
150
|
if (!currentMatch.fullMatch.includes('__VAR_') && !currentMatch.variable.includes('__VAR_')) {
|
|
187
151
|
continue
|
|
188
152
|
}
|
|
153
|
+
|
|
154
|
+
if (currentMatch.hasFallback) {
|
|
155
|
+
currentMatch.fallbackValues.forEach((item) => {
|
|
156
|
+
item.fullMatch = replaceAllPlaceholders(item.fullMatch, matches, 'fullMatch')
|
|
157
|
+
item.variable = replaceAllPlaceholders(item.variable, matches, 'variable')
|
|
158
|
+
})
|
|
159
|
+
}
|
|
189
160
|
|
|
190
161
|
// Reconstruct with all nested variables
|
|
191
162
|
currentMatch.fullMatch = replaceAllPlaceholders(currentMatch.fullMatch, matches)
|
|
192
163
|
currentMatch.variable = replaceAllPlaceholders(currentMatch.variable, matches)
|
|
164
|
+
|
|
165
|
+
|
|
193
166
|
}
|
|
194
167
|
|
|
195
168
|
if (debug) {
|
|
@@ -207,6 +180,67 @@ function findNestedVariables(input, regex, debug = false) {
|
|
|
207
180
|
}
|
|
208
181
|
|
|
209
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Processes nested variable interpolations in a string and collects all matches
|
|
185
|
+
* @param {string} input - The input string containing variable interpolations
|
|
186
|
+
* @param {boolean} debug - Whether to print debug information
|
|
187
|
+
* @returns {Array} Array of match objects containing full match and captured group
|
|
188
|
+
*/
|
|
189
|
+
function findNestedVariablesx(input, regex, variablesKnownTypes, debug = false) {
|
|
190
|
+
let str = input
|
|
191
|
+
let matches = []
|
|
192
|
+
let match
|
|
193
|
+
let iteration = 0
|
|
194
|
+
|
|
195
|
+
console.log('input', input)
|
|
196
|
+
|
|
197
|
+
if (debug) console.log(`Initial string: ${str}`)
|
|
198
|
+
|
|
199
|
+
// Process string until no more matches are found
|
|
200
|
+
while (true) {
|
|
201
|
+
iteration++
|
|
202
|
+
if (debug) console.log(`\nIteration ${iteration}:`)
|
|
203
|
+
|
|
204
|
+
// Reset regex index
|
|
205
|
+
regex.lastIndex = 0
|
|
206
|
+
|
|
207
|
+
// Find the next match
|
|
208
|
+
match = regex.exec(str)
|
|
209
|
+
if (!match) break
|
|
210
|
+
|
|
211
|
+
// Log match details if in debug mode
|
|
212
|
+
if (debug) {
|
|
213
|
+
console.log(`Match: ${match[0]}`)
|
|
214
|
+
console.log(`Captured group: ${match[1]}`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Store the match
|
|
218
|
+
matches.push({
|
|
219
|
+
fullMatch: match[0],
|
|
220
|
+
variable: match[1],
|
|
221
|
+
order: iteration
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Replace the match with placeholder
|
|
225
|
+
str = str.replace(regex, `__REPLACED_${iteration - 1}__`)
|
|
226
|
+
if (debug) console.log(`After replacement: ${str}`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Replace the `__REPLACED_${iteration - 1}__` with the original match
|
|
230
|
+
matches = matches.map((match, index) => {
|
|
231
|
+
const indexOfReplaced = match.fullMatch.match(/__REPLACED_(\d+)__/)
|
|
232
|
+
if (indexOfReplaced) {
|
|
233
|
+
const replacedIndex = parseInt(indexOfReplaced[1])
|
|
234
|
+
match.fullMatch = match.fullMatch.replace(`__REPLACED_${replacedIndex}__`, matches[replacedIndex].variable)
|
|
235
|
+
match.variable = match.variable.replace(`__REPLACED_${replacedIndex}__`, matches[replacedIndex].variable)
|
|
236
|
+
}
|
|
237
|
+
return match
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (debug) console.log(`\nTotal matches found: ${matches.length}`)
|
|
241
|
+
return matches
|
|
242
|
+
}
|
|
243
|
+
|
|
210
244
|
// // Test with the example
|
|
211
245
|
// const regex = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g
|
|
212
246
|
// const input = '${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}'
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
const { test } = require('uvu');
|
|
2
2
|
const assert = require('uvu/assert');
|
|
3
3
|
const { findNestedVariables } = require('./find-nested-variables');
|
|
4
|
+
const deepLog = require('./deep-log')
|
|
4
5
|
|
|
5
6
|
// Define the regex pattern as used in the main function
|
|
6
7
|
const regex = /\${((?!AWS|stageVariables)[ ~:a-zA-Z0-9=+!@#%*<>?._'",|\-\/\(\)\\]+?)}/g;
|
|
8
|
+
const variablesKnownTypes = /(^env:|^opt:|^self:|^file\((~?[\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)|^git:|(\${)?deep:\d+(\.[^}]+)*()}?)/
|
|
7
9
|
|
|
8
10
|
test('findNestedVariables - simple variables', () => {
|
|
9
11
|
const input = '${simple}';
|
|
10
|
-
const result = findNestedVariables(input, regex);
|
|
12
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes, 'key');
|
|
13
|
+
deepLog('result', result)
|
|
11
14
|
|
|
12
15
|
assert.equal(result.length, 1);
|
|
13
16
|
assert.equal(result[0].fullMatch, '${simple}');
|
|
14
17
|
assert.equal(result[0].variable, 'simple');
|
|
15
|
-
assert.equal(result[0].
|
|
18
|
+
assert.equal(result[0].resolveOrder, 1, 'order should be 1');
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
test('findNestedVariables - complex variable with colon syntax', () => {
|
|
19
22
|
const input = '${opt:stage, dev}';
|
|
20
|
-
const result = findNestedVariables(input, regex);
|
|
23
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
21
24
|
|
|
22
25
|
assert.equal(result.length, 1);
|
|
23
26
|
assert.equal(result[0].fullMatch, '${opt:stage, dev}');
|
|
@@ -26,7 +29,7 @@ test('findNestedVariables - complex variable with colon syntax', () => {
|
|
|
26
29
|
|
|
27
30
|
test('findNestedVariables - one level nesting', () => {
|
|
28
31
|
const input = '${file(./config.${stage}.json)}';
|
|
29
|
-
const result = findNestedVariables(input, regex);
|
|
32
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
30
33
|
|
|
31
34
|
assert.equal(result.length, 2);
|
|
32
35
|
// The innermost variable should be found first
|
|
@@ -39,7 +42,7 @@ test('findNestedVariables - one level nesting', () => {
|
|
|
39
42
|
|
|
40
43
|
test('findNestedVariables - two levels of nesting', () => {
|
|
41
44
|
const input = '${file(./config.${opt:stage, ${defaultStage}}.json):CREDS}';
|
|
42
|
-
const result = findNestedVariables(input, regex);
|
|
45
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
43
46
|
|
|
44
47
|
assert.equal(result.length, 3);
|
|
45
48
|
// Innermost first
|
|
@@ -55,7 +58,7 @@ test('findNestedVariables - two levels of nesting', () => {
|
|
|
55
58
|
|
|
56
59
|
test('findNestedVariables - multiple separate variables', () => {
|
|
57
60
|
const input = 'Hello ${name}, welcome to ${service}!';
|
|
58
|
-
const result = findNestedVariables(input, regex);
|
|
61
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
59
62
|
|
|
60
63
|
assert.equal(result.length, 2);
|
|
61
64
|
assert.equal(result[0].fullMatch, '${name}');
|
|
@@ -64,7 +67,7 @@ test('findNestedVariables - multiple separate variables', () => {
|
|
|
64
67
|
|
|
65
68
|
test('findNestedVariables - complex mixed case', () => {
|
|
66
69
|
const input = '${db.${envOne}.host}:${db.${envTwo}.port} using ${credentials.${user.role}}';
|
|
67
|
-
const result = findNestedVariables(input, regex,
|
|
70
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
68
71
|
console.log('result', result)
|
|
69
72
|
assert.equal(result.length, 6);
|
|
70
73
|
// Check the correct nesting order
|
|
@@ -77,23 +80,40 @@ test('findNestedVariables - complex mixed case', () => {
|
|
|
77
80
|
|
|
78
81
|
test('findNestedVariables - empty string', () => {
|
|
79
82
|
const input = '';
|
|
80
|
-
const result = findNestedVariables(input, regex);
|
|
83
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
81
84
|
assert.equal(result.length, 0);
|
|
82
85
|
});
|
|
83
86
|
|
|
84
87
|
test('findNestedVariables - string with no variables', () => {
|
|
85
88
|
const input = 'This is a string with no variables';
|
|
86
|
-
const result = findNestedVariables(input, regex);
|
|
89
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
87
90
|
assert.equal(result.length, 0);
|
|
88
91
|
});
|
|
89
92
|
|
|
90
93
|
test('findNestedVariables - varString property for nested variables', () => {
|
|
91
94
|
const input = '${file(./config.${opt:stage, ${defaultStage}}.json)}';
|
|
92
|
-
const result = findNestedVariables(input, regex);
|
|
93
|
-
|
|
95
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
96
|
+
deepLog('result', result)
|
|
94
97
|
// Check varString property for the outermost variable
|
|
95
98
|
assert.equal(result[2].variable, 'file(./config.${opt:stage, ${defaultStage}}.json)');
|
|
96
99
|
});
|
|
97
100
|
|
|
101
|
+
test('findNestedVariables - mutliple fallback items', () => {
|
|
102
|
+
const input = '${file(./config.${opt:stage, ${opt:stageOne}, ${opt:stageTwo}, "three"}.json)}';
|
|
103
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes);
|
|
104
|
+
deepLog('result', result)
|
|
105
|
+
// Check varString property for the outermost variable
|
|
106
|
+
assert.equal(result[result.length - 1].variable, 'file(./config.${opt:stage, ${opt:stageOne}, ${opt:stageTwo}, "three"}.json)');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test.skip('findNestedVariables - deep', () => {
|
|
110
|
+
const input = '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}';
|
|
111
|
+
const result = findNestedVariables(input, regex, variablesKnownTypes, 'xyz');
|
|
112
|
+
deepLog('result', result)
|
|
113
|
+
// Check varString property for the outermost variable
|
|
114
|
+
assert.equal(result[result.length - 1].variable, 'file(./config.${opt:stage, ${opt:stageOne}, ${opt:stageTwo}, "three"}.json)');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
|
|
98
118
|
// Run all tests
|
|
99
119
|
test.run();
|
package/src/utils/x.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
process.env.foo = 'foo'
|
|
2
|
+
process.env.opt_stage = 'stage'
|
|
3
|
+
process.env.opt_stageOne = 'stageOne'
|
|
4
|
+
process.env.opt_stageTwo = 'stageTwo'
|
|
5
|
+
|
|
6
|
+
function getResolvers() {
|
|
7
|
+
return {
|
|
8
|
+
'file': async function fileResolver(arg) {
|
|
9
|
+
return 'filevalue'
|
|
10
|
+
},
|
|
11
|
+
'env:': async function envResolver(key) {
|
|
12
|
+
return process.env[key]
|
|
13
|
+
},
|
|
14
|
+
'opt:': async function optResolver(key) {
|
|
15
|
+
return process.env[`opt_${key}`] || null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function resolveVariables(variableArray) {
|
|
21
|
+
// Sort by resolveOrder to ensure inner variables are resolved first
|
|
22
|
+
const sortedArray = [...variableArray].sort((a, b) => a.resolveOrder - b.resolveOrder)
|
|
23
|
+
const resolvedValues = {}
|
|
24
|
+
const resolvers = getResolvers()
|
|
25
|
+
|
|
26
|
+
for (const item of sortedArray) {
|
|
27
|
+
let resolvedValue = null
|
|
28
|
+
|
|
29
|
+
if (item.hasFallback) {
|
|
30
|
+
// Try to resolve the primary value
|
|
31
|
+
const resolver = resolvers[item.varType]
|
|
32
|
+
const primaryKey = item.valueBeforeFallback.replace(`${item.varType}`, '')
|
|
33
|
+
resolvedValue = await resolver(primaryKey)
|
|
34
|
+
|
|
35
|
+
// If primary value is null, try fallbacks in order
|
|
36
|
+
if (resolvedValue === null) {
|
|
37
|
+
for (const fallback of item.fallbackValues) {
|
|
38
|
+
if (fallback.isVariable) {
|
|
39
|
+
// This is a reference to another variable that should be already resolved
|
|
40
|
+
const placeholderMatch = fallback.variable.match(/__VAR_(\d+)__/)
|
|
41
|
+
if (placeholderMatch) {
|
|
42
|
+
resolvedValue = resolvedValues[`__VAR_${placeholderMatch[1]}__`]
|
|
43
|
+
if (resolvedValue !== null) break
|
|
44
|
+
} else {
|
|
45
|
+
// It's a direct variable reference
|
|
46
|
+
const varType = fallback.variable.split(':')[0] + ':'
|
|
47
|
+
const varKey = fallback.variable.replace(`${varType}`, '')
|
|
48
|
+
resolvedValue = await resolvers[varType](varKey)
|
|
49
|
+
if (resolvedValue !== null) break
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// It's a literal value
|
|
53
|
+
resolvedValue = fallback.variable.replace(/"/g, '')
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} else if (item.varType.startsWith('file')) {
|
|
59
|
+
// Handle file type specially as it requires the resolved path
|
|
60
|
+
let filePath = item.varString
|
|
61
|
+
for (const [placeholder, value] of Object.entries(resolvedValues)) {
|
|
62
|
+
filePath = filePath.replace(placeholder, value)
|
|
63
|
+
}
|
|
64
|
+
resolvedValue = await resolvers['file'](filePath)
|
|
65
|
+
} else {
|
|
66
|
+
// Simple variable resolution
|
|
67
|
+
const resolver = resolvers[item.varType]
|
|
68
|
+
const key = item.variable.replace(`${item.varType}`, '')
|
|
69
|
+
resolvedValue = await resolver(key)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
resolvedValues[item.placeholder] = resolvedValue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return resolvedValues
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
const array = [
|
|
80
|
+
{
|
|
81
|
+
location: 'xyz',
|
|
82
|
+
value: '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}',
|
|
83
|
+
fullMatch: '${env:foo}',
|
|
84
|
+
variable: 'env:foo',
|
|
85
|
+
varString: 'env:foo',
|
|
86
|
+
resolveOrder: 1,
|
|
87
|
+
start: 45,
|
|
88
|
+
end: 55,
|
|
89
|
+
placeholder: '__VAR_0__',
|
|
90
|
+
varType: 'env:'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
location: 'xyz',
|
|
94
|
+
value: '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}',
|
|
95
|
+
fullMatch: '${opt:stageOne, ${env:foo}}',
|
|
96
|
+
variable: 'opt:stageOne, ${env:foo}',
|
|
97
|
+
varString: 'opt:stageOne, __VAR_0__',
|
|
98
|
+
resolveOrder: 2,
|
|
99
|
+
start: 29,
|
|
100
|
+
end: 55,
|
|
101
|
+
placeholder: '__VAR_1__',
|
|
102
|
+
varType: 'opt:',
|
|
103
|
+
hasFallback: true,
|
|
104
|
+
valueBeforeFallback: 'opt:stageOne',
|
|
105
|
+
fallbackValues: [
|
|
106
|
+
{
|
|
107
|
+
isVariable: true,
|
|
108
|
+
variable: 'env:foo',
|
|
109
|
+
fullMatch: '${env:foo}'
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
location: 'xyz',
|
|
115
|
+
value: '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}',
|
|
116
|
+
fullMatch: '${opt:stageTwo}',
|
|
117
|
+
variable: 'opt:stageTwo',
|
|
118
|
+
varString: 'opt:stageTwo',
|
|
119
|
+
resolveOrder: 3,
|
|
120
|
+
start: 40,
|
|
121
|
+
end: 55,
|
|
122
|
+
placeholder: '__VAR_2__',
|
|
123
|
+
varType: 'opt:'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
location: 'xyz',
|
|
127
|
+
value: '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}',
|
|
128
|
+
fullMatch: '${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }',
|
|
129
|
+
variable: 'opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three"',
|
|
130
|
+
varString: 'opt:stage, __VAR_1__, __VAR_2__, "three"',
|
|
131
|
+
resolveOrder: 4,
|
|
132
|
+
start: 16,
|
|
133
|
+
end: 60,
|
|
134
|
+
placeholder: '__VAR_3__',
|
|
135
|
+
varType: 'opt:',
|
|
136
|
+
hasFallback: true,
|
|
137
|
+
valueBeforeFallback: 'opt:stage',
|
|
138
|
+
fallbackValues: [
|
|
139
|
+
{
|
|
140
|
+
isVariable: true,
|
|
141
|
+
variable: 'opt:stageOne, ${env:foo}',
|
|
142
|
+
fullMatch: '${opt:stageOne, ${env:foo}}'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
isVariable: true,
|
|
146
|
+
variable: 'opt:stageTwo',
|
|
147
|
+
fullMatch: '${opt:stageTwo}'
|
|
148
|
+
},
|
|
149
|
+
{ isVariable: false, variable: '"three"', fullMatch: '"three"' }
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
location: 'xyz',
|
|
154
|
+
value: '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}',
|
|
155
|
+
fullMatch: '${file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)}',
|
|
156
|
+
variable: 'file(./config.${opt:stage, ${opt:stageOne, ${env:foo}}, ${opt:stageTwo}, "three" }.json)',
|
|
157
|
+
varString: 'file(./config.__VAR_3__.json)',
|
|
158
|
+
resolveOrder: 5,
|
|
159
|
+
start: 0,
|
|
160
|
+
end: 32,
|
|
161
|
+
placeholder: '__VAR_4__',
|
|
162
|
+
varType: 'file(./config.__VAR_3__.json)'
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
resolveVariables(array)
|
|
168
|
+
.then((res) => {
|
|
169
|
+
console.log('res', res)
|
|
170
|
+
})
|
|
171
|
+
.catch((err) => {
|
|
172
|
+
console.log('err', err)
|
|
173
|
+
})
|