configorama 0.6.11 → 0.6.13
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/README.md +196 -24
- package/cli.js +3 -3
- package/package.json +1 -1
- package/src/index.js +22 -32
- package/src/main.js +775 -857
- package/src/parsers/yaml.js +3 -47
- package/src/resolvers/valueFromCron.js +3 -1
- package/src/resolvers/valueFromEnv.js +1 -0
- package/src/resolvers/valueFromEval.js +1 -0
- package/src/resolvers/valueFromFile.js +394 -0
- package/src/resolvers/valueFromGit.js +3 -2
- package/src/resolvers/valueFromOptions.js +1 -0
- package/src/resolvers/valueFromString.js +2 -1
- package/src/sync.js +12 -5
- package/src/utils/parsing/arrayToJsonPath.test.js +56 -0
- package/src/utils/{enrichMetadata.js → parsing/enrichMetadata.js} +244 -94
- package/src/utils/{parse.js → parsing/parse.js} +13 -13
- package/src/utils/parsing/preProcess.js +165 -0
- package/src/utils/paths/filePathUtils.js +136 -0
- package/src/utils/paths/filePathUtils.test.js +214 -0
- package/src/utils/paths/findLineForKey.js +47 -0
- package/src/utils/paths/findLineForKey.test.js +126 -0
- package/src/utils/{getFullFilePath.js → paths/getFullFilePath.js} +22 -26
- package/src/utils/{resolveAlias.js → paths/resolveAlias.js} +1 -1
- package/src/utils/regex/index.js +23 -1
- package/src/utils/resolution/preResolveVariable.js +260 -0
- package/src/utils/resolution/preResolveVariable.test.js +98 -0
- package/src/utils/strings/bracketMatcher.js +86 -0
- package/src/utils/strings/bracketMatcher.test.js +135 -0
- package/src/utils/{formatFunctionArgs.js → strings/formatFunctionArgs.js} +3 -2
- package/src/utils/strings/formatFunctionArgs.test.js +77 -0
- package/src/utils/strings/quoteUtils.js +89 -0
- package/src/utils/strings/quoteUtils.test.js +217 -0
- package/src/utils/strings/replaceAll.test.js +82 -0
- package/src/utils/{splitByComma.js → strings/splitByComma.js} +1 -1
- package/src/utils/strings/splitCsv.js +38 -0
- package/src/utils/strings/splitCsv.test.js +96 -0
- package/src/utils/strings/textUtils.test.js +86 -0
- package/src/utils/{configWizard.js → ui/configWizard.js} +212 -60
- package/src/utils/{createEditorLink.js → ui/createEditorLink.js} +11 -2
- package/src/utils/{logs.js → ui/logs.js} +3 -3
- package/src/utils/validation/isValidValue.test.js +64 -0
- package/src/utils/validation/warnIfNotFound.js +52 -0
- package/src/utils/variables/appendDeepVariable.test.js +41 -0
- package/src/utils/{cleanVariable.js → variables/cleanVariable.js} +5 -26
- package/src/utils/{find-nested-variables.js → variables/findNestedVariables.js} +2 -2
- package/src/utils/{find-nested-variables.test.js → variables/findNestedVariables.test.js} +5 -5
- package/src/utils/variables/getVariableType.test.js +109 -0
- package/src/utils/variables/variableUtils.test.js +117 -0
- package/src/utils/isValidValue.js +0 -8
- package/src/utils/splitCsv.js +0 -29
- package/src/utils/trimSurroundingQuotes.js +0 -5
- /package/src/utils/{arrayToJsonPath.js → parsing/arrayToJsonPath.js} +0 -0
- /package/src/utils/{cloudformationSchema.js → parsing/cloudformationSchema.js} +0 -0
- /package/src/utils/{mergeByKeys.js → parsing/mergeByKeys.js} +0 -0
- /package/src/utils/{find-project-root.js → paths/findProjectRoot.js} +0 -0
- /package/src/utils/{resolveAlias.test.js → paths/resolveAlias.test.js} +0 -0
- /package/src/utils/{replaceAll.js → strings/replaceAll.js} +0 -0
- /package/src/utils/{splitByComma.test.js → strings/splitByComma.test.js} +0 -0
- /package/src/utils/{textUtils.js → strings/textUtils.js} +0 -0
- /package/src/utils/{chalk.js → ui/chalk.js} +0 -0
- /package/src/utils/{deep-log.js → ui/deep-log.js} +0 -0
- /package/src/utils/{appendDeepVariable.js → variables/appendDeepVariable.js} +0 -0
- /package/src/utils/{cleanVariable.test.js → variables/cleanVariable.test.js} +0 -0
- /package/src/utils/{getVariableType.js → variables/getVariableType.js} +0 -0
- /package/src/utils/{variableUtils.js → variables/variableUtils.js} +0 -0
package/src/main.js
CHANGED
|
@@ -1,23 +1,76 @@
|
|
|
1
|
+
/* Node built-ins */
|
|
1
2
|
const os = require('os')
|
|
2
3
|
const path = require('path')
|
|
3
4
|
const fs = require('fs')
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
/* // disable logs to find broken tests
|
|
6
7
|
console.log = () => {}
|
|
7
8
|
// process.exit(1)
|
|
8
9
|
/** */
|
|
9
10
|
|
|
11
|
+
/* External dependencies */
|
|
10
12
|
const promiseFinallyShim = require('promise.prototype.finally').shim()
|
|
11
|
-
// @TODO only import lodash we need
|
|
12
|
-
|
|
13
13
|
const findUp = require('find-up')
|
|
14
14
|
const traverse = require('traverse')
|
|
15
15
|
const dotProp = require('dot-prop')
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
|
|
17
|
+
|
|
18
|
+
/* Utils - root */
|
|
19
|
+
const {
|
|
20
|
+
isArray, isString, isNumber, isObject, isDate, isRegExp, isFunction,
|
|
21
|
+
isEmpty, trim, camelCase, kebabCase, capitalize, split, map, mapValues,
|
|
22
|
+
assign, set, cloneDeep
|
|
23
|
+
} = require('./utils/lodash')
|
|
24
|
+
const PromiseTracker = require('./utils/PromiseTracker')
|
|
25
|
+
const handleSignalEvents = require('./utils/handleSignalEvents')
|
|
19
26
|
|
|
20
|
-
/*
|
|
27
|
+
/* Utils - encoders */
|
|
28
|
+
const { encodeUnknown, decodeUnknown } = require('./utils/encoders/unknown-values')
|
|
29
|
+
const { decodeEncodedValue } = require('./utils/encoders')
|
|
30
|
+
const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder } = require('./utils/encoders/js-fixes')
|
|
31
|
+
|
|
32
|
+
/* Utils - parsing */
|
|
33
|
+
const enrichMetadata = require('./utils/parsing/enrichMetadata')
|
|
34
|
+
const preProcess = require('./utils/parsing/preProcess')
|
|
35
|
+
const { parseFileContents } = require('./utils/parsing/parse')
|
|
36
|
+
const { mergeByKeys } = require('./utils/parsing/mergeByKeys')
|
|
37
|
+
const { arrayToJsonPath } = require('./utils/parsing/arrayToJsonPath')
|
|
38
|
+
|
|
39
|
+
/* Utils - paths */
|
|
40
|
+
const { normalizePath, extractFilePath, resolveInnerVariables } = require('./utils/paths/filePathUtils')
|
|
41
|
+
const { resolveAlias } = require('./utils/paths/resolveAlias')
|
|
42
|
+
const { resolveFilePathFromMatch } = require('./utils/paths/getFullFilePath')
|
|
43
|
+
const { findLineForKey } = require('./utils/paths/findLineForKey')
|
|
44
|
+
|
|
45
|
+
/* Utils - regex */
|
|
46
|
+
const { combineRegexes, funcRegex, funcStartOfLineRegex, subFunctionRegex } = require('./utils/regex')
|
|
47
|
+
|
|
48
|
+
/* Utils - strings */
|
|
49
|
+
const formatFunctionArgs = require('./utils/strings/formatFunctionArgs')
|
|
50
|
+
|
|
51
|
+
const { splitByComma } = require('./utils/strings/splitByComma')
|
|
52
|
+
const { splitCsv } = require('./utils/strings/splitCsv')
|
|
53
|
+
const { replaceAll } = require('./utils/strings/replaceAll')
|
|
54
|
+
const { getTextAfterOccurrence, findNestedVariable } = require('./utils/strings/textUtils')
|
|
55
|
+
const { trimSurroundingQuotes, ensureQuote, isSurroundedByQuotes, startsWithQuotedPipe } = require('./utils/strings/quoteUtils')
|
|
56
|
+
|
|
57
|
+
/* Utils - ui */
|
|
58
|
+
const chalk = require('./utils/ui/chalk')
|
|
59
|
+
const deepLog = require('./utils/ui/deep-log')
|
|
60
|
+
const { logHeader } = require('./utils/ui/logs')
|
|
61
|
+
const { createEditorLink } = require('./utils/ui/createEditorLink')
|
|
62
|
+
const { runConfigWizard, isSensitiveVariable } = require('./utils/ui/configWizard')
|
|
63
|
+
|
|
64
|
+
/* Utils - validation */
|
|
65
|
+
const { warnIfNotFound, isValidValue } = require('./utils/validation/warnIfNotFound')
|
|
66
|
+
|
|
67
|
+
/* Utils - variables */
|
|
68
|
+
const cleanVariable = require('./utils/variables/cleanVariable')
|
|
69
|
+
const appendDeepVariable = require('./utils/variables/appendDeepVariable')
|
|
70
|
+
const { getFallbackString, verifyVariable } = require('./utils/variables/variableUtils')
|
|
71
|
+
const { findNestedVariables } = require('./utils/variables/findNestedVariables')
|
|
72
|
+
|
|
73
|
+
/* Resolvers */
|
|
21
74
|
const getValueFromString = require('./resolvers/valueFromString')
|
|
22
75
|
const getValueFromNumber = require('./resolvers/valueFromNumber')
|
|
23
76
|
const getValueFromEnv = require('./resolvers/valueFromEnv')
|
|
@@ -25,44 +78,16 @@ const getValueFromOptions = require('./resolvers/valueFromOptions')
|
|
|
25
78
|
const getValueFromCron = require('./resolvers/valueFromCron')
|
|
26
79
|
const getValueFromEval = require('./resolvers/valueFromEval')
|
|
27
80
|
const createGitResolver = require('./resolvers/valueFromGit')
|
|
28
|
-
|
|
81
|
+
const { getValueFromFile: getValueFromFileResolver } = require('./resolvers/valueFromFile')
|
|
82
|
+
|
|
83
|
+
/* Parsers */
|
|
29
84
|
const YAML = require('./parsers/yaml')
|
|
30
85
|
const TOML = require('./parsers/toml')
|
|
31
86
|
const INI = require('./parsers/ini')
|
|
32
87
|
const JSON5 = require('./parsers/json5')
|
|
33
|
-
/* functions */
|
|
34
|
-
const md5Function = require('./functions/md5')
|
|
35
88
|
|
|
36
|
-
/*
|
|
37
|
-
const
|
|
38
|
-
const appendDeepVariable = require('./utils/appendDeepVariable')
|
|
39
|
-
const isValidValue = require('./utils/isValidValue')
|
|
40
|
-
const PromiseTracker = require('./utils/PromiseTracker')
|
|
41
|
-
const handleSignalEvents = require('./utils/handleSignalEvents')
|
|
42
|
-
const formatFunctionArgs = require('./utils/formatFunctionArgs')
|
|
43
|
-
const trimSurroundingQuotes = require('./utils/trimSurroundingQuotes')
|
|
44
|
-
const deepLog = require('./utils/deep-log')
|
|
45
|
-
const { splitByComma } = require('./utils/splitByComma')
|
|
46
|
-
const {
|
|
47
|
-
isArray, isString, isNumber, isObject, isDate, isRegExp, isFunction,
|
|
48
|
-
isEmpty, trim, camelCase, kebabCase, capitalize, split, map, mapValues,
|
|
49
|
-
assign, set, cloneDeep
|
|
50
|
-
} = require('./utils/lodash')
|
|
51
|
-
const { parseFileContents } = require('./utils/parse')
|
|
52
|
-
const { splitCsv } = require('./utils/splitCsv')
|
|
53
|
-
const { replaceAll } = require('./utils/replaceAll')
|
|
54
|
-
const { getTextAfterOccurrence, findNestedVariable } = require('./utils/textUtils')
|
|
55
|
-
const { getFallbackString, verifyVariable } = require('./utils/variableUtils')
|
|
56
|
-
const { encodeUnknown, decodeUnknown } = require('./utils/encoders/unknown-values')
|
|
57
|
-
const { decodeEncodedValue } = require('./utils/encoders')
|
|
58
|
-
const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder } = require('./utils/encoders/js-fixes')
|
|
59
|
-
const { mergeByKeys } = require('./utils/mergeByKeys')
|
|
60
|
-
const { arrayToJsonPath } = require('./utils/arrayToJsonPath')
|
|
61
|
-
const { findNestedVariables } = require('./utils/find-nested-variables')
|
|
62
|
-
const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
|
|
63
|
-
const { logHeader } = require('./utils/logs')
|
|
64
|
-
const { createEditorLink } = require('./utils/createEditorLink')
|
|
65
|
-
const { runConfigWizard } = require('./utils/configWizard')
|
|
89
|
+
/* Functions */
|
|
90
|
+
const md5Function = require('./functions/md5')
|
|
66
91
|
/**
|
|
67
92
|
* Maintainer's notes:
|
|
68
93
|
*
|
|
@@ -88,9 +113,6 @@ const textRefSyntax = RegExp(/^text\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
|
88
113
|
const envRefSyntax = RegExp(/^env:/g)
|
|
89
114
|
const optRefSyntax = RegExp(/^opt:/g)
|
|
90
115
|
const selfRefSyntax = RegExp(/^self:/g)
|
|
91
|
-
const funcRegex = /(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
92
|
-
const funcStartOfLineRegex = /^(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
93
|
-
const subFunctionRegex = /(\w+):(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
94
116
|
const base64WrapperRegex = /\[_\[([A-Za-z0-9+/=\s]*)\]_\]/g
|
|
95
117
|
const logLines = '─────────────────────────────────────────────────'
|
|
96
118
|
|
|
@@ -101,147 +123,6 @@ let SETUP_MODE = process.argv.includes('--setup') ? true : false
|
|
|
101
123
|
let DEBUG_TYPE = false
|
|
102
124
|
const ENABLE_FUNCTIONS = true
|
|
103
125
|
|
|
104
|
-
function combineRegexes(regexes) {
|
|
105
|
-
// Extract the pattern from each RegExp and join with OR operator
|
|
106
|
-
const patterns = regexes.map(regex => {
|
|
107
|
-
// Get source pattern string without flags
|
|
108
|
-
return regex.source
|
|
109
|
-
}).filter(Boolean)
|
|
110
|
-
// Join patterns with the OR operator and create new RegExp
|
|
111
|
-
return new RegExp(`(${patterns.join('|')})`)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Preprocess config to fix malformed fallback references
|
|
116
|
-
* @param {Object} configObject - The parsed configuration object
|
|
117
|
-
* @param {RegExp} variableSyntax - The variable syntax regex to use
|
|
118
|
-
* @returns {Object} The preprocessed configuration object
|
|
119
|
-
*/
|
|
120
|
-
function preProcess(configObject, variableSyntax) {
|
|
121
|
-
// Known reference prefixes that should be wrapped in ${}
|
|
122
|
-
const refPrefixes = ['self:', 'opt:', 'env:', 'file:', 'text:', 'deep:']
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Fix malformed fallback references in a string
|
|
126
|
-
* @param {string} str - String potentially containing variables
|
|
127
|
-
* @returns {string} String with fixed fallback references
|
|
128
|
-
*/
|
|
129
|
-
function fixFallbacksInString(str) {
|
|
130
|
-
if (typeof str !== 'string') return str
|
|
131
|
-
|
|
132
|
-
let result = str
|
|
133
|
-
// result = result.replace(/\$\{self:/g, '${')
|
|
134
|
-
let changed = true
|
|
135
|
-
|
|
136
|
-
// Keep iterating until no more changes (to handle nested variables)
|
|
137
|
-
while (changed) {
|
|
138
|
-
changed = false
|
|
139
|
-
|
|
140
|
-
// Find innermost ${...} blocks (ones that don't contain other ${)
|
|
141
|
-
let i = 0
|
|
142
|
-
while (i < result.length) {
|
|
143
|
-
if (result[i] === '$' && result[i + 1] === '{') {
|
|
144
|
-
const start = i
|
|
145
|
-
let braceCount = 1
|
|
146
|
-
let j = i + 2
|
|
147
|
-
|
|
148
|
-
// Find the matching closing brace by counting { and }
|
|
149
|
-
while (j < result.length && braceCount > 0) {
|
|
150
|
-
if (result[j] === '{') {
|
|
151
|
-
braceCount++
|
|
152
|
-
} else if (result[j] === '}') {
|
|
153
|
-
braceCount--
|
|
154
|
-
}
|
|
155
|
-
j++
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (braceCount === 0) {
|
|
159
|
-
const end = j
|
|
160
|
-
const match = result.substring(start, end)
|
|
161
|
-
const content = result.substring(start + 2, end - 1)
|
|
162
|
-
|
|
163
|
-
// Only process if there's a comma (indicating fallback syntax)
|
|
164
|
-
if (content.includes(',')) {
|
|
165
|
-
// Split by comma
|
|
166
|
-
const parts = splitByComma(content, variableSyntax)
|
|
167
|
-
|
|
168
|
-
if (parts.length > 1) {
|
|
169
|
-
// Check if the first part has nested ${} - if so, skip this (process inner ones first)
|
|
170
|
-
const firstPart = parts[0]
|
|
171
|
-
if (firstPart.includes('${')) {
|
|
172
|
-
i = start + 2 // Move past ${ to find inner variables
|
|
173
|
-
continue
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Check each part after the first (these are fallback values)
|
|
177
|
-
const fixed = parts.map((part, index) => {
|
|
178
|
-
if (index === 0) {
|
|
179
|
-
return part // Keep the main reference as-is
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const trimmed = part.trim()
|
|
183
|
-
|
|
184
|
-
// Check if this looks like a reference but is not wrapped
|
|
185
|
-
const looksLikeRef = refPrefixes.some(prefix => trimmed.startsWith(prefix))
|
|
186
|
-
const alreadyWrapped = trimmed.startsWith('${') && trimmed.endsWith('}')
|
|
187
|
-
|
|
188
|
-
if (looksLikeRef && !alreadyWrapped) {
|
|
189
|
-
return ` \${${trimmed}}`
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return ` ${trimmed}`
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const replacement = `\${${fixed.join(',')}}`
|
|
196
|
-
if (replacement !== match) {
|
|
197
|
-
result = result.substring(0, start) + replacement + result.substring(end)
|
|
198
|
-
changed = true
|
|
199
|
-
break // Restart search from beginning
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
i = start + 2 // Move past ${ to continue searching for nested variables
|
|
205
|
-
} else {
|
|
206
|
-
i++
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
i++
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return result
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Recursively traverse and fix the config object
|
|
219
|
-
*/
|
|
220
|
-
function traverseAndFix(obj) {
|
|
221
|
-
if (typeof obj === 'string') {
|
|
222
|
-
return fixFallbacksInString(obj)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (Array.isArray(obj)) {
|
|
226
|
-
return obj.map(item => traverseAndFix(item))
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (obj !== null && typeof obj === 'object') {
|
|
230
|
-
const result = {}
|
|
231
|
-
for (const key in obj) {
|
|
232
|
-
if (obj.hasOwnProperty(key)) {
|
|
233
|
-
result[key] = traverseAndFix(obj[key])
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return result
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return obj
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return traverseAndFix(configObject)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
126
|
class Configorama {
|
|
246
127
|
constructor(fileOrObject, opts) {
|
|
247
128
|
/* attach sig events on async calls */
|
|
@@ -253,17 +134,24 @@ class Configorama {
|
|
|
253
134
|
// Set opts to pass into JS file calls
|
|
254
135
|
this.opts = Object.assign({}, {
|
|
255
136
|
// Allow for unknown variable syntax to pass through without throwing errors
|
|
256
|
-
|
|
137
|
+
allowUnknownVariables: false,
|
|
257
138
|
// Allow undefined to be an end result.
|
|
258
139
|
allowUndefinedValues: false,
|
|
259
140
|
// Allow unknown file refs to pass through without throwing errors
|
|
260
141
|
allowUnknownFileRefs: false,
|
|
142
|
+
// Allow known variable types that can't be resolved to pass through
|
|
143
|
+
allowUnresolvedVariables: false,
|
|
261
144
|
// Return metadata
|
|
262
145
|
returnMetadata: false,
|
|
263
146
|
// Return preResolvedVariableDetails
|
|
264
147
|
returnPreResolvedVariableDetails: false,
|
|
265
148
|
}, options)
|
|
266
149
|
|
|
150
|
+
// Backward compat: allowUnknownVars -> allowUnknownVariables
|
|
151
|
+
if (options.allowUnknownVars !== undefined && options.allowUnknownVariables === undefined) {
|
|
152
|
+
this.opts.allowUnknownVariables = options.allowUnknownVars
|
|
153
|
+
}
|
|
154
|
+
|
|
267
155
|
this.filterCache = {}
|
|
268
156
|
|
|
269
157
|
this.foundVariables = []
|
|
@@ -357,6 +245,7 @@ class Configorama {
|
|
|
357
245
|
*/
|
|
358
246
|
{
|
|
359
247
|
type: 'self',
|
|
248
|
+
source: 'config',
|
|
360
249
|
prefix: 'self',
|
|
361
250
|
syntax: '${self:pathToKeyInConfig}',
|
|
362
251
|
description: `Resolves values from the current config object. Supports sub-properties via :key lookup.`,
|
|
@@ -373,9 +262,10 @@ class Configorama {
|
|
|
373
262
|
*/
|
|
374
263
|
{
|
|
375
264
|
type: 'file',
|
|
265
|
+
source: 'config',
|
|
376
266
|
prefix: 'file',
|
|
377
267
|
syntax: '${file(pathToFile.json)}',
|
|
378
|
-
description: `Resolves values from files. Supports sub-properties via :key lookup.`,
|
|
268
|
+
description: `Resolves values from files. Supports sub-properties via :key or .key lookup.`,
|
|
379
269
|
match: fileRefSyntax,
|
|
380
270
|
resolver: (varString, o, x, pathValue) => {
|
|
381
271
|
return this.getValueFromFile(varString, { context: pathValue })
|
|
@@ -385,6 +275,7 @@ class Configorama {
|
|
|
385
275
|
|
|
386
276
|
{
|
|
387
277
|
type: 'text',
|
|
278
|
+
source: 'config',
|
|
388
279
|
prefix: 'text',
|
|
389
280
|
match: textRefSyntax,
|
|
390
281
|
resolver: (varString, o, x, pathValue) => {
|
|
@@ -420,6 +311,7 @@ class Configorama {
|
|
|
420
311
|
/* Nicer self: references. Match key in object */
|
|
421
312
|
const fallThroughSelfMatcher = {
|
|
422
313
|
type: 'dot.prop',
|
|
314
|
+
source: 'config',
|
|
423
315
|
match: (varString, fullObject, valueObject) => {
|
|
424
316
|
/*
|
|
425
317
|
console.log('fallThroughSelfMatcher varString', varString)
|
|
@@ -541,8 +433,8 @@ class Configorama {
|
|
|
541
433
|
Boolean: (value) => {
|
|
542
434
|
if (typeof value === 'boolean') return value
|
|
543
435
|
const v = String(value).toLowerCase()
|
|
544
|
-
if (['true', '1', 'yes', 'on'].includes(v)) return true
|
|
545
|
-
if (['false', '0', 'no', 'off'].includes(v)) return false
|
|
436
|
+
if (['true', '1', 'yes', 'on', 'enabled'].includes(v)) return true
|
|
437
|
+
if (['false', '0', 'no', 'off', 'disabled'].includes(v)) return false
|
|
546
438
|
throw new Error(`Configorama Error: Expected Boolean, got "${value}"`)
|
|
547
439
|
},
|
|
548
440
|
String: (value) => {
|
|
@@ -572,7 +464,9 @@ class Configorama {
|
|
|
572
464
|
// (\|\s*(toUpperCase|toLowerCase|toCamelCase|toKebabCase|capitalize)\s*)+$
|
|
573
465
|
// Updated to support function-style filters like help('text') with nested parens
|
|
574
466
|
// Use a more permissive pattern that matches anything between parens including nested parens
|
|
575
|
-
this.filterMatch = new RegExp(
|
|
467
|
+
this.filterMatch = new RegExp(
|
|
468
|
+
`(\\|\\s*(${Object.keys(this.filters).join('|')})(?:\\s*\\([^)]*(?:\\([^)]*\\))?[^)]*\\))?\\s*)+}?$`
|
|
469
|
+
)
|
|
576
470
|
// console.log('this.filterMatch', this.filterMatch)
|
|
577
471
|
|
|
578
472
|
this.functions = {
|
|
@@ -637,15 +531,9 @@ class Configorama {
|
|
|
637
531
|
this.callCount = 0
|
|
638
532
|
}
|
|
639
533
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return func().finally(() => {
|
|
644
|
-
this.tracker.stop()
|
|
645
|
-
this.deep = []
|
|
646
|
-
})
|
|
647
|
-
}
|
|
648
|
-
|
|
534
|
+
// ################
|
|
535
|
+
// ## PUBLIC API ##
|
|
536
|
+
// ################
|
|
649
537
|
/**
|
|
650
538
|
* Populate all variables in the service, conveniently remove and restore the service attributes
|
|
651
539
|
* that confuse the population methods.
|
|
@@ -657,6 +545,7 @@ class Configorama {
|
|
|
657
545
|
const configoramaOpts = this.opts
|
|
658
546
|
|
|
659
547
|
const showFoundVariables = configoramaOpts && configoramaOpts.dynamicArgs && (configoramaOpts.dynamicArgs.list || configoramaOpts.dynamicArgs.info)
|
|
548
|
+
|
|
660
549
|
|
|
661
550
|
// If we have a file path but no config yet, parse it now
|
|
662
551
|
if (this.configFilePath && !this.config) {
|
|
@@ -668,13 +557,16 @@ class Configorama {
|
|
|
668
557
|
this.opts
|
|
669
558
|
)
|
|
670
559
|
this.configFileContents = ''
|
|
671
|
-
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails) {
|
|
560
|
+
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails || SETUP_MODE) {
|
|
672
561
|
this.configFileContents = fs.readFileSync(this.configFilePath, 'utf8')
|
|
673
562
|
}
|
|
674
563
|
/*
|
|
675
564
|
console.log('before preprocess', configObject)
|
|
676
565
|
/** */
|
|
677
|
-
|
|
566
|
+
// Store truly raw config before any preprocessing (for metadata display)
|
|
567
|
+
this.rawOriginalConfig = cloneDeep(configObject)
|
|
568
|
+
|
|
569
|
+
/* Preprocess step here - escapes ${} in help() args, fixes malformed fallbacks */
|
|
678
570
|
configObject = preProcess(configObject, this.variableSyntax)
|
|
679
571
|
/*
|
|
680
572
|
console.log('after preprocess', configObject)
|
|
@@ -695,22 +587,24 @@ class Configorama {
|
|
|
695
587
|
const variableSyntax = this.variableSyntax
|
|
696
588
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
697
589
|
|
|
698
|
-
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails) {
|
|
699
|
-
// Use collectVariableMetadata to get variable info (DRY - don't duplicate logic)
|
|
590
|
+
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails || SETUP_MODE) {
|
|
700
591
|
const metadata = this.collectVariableMetadata()
|
|
701
592
|
|
|
702
|
-
const enrich = enrichMetadata(
|
|
593
|
+
const enrich = await enrichMetadata(
|
|
703
594
|
metadata,
|
|
704
595
|
this.resolutionTracking,
|
|
705
596
|
this.variableSyntax,
|
|
706
597
|
this.fileRefsFound,
|
|
707
598
|
this.originalConfig,
|
|
708
599
|
this.configFilePath,
|
|
709
|
-
Object.keys(this.filters)
|
|
600
|
+
Object.keys(this.filters),
|
|
601
|
+
undefined, // resolvedConfig not available yet
|
|
602
|
+
this.opts.options,
|
|
603
|
+
this.variableTypes
|
|
710
604
|
)
|
|
711
605
|
|
|
712
606
|
if (showFoundVariables) {
|
|
713
|
-
|
|
607
|
+
//*
|
|
714
608
|
deepLog('metadata', metadata)
|
|
715
609
|
fs.writeFileSync(`metadata-${path.basename(this.configFilePath)}.json`, JSON.stringify(metadata, null, 2))
|
|
716
610
|
deepLog('enrich', enrich)
|
|
@@ -724,7 +618,10 @@ class Configorama {
|
|
|
724
618
|
const uniqueVarKeys = Object.keys(uniqueVariables)
|
|
725
619
|
|
|
726
620
|
if (this.opts.returnPreResolvedVariableDetails) {
|
|
727
|
-
return
|
|
621
|
+
return Object.assign({}, {
|
|
622
|
+
resolved: false,
|
|
623
|
+
originalConfig: this.originalConfig
|
|
624
|
+
}, enrich)
|
|
728
625
|
}
|
|
729
626
|
|
|
730
627
|
if (!varKeys.length) {
|
|
@@ -750,9 +647,16 @@ class Configorama {
|
|
|
750
647
|
console.log()
|
|
751
648
|
}
|
|
752
649
|
|
|
650
|
+
const lines = this.configFileContents ? this.configFileContents.split('\n') : []
|
|
651
|
+
const fileType = this.configFileType
|
|
652
|
+
const configFilePath = this.configFilePath
|
|
653
|
+
|
|
753
654
|
if (varKeys.length > 0) {
|
|
754
655
|
const fileName = this.configFilePath ? ` in ${this.configFilePath}` : ''
|
|
755
656
|
|
|
657
|
+
// Extract base variable name from varMatch key (e.g., '${env:FOO, default}' -> 'env:FOO')
|
|
658
|
+
const getBaseVarName = (key) => key.replace(/^\$\{/, '').replace(/\}$/, '').split(',')[0].trim()
|
|
659
|
+
|
|
756
660
|
logHeader(`Found ${varKeys.length} Variables${fileName}`)
|
|
757
661
|
|
|
758
662
|
// deepLog('variableData', variableData)
|
|
@@ -765,9 +669,7 @@ class Configorama {
|
|
|
765
669
|
|
|
766
670
|
// Use uniqueVariables for simpler reference counting
|
|
767
671
|
const referenceData = varKeys.map((k) => {
|
|
768
|
-
|
|
769
|
-
// Extract the variable name from the key by removing ${ and }
|
|
770
|
-
const varName = k.replace(/^\$\{/, '').replace(/\}$/, '').split(',')[0].trim()
|
|
672
|
+
const varName = getBaseVarName(k)
|
|
771
673
|
const uniqueVar = uniqueVariables[varName]
|
|
772
674
|
const refCount = uniqueVar ? uniqueVar.occurrences.length : variableData[k].length
|
|
773
675
|
const placesWord = refCount > 1 ? 'places' : 'place'
|
|
@@ -779,8 +681,6 @@ class Configorama {
|
|
|
779
681
|
|
|
780
682
|
logHeader('Variable Details')
|
|
781
683
|
|
|
782
|
-
const lines = this.configFileContents ? this.configFileContents.split('\n') : []
|
|
783
|
-
|
|
784
684
|
const indent = ''
|
|
785
685
|
const boxes = varKeys.map((key, i) => {
|
|
786
686
|
const variableInstances = variableData[key]
|
|
@@ -788,7 +688,7 @@ class Configorama {
|
|
|
788
688
|
const firstInstance = variableInstances[0]
|
|
789
689
|
|
|
790
690
|
// Get uniqueVariable data for description and other metadata
|
|
791
|
-
const varName = key
|
|
691
|
+
const varName = getBaseVarName(key)
|
|
792
692
|
const uniqueVar = uniqueVariables[varName]
|
|
793
693
|
|
|
794
694
|
// Build display message from enriched metadata
|
|
@@ -807,41 +707,24 @@ class Configorama {
|
|
|
807
707
|
}
|
|
808
708
|
|
|
809
709
|
// Show type filter if present (Boolean, String, Number, etc.)
|
|
810
|
-
if (uniqueVar && uniqueVar.
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
uniqueVar.occurrences.forEach(occ => {
|
|
815
|
-
if (occ.filters && Array.isArray(occ.filters)) {
|
|
816
|
-
occ.filters.forEach(filter => {
|
|
817
|
-
if (typeFilters.includes(filter)) {
|
|
818
|
-
foundTypes.add(filter)
|
|
819
|
-
}
|
|
820
|
-
})
|
|
821
|
-
}
|
|
822
|
-
})
|
|
823
|
-
|
|
824
|
-
if (foundTypes.size > 0) {
|
|
825
|
-
const typeText = `${indent}${keyChalk('Type:'.padEnd(titleText.length, ' '))}`
|
|
826
|
-
varMsg += `${typeText} ${valueChalk(Array.from(foundTypes).join(', '))}\n`
|
|
827
|
-
}
|
|
710
|
+
if (uniqueVar && uniqueVar.types && uniqueVar.types.length > 0) {
|
|
711
|
+
const typeLabel = `${indent}${keyChalk('Type:'.padEnd(titleText.length, ' '))}`
|
|
712
|
+
varMsg += `${typeLabel} ${valueChalk(uniqueVar.types.join(', '))}\n`
|
|
828
713
|
}
|
|
829
714
|
|
|
830
715
|
// Show description from uniqueVariables if available
|
|
831
|
-
if (uniqueVar && uniqueVar.
|
|
832
|
-
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
.filter((desc, index, self) => desc && self.indexOf(desc) === index)
|
|
836
|
-
|
|
837
|
-
if (descriptions.length > 0) {
|
|
838
|
-
const descText = `${indent}${keyChalk('Description:'.padEnd(titleText.length, ' '))}`
|
|
839
|
-
const combinedDesc = descriptions.join('. ')
|
|
840
|
-
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
841
|
-
}
|
|
716
|
+
if (uniqueVar && uniqueVar.descriptions && uniqueVar.descriptions.length > 0) {
|
|
717
|
+
const descText = `${indent}${keyChalk('Description:'.padEnd(titleText.length, ' '))}`
|
|
718
|
+
const combinedDesc = uniqueVar.descriptions.join('. ')
|
|
719
|
+
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
842
720
|
}
|
|
843
721
|
|
|
844
|
-
|
|
722
|
+
// Show resolve order from metadata
|
|
723
|
+
if (firstInstance.resolveOrder.length > 1) {
|
|
724
|
+
varMsg += `${indent}${keyChalk('Resolve Order:'.padEnd(titleText.length, ' '))}`
|
|
725
|
+
const resolveOrder = firstInstance.resolveOrder.join(', ')
|
|
726
|
+
varMsg += ` ${valueChalk(resolveOrder)}\n`
|
|
727
|
+
}
|
|
845
728
|
|
|
846
729
|
// Show default value from metadata
|
|
847
730
|
if (typeof firstInstance.defaultValue !== 'undefined') {
|
|
@@ -852,82 +735,49 @@ class Configorama {
|
|
|
852
735
|
|
|
853
736
|
// Show default value source path from metadata
|
|
854
737
|
if (firstInstance.defaultValueSrc) {
|
|
855
|
-
varMsg += `${indent}${keyChalk('Default
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const resolveOrder = firstInstance.resolveOrder.join(', ')
|
|
863
|
-
varMsg += ` ${valueChalk(resolveOrder)}\n`
|
|
738
|
+
varMsg += `${indent}${keyChalk('Default path:'.padEnd(titleText.length, ' '))} `
|
|
739
|
+
const defaultPathLine = findLineForKey(firstInstance.defaultValueSrc, lines, fileType)
|
|
740
|
+
if (defaultPathLine) {
|
|
741
|
+
varMsg += `${createEditorLink(configFilePath, defaultPathLine, 1, firstInstance.defaultValueSrc, 'gray')}\n`
|
|
742
|
+
} else {
|
|
743
|
+
varMsg += `${valueChalk(firstInstance.defaultValueSrc)}\n`
|
|
744
|
+
}
|
|
864
745
|
}
|
|
865
746
|
|
|
866
747
|
// Show path(s) from metadata
|
|
867
|
-
|
|
748
|
+
const configPathLine = findLineForKey(variableInstances[0].path, lines, fileType)
|
|
749
|
+
let locationRender = configPathLine
|
|
750
|
+
? createEditorLink(configFilePath, configPathLine, 1, variableInstances[0].path, 'gray')
|
|
751
|
+
: valueChalk(variableInstances[0].path)
|
|
868
752
|
let locationLabel = `${indent}${keyChalk('Config Path:'.padEnd(titleText.length, ' '))}`
|
|
869
753
|
let typeText = ''
|
|
870
754
|
if (variableInstances.length > 1) {
|
|
871
755
|
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
872
756
|
const pathItems = variableInstances.map((v, idx) => {
|
|
757
|
+
const pathLine = findLineForKey(v.path, lines, fileType)
|
|
758
|
+
const pathLink = pathLine
|
|
759
|
+
? createEditorLink(configFilePath, pathLine, 1, `- ${v.path}`, 'gray')
|
|
760
|
+
: valueChalk(`- ${v.path}`)
|
|
873
761
|
// Show type filter per path if different
|
|
874
762
|
if (uniqueVar && uniqueVar.occurrences.length > 1) {
|
|
875
763
|
const occurrence = uniqueVar.occurrences.find(occ => occ.path === v.path)
|
|
876
|
-
const
|
|
877
|
-
const pathType = occurrence && occurrence.filters
|
|
878
|
-
? occurrence.filters.find(f => typeFilters.includes(f))
|
|
879
|
-
: null
|
|
880
|
-
|
|
764
|
+
const pathType = occurrence && occurrence.type
|
|
881
765
|
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
882
766
|
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
883
|
-
return `${prefix}${
|
|
767
|
+
return `${prefix}${pathLink}${typeText}`
|
|
884
768
|
}
|
|
885
769
|
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
886
|
-
return `${prefix}${
|
|
770
|
+
return `${prefix}${pathLink}${typeText}`
|
|
887
771
|
})
|
|
888
772
|
locationRender = pathItems.join('\n')
|
|
889
773
|
locationLabel = `${indent}${keyChalk('Config Paths:'.padEnd(titleText.length, ' '))}`
|
|
890
774
|
} else {
|
|
891
|
-
|
|
892
|
-
const typeFilters = ['Boolean', 'String', 'Number', 'Array', 'Object']
|
|
893
|
-
const pathType = firstInstance.filters
|
|
894
|
-
? firstInstance.filters.find(f => typeFilters.includes(f))
|
|
895
|
-
: null
|
|
896
|
-
|
|
775
|
+
const pathType = firstInstance.type
|
|
897
776
|
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
898
777
|
}
|
|
899
778
|
varMsg += `${locationLabel} ${locationRender}`
|
|
900
779
|
|
|
901
|
-
|
|
902
|
-
const configKey = firstInstance.key
|
|
903
|
-
const line = lines.findIndex((line) => {
|
|
904
|
-
const fileType = this.configFileType
|
|
905
|
-
const escapedKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
906
|
-
// YAML: key: or key :
|
|
907
|
-
if (fileType === '.yml' || fileType === '.yaml') {
|
|
908
|
-
return new RegExp(`^\\s*${escapedKey}\\s*:`).test(line)
|
|
909
|
-
}
|
|
910
|
-
// TOML: key = or key=
|
|
911
|
-
if (fileType === '.toml') {
|
|
912
|
-
return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
|
|
913
|
-
}
|
|
914
|
-
// JSON: "key": or "key" :
|
|
915
|
-
if (fileType === '.json' || fileType === '.json5') {
|
|
916
|
-
return new RegExp(`"${escapedKey}"\\s*:`).test(line)
|
|
917
|
-
}
|
|
918
|
-
// INI: key = or key=
|
|
919
|
-
if (fileType === '.ini') {
|
|
920
|
-
return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
|
|
921
|
-
}
|
|
922
|
-
// JS/TS/ESM: key: or "key": or 'key': or `key`: or [`key`]:
|
|
923
|
-
if (['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(fileType)) {
|
|
924
|
-
return new RegExp(`(?:${escapedKey}|"${escapedKey}"|'${escapedKey}'|\`${escapedKey}\`|\\[\`${escapedKey}\`\\])\\s*:`).test(line)
|
|
925
|
-
}
|
|
926
|
-
// Default fallback: try YAML-style
|
|
927
|
-
return line.includes(`${configKey}:`)
|
|
928
|
-
})
|
|
929
|
-
const lineNumber = line !== -1 ? line + 1 : 0
|
|
930
|
-
|
|
780
|
+
const lineNumber = findLineForKey(firstInstance.key, lines, fileType)
|
|
931
781
|
|
|
932
782
|
return {
|
|
933
783
|
content: {
|
|
@@ -957,6 +807,276 @@ class Configorama {
|
|
|
957
807
|
// process.exit(1)
|
|
958
808
|
}
|
|
959
809
|
|
|
810
|
+
// New unique variable makeStackedBoxes display
|
|
811
|
+
const uniqueBoxes = uniqueVarKeys.map((varName, i) => {
|
|
812
|
+
const uniqueVar = uniqueVariables[varName]
|
|
813
|
+
const occurrences = uniqueVar.occurrences || []
|
|
814
|
+
const firstOcc = occurrences[0] || {}
|
|
815
|
+
|
|
816
|
+
const spacing = ' '
|
|
817
|
+
const titleText = `Variable:${spacing}`
|
|
818
|
+
const VALUE_HEX = '#899499'
|
|
819
|
+
const keyChalk = chalk.whiteBright
|
|
820
|
+
const valueChalk = chalk.hex(VALUE_HEX)
|
|
821
|
+
|
|
822
|
+
let varMsg = ''
|
|
823
|
+
let requiredMessage = ''
|
|
824
|
+
|
|
825
|
+
// Show required status from computed isRequired (accounts for resolved self-refs)
|
|
826
|
+
const isRequired = occurrences.some(occ => occ.isRequired)
|
|
827
|
+
if (isRequired) {
|
|
828
|
+
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Show type filter if present
|
|
832
|
+
if (uniqueVar.types && uniqueVar.types.length > 0) {
|
|
833
|
+
const typeLabel = `${keyChalk('Type:'.padEnd(titleText.length, ' '))}`
|
|
834
|
+
varMsg += `${typeLabel} ${valueChalk(uniqueVar.types.join(', '))}\n`
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Show description
|
|
838
|
+
if (uniqueVar.descriptions && uniqueVar.descriptions.length > 0) {
|
|
839
|
+
const descText = `${keyChalk('Description:'.padEnd(titleText.length, ' '))}`
|
|
840
|
+
const combinedDesc = uniqueVar.descriptions.join('. ')
|
|
841
|
+
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Show default value only if it's a true fallback, not a pre-resolved value
|
|
845
|
+
// Redact sensitive values like API keys, secrets, tokens
|
|
846
|
+
const isSensitive = isSensitiveVariable(varName)
|
|
847
|
+
const hasActualDefault = firstOcc.hasFallback && typeof firstOcc.defaultValue !== 'undefined'
|
|
848
|
+
if (hasActualDefault) {
|
|
849
|
+
const defaultValueRender = isSensitive ? '********' : (firstOcc.defaultValue === '' ? '""' : firstOcc.defaultValue)
|
|
850
|
+
const defaultValueText = `${keyChalk('Default value:'.padEnd(titleText.length, ' '))}`
|
|
851
|
+
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}\n`
|
|
852
|
+
} else if (uniqueVar.resolvedValue !== undefined) {
|
|
853
|
+
// Show pre-resolved current value (e.g., from env, git)
|
|
854
|
+
const resolvedRender = isSensitive ? '********' : (uniqueVar.resolvedValue === '' ? '""' : uniqueVar.resolvedValue)
|
|
855
|
+
const resolvedText = `${keyChalk('Current value:'.padEnd(titleText.length, ' '))}`
|
|
856
|
+
const envIndicator = uniqueVar.variableType === 'env' ? ` ${chalk.red('(currently set env var)')}` : ''
|
|
857
|
+
varMsg += `${resolvedText} ${valueChalk(resolvedRender)}${envIndicator}\n`
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Show default value source path
|
|
861
|
+
if (firstOcc.defaultValueSrc) {
|
|
862
|
+
varMsg += `${keyChalk('Default path:'.padEnd(titleText.length, ' '))} `
|
|
863
|
+
const defaultPathLine = findLineForKey(firstOcc.defaultValueSrc, lines, fileType)
|
|
864
|
+
if (defaultPathLine) {
|
|
865
|
+
varMsg += `${createEditorLink(configFilePath, defaultPathLine, 1, firstOcc.defaultValueSrc, 'gray')}\n`
|
|
866
|
+
} else {
|
|
867
|
+
varMsg += `${valueChalk(firstOcc.defaultValueSrc)}\n`
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Show config path(s) from occurrences
|
|
872
|
+
let locationRender
|
|
873
|
+
let locationLabel
|
|
874
|
+
if (occurrences.length > 1) {
|
|
875
|
+
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
876
|
+
const pathItems = occurrences.map((occ, idx) => {
|
|
877
|
+
const pathLine = findLineForKey(occ.path, lines, fileType)
|
|
878
|
+
const pathLink = pathLine
|
|
879
|
+
? createEditorLink(configFilePath, pathLine, 1, `- ${occ.path}`, 'gray')
|
|
880
|
+
: valueChalk(`- ${occ.path}`)
|
|
881
|
+
const typeText = occ.type ? ` ${chalk.dim(`Type: ${occ.type}`)}` : ''
|
|
882
|
+
const prefix = idx === 0 ? '' : `${pathIndent}`
|
|
883
|
+
return `${prefix}${pathLink}${typeText}`
|
|
884
|
+
})
|
|
885
|
+
locationRender = pathItems.join('\n')
|
|
886
|
+
locationLabel = `${keyChalk('Config Paths:'.padEnd(titleText.length, ' '))}`
|
|
887
|
+
} else {
|
|
888
|
+
const pathLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
889
|
+
locationRender = pathLine
|
|
890
|
+
? createEditorLink(configFilePath, pathLine, 1, firstOcc.path, 'gray')
|
|
891
|
+
: valueChalk(firstOcc.path)
|
|
892
|
+
locationLabel = `${keyChalk('Config Path:'.padEnd(titleText.length, ' '))}`
|
|
893
|
+
}
|
|
894
|
+
varMsg += `${locationLabel} ${locationRender}`
|
|
895
|
+
|
|
896
|
+
// Find first line number for title
|
|
897
|
+
const lineNumber = findLineForKey(firstOcc.path, lines, fileType)
|
|
898
|
+
|
|
899
|
+
return {
|
|
900
|
+
content: {
|
|
901
|
+
left: varMsg,
|
|
902
|
+
backgroundColor: 'red',
|
|
903
|
+
width: '100%',
|
|
904
|
+
},
|
|
905
|
+
title: {
|
|
906
|
+
left: `▷ ${firstOcc.varMatch}`,
|
|
907
|
+
right: `${requiredMessage} ${lineNumber ? `Line: ${lineNumber.toString().padEnd(2, ' ')}` : ''}`,
|
|
908
|
+
paddingBottom: 1,
|
|
909
|
+
paddingTop: (i === 0) ? 1 : 0,
|
|
910
|
+
truncate: true,
|
|
911
|
+
},
|
|
912
|
+
width: '100%',
|
|
913
|
+
}
|
|
914
|
+
})
|
|
915
|
+
|
|
916
|
+
console.log(makeStackedBoxes(uniqueBoxes, {
|
|
917
|
+
borderText: 'Unique Variables',
|
|
918
|
+
borderColor: 'gray',
|
|
919
|
+
minWidth: '96%',
|
|
920
|
+
borderStyle: 'bold',
|
|
921
|
+
disableTitleSeparator: true,
|
|
922
|
+
}))
|
|
923
|
+
console.log()
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
// Unique variables that require setup (excludes readonly source types)
|
|
927
|
+
const CONFIGURABLE_SOURCES = ['user', 'config', 'remote']
|
|
928
|
+
const configurableVariables = {}
|
|
929
|
+
const configurableVarKeys = []
|
|
930
|
+
|
|
931
|
+
for (const varName of uniqueVarKeys) {
|
|
932
|
+
const uniqueVar = uniqueVariables[varName]
|
|
933
|
+
// Include if source type is user, config, or remote (not readonly)
|
|
934
|
+
if (CONFIGURABLE_SOURCES.includes(uniqueVar.variableSourceType)) {
|
|
935
|
+
configurableVariables[varName] = uniqueVar
|
|
936
|
+
configurableVarKeys.push(varName)
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Display configurable variables by source type
|
|
941
|
+
if (configurableVarKeys.length > 0) {
|
|
942
|
+
const spacing = ' '
|
|
943
|
+
const titleText = `Variable:${spacing}`
|
|
944
|
+
const VALUE_HEX = '#899499'
|
|
945
|
+
const keyChalk = chalk.whiteBright
|
|
946
|
+
const valueChalk = chalk.hex(VALUE_HEX)
|
|
947
|
+
|
|
948
|
+
// Group by source type
|
|
949
|
+
const bySource = {
|
|
950
|
+
user: [],
|
|
951
|
+
config: [],
|
|
952
|
+
remote: [],
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
for (const varName of configurableVarKeys) {
|
|
956
|
+
const v = configurableVariables[varName]
|
|
957
|
+
const sourceType = v.variableSourceType || 'user'
|
|
958
|
+
if (bySource[sourceType]) {
|
|
959
|
+
bySource[sourceType].push({ varName, ...v })
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const sourceLabels = {
|
|
964
|
+
user: 'User Input Required',
|
|
965
|
+
config: 'Config References',
|
|
966
|
+
remote: 'Remote Services',
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const sourceColors = {
|
|
970
|
+
user: 'yellow',
|
|
971
|
+
config: 'cyan',
|
|
972
|
+
remote: 'magenta',
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const configurableBoxes = []
|
|
976
|
+
|
|
977
|
+
for (const [sourceType, vars] of Object.entries(bySource)) {
|
|
978
|
+
if (vars.length === 0) continue
|
|
979
|
+
|
|
980
|
+
for (let i = 0; i < vars.length; i++) {
|
|
981
|
+
const v = vars[i]
|
|
982
|
+
const occurrences = v.occurrences || []
|
|
983
|
+
const firstOcc = occurrences[0] || {}
|
|
984
|
+
|
|
985
|
+
let varMsg = ''
|
|
986
|
+
let requiredMessage = ''
|
|
987
|
+
|
|
988
|
+
// Show required status from computed isRequired (accounts for resolved self-refs)
|
|
989
|
+
const isRequired = occurrences.some(occ => occ.isRequired)
|
|
990
|
+
if (isRequired) {
|
|
991
|
+
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Show description if present (directly under title, not as key/value)
|
|
995
|
+
if (v.descriptions && v.descriptions.length > 0) {
|
|
996
|
+
varMsg += `${chalk.dim(v.descriptions.join('. '))}\n\n`
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Show type filter if defined (String, Number, etc.)
|
|
1000
|
+
const varType = (v.types && v.types[0]) || firstOcc.type
|
|
1001
|
+
if (varType) {
|
|
1002
|
+
varMsg += `${keyChalk('Type:'.padEnd(titleText.length, ' '))} ${valueChalk(varType)}\n`
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Show current/default value (redact sensitive values)
|
|
1006
|
+
const isSensitive = isSensitiveVariable(v.varName)
|
|
1007
|
+
if (v.resolvedValue !== undefined) {
|
|
1008
|
+
const resolvedRender = isSensitive ? '********' : (v.resolvedValue === '' ? '""' : v.resolvedValue)
|
|
1009
|
+
varMsg += `${keyChalk('Current value:'.padEnd(titleText.length, ' '))} ${valueChalk(resolvedRender)}\n`
|
|
1010
|
+
} else if (firstOcc.hasFallback && firstOcc.defaultValue !== undefined) {
|
|
1011
|
+
const defaultRender = isSensitive ? '********' : (firstOcc.defaultValue === '' ? '""' : firstOcc.defaultValue)
|
|
1012
|
+
varMsg += `${keyChalk('Default value:'.padEnd(titleText.length, ' '))} ${valueChalk(defaultRender)}\n`
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Show config path(s)
|
|
1016
|
+
let locationRender
|
|
1017
|
+
let locationLabel
|
|
1018
|
+
if (occurrences.length > 1) {
|
|
1019
|
+
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
1020
|
+
const pathItems = occurrences.map((occ, idx) => {
|
|
1021
|
+
const pathLine = findLineForKey(occ.path, lines, fileType)
|
|
1022
|
+
const pathLink = pathLine
|
|
1023
|
+
? createEditorLink(configFilePath, pathLine, 1, `- ${occ.path}`, VALUE_HEX)
|
|
1024
|
+
: valueChalk(`- ${occ.path}`)
|
|
1025
|
+
const prefix = idx === 0 ? '' : `${pathIndent}`
|
|
1026
|
+
return `${prefix}${pathLink}`
|
|
1027
|
+
})
|
|
1028
|
+
locationRender = pathItems.join('\n')
|
|
1029
|
+
locationLabel = 'Config Paths:'
|
|
1030
|
+
} else {
|
|
1031
|
+
const pathLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
1032
|
+
locationRender = pathLine
|
|
1033
|
+
? createEditorLink(configFilePath, pathLine, 1, firstOcc.path, VALUE_HEX)
|
|
1034
|
+
: valueChalk(firstOcc.path)
|
|
1035
|
+
locationLabel = 'Config Path:'
|
|
1036
|
+
}
|
|
1037
|
+
varMsg += `${keyChalk(locationLabel.padEnd(titleText.length, ' '))} ${locationRender}`
|
|
1038
|
+
|
|
1039
|
+
// Get type for center heading (reuse varType from above)
|
|
1040
|
+
const typeText = varType ? chalk.dim(`Type: ${varType}`) : ''
|
|
1041
|
+
|
|
1042
|
+
// Get line number for first occurrence
|
|
1043
|
+
const firstOccLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
1044
|
+
const varTitle = firstOcc.varMatch || v.varName
|
|
1045
|
+
const requiredSuffix = requiredMessage ? ` - ${requiredMessage}` : ''
|
|
1046
|
+
const titleLink = firstOccLine
|
|
1047
|
+
? createEditorLink(configFilePath, firstOccLine, 1, `▷ ${varTitle}`) + requiredSuffix
|
|
1048
|
+
: `▷ ${varTitle}${requiredSuffix}`
|
|
1049
|
+
|
|
1050
|
+
configurableBoxes.push({
|
|
1051
|
+
content: {
|
|
1052
|
+
left: varMsg,
|
|
1053
|
+
width: '100%',
|
|
1054
|
+
},
|
|
1055
|
+
title: {
|
|
1056
|
+
left: titleLink,
|
|
1057
|
+
// center: typeText,
|
|
1058
|
+
right: chalk.dim(`${v.variableType}`),
|
|
1059
|
+
paddingBottom: 1,
|
|
1060
|
+
paddingTop: (configurableBoxes.length === 0) ? 1 : 0,
|
|
1061
|
+
truncate: true,
|
|
1062
|
+
},
|
|
1063
|
+
width: '100%',
|
|
1064
|
+
})
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (configurableBoxes.length > 0) {
|
|
1069
|
+
console.log(makeStackedBoxes(configurableBoxes, {
|
|
1070
|
+
borderText: `Configurable Variables (${configurableVarKeys.length})`,
|
|
1071
|
+
borderColor: 'yellow',
|
|
1072
|
+
minWidth: '96%',
|
|
1073
|
+
borderStyle: 'bold',
|
|
1074
|
+
disableTitleSeparator: true,
|
|
1075
|
+
}))
|
|
1076
|
+
console.log()
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
960
1080
|
|
|
961
1081
|
// WALK through CLI prompt if --setup flag is set
|
|
962
1082
|
if (SETUP_MODE) {
|
|
@@ -964,20 +1084,29 @@ class Configorama {
|
|
|
964
1084
|
// deepLog('enrich', enrich)
|
|
965
1085
|
const userInputs = await runConfigWizard(enrich, this.originalConfig, this.configFilePath)
|
|
966
1086
|
|
|
967
|
-
console.log('\n')
|
|
968
1087
|
logHeader('User Inputs Summary')
|
|
1088
|
+
console.log()
|
|
969
1089
|
console.log(JSON.stringify(userInputs, null, 2))
|
|
970
1090
|
|
|
971
1091
|
// TODO set values
|
|
972
1092
|
|
|
973
1093
|
// Apply user inputs to options and environment
|
|
974
1094
|
if (userInputs.options) {
|
|
975
|
-
Object.assign(this.
|
|
1095
|
+
Object.assign(this.options, userInputs.options)
|
|
976
1096
|
}
|
|
977
1097
|
if (userInputs.env) {
|
|
978
1098
|
Object.assign(process.env, userInputs.env)
|
|
979
1099
|
}
|
|
980
|
-
|
|
1100
|
+
|
|
1101
|
+
if (userInputs.self) {
|
|
1102
|
+
Object.assign(this.config, userInputs.self)
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (userInputs.dotProp) {
|
|
1106
|
+
for (const [key, value] of Object.entries(userInputs.dotProp)) {
|
|
1107
|
+
dotProp.set(this.config, key, value)
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
981
1110
|
|
|
982
1111
|
console.log()
|
|
983
1112
|
logHeader('Resolving Configuration')
|
|
@@ -991,7 +1120,8 @@ class Configorama {
|
|
|
991
1120
|
|
|
992
1121
|
/* Exit early if list or info flag is set */
|
|
993
1122
|
if (showFoundVariables) {
|
|
994
|
-
|
|
1123
|
+
// TODO re-enable this
|
|
1124
|
+
// process.exit(0)
|
|
995
1125
|
}
|
|
996
1126
|
}
|
|
997
1127
|
|
|
@@ -1125,17 +1255,28 @@ class Configorama {
|
|
|
1125
1255
|
* @returns {object} Metadata object containing variables, fileRefs, and summary
|
|
1126
1256
|
*/
|
|
1127
1257
|
collectVariableMetadata() {
|
|
1258
|
+
// Return cached metadata if already computed
|
|
1259
|
+
if (this._cachedMetadata) {
|
|
1260
|
+
return this._cachedMetadata
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1128
1263
|
const variableSyntax = this.variableSyntax
|
|
1129
1264
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
1130
1265
|
const variableTypes = this.variableTypes
|
|
1131
1266
|
const filterMatch = this.filterMatch
|
|
1267
|
+
const configFilePath = this.configFilePath
|
|
1268
|
+
// Use rawOriginalConfig for metadata display (truly original, no escaping)
|
|
1269
|
+
const originalConfig = this.rawOriginalConfig || this.originalConfig
|
|
1132
1270
|
const foundVariables = []
|
|
1133
1271
|
const variableData = {}
|
|
1134
1272
|
const fileRefs = []
|
|
1135
1273
|
const fileGlobPatterns = []
|
|
1274
|
+
const preResolvedPaths = new Set()
|
|
1275
|
+
const byConfigPath = []
|
|
1276
|
+
const referencesMap = new Map()
|
|
1136
1277
|
let matchCount = 1
|
|
1137
1278
|
|
|
1138
|
-
traverse(
|
|
1279
|
+
traverse(originalConfig).forEach(function (rawValue) {
|
|
1139
1280
|
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
1140
1281
|
const configValuePath = this.path.join('.')
|
|
1141
1282
|
/* Skip Fn::Sub variables */
|
|
@@ -1176,6 +1317,21 @@ class Configorama {
|
|
|
1176
1317
|
|
|
1177
1318
|
const key = keyWithoutFilters
|
|
1178
1319
|
|
|
1320
|
+
// Helper to pre-resolve a variable from config
|
|
1321
|
+
const preResolveFromConfig = (varString, varType) => {
|
|
1322
|
+
if (!varString) return undefined
|
|
1323
|
+
// Handle self: prefix
|
|
1324
|
+
const varPath = varString.startsWith('self:') ? varString.slice(5) : varString
|
|
1325
|
+
// Only pre-resolve dot.prop and self references
|
|
1326
|
+
if (varType === 'dot.prop' || varType === 'self') {
|
|
1327
|
+
const value = dotProp.get(originalConfig, varPath)
|
|
1328
|
+
if (value !== undefined && typeof value !== 'object') {
|
|
1329
|
+
return { resolved: value, path: varPath }
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return undefined
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1179
1335
|
// Strip filters from resolveDetails
|
|
1180
1336
|
const cleanedResolveDetails = nested.map(detail => {
|
|
1181
1337
|
const cleaned = { ...detail }
|
|
@@ -1197,6 +1353,14 @@ class Configorama {
|
|
|
1197
1353
|
cleaned.varString = cleaned.varString.replace(filterMatch, '').trim()
|
|
1198
1354
|
}
|
|
1199
1355
|
}
|
|
1356
|
+
|
|
1357
|
+
// Pre-resolve dot.prop and self references
|
|
1358
|
+
const preResolved = preResolveFromConfig(cleaned.varString || cleaned.variable, cleaned.variableType)
|
|
1359
|
+
if (preResolved) {
|
|
1360
|
+
cleaned.varResolved = preResolved.resolved
|
|
1361
|
+
cleaned.varResolvedPath = preResolved.path
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1200
1364
|
// Also clean fallbackValues if present
|
|
1201
1365
|
if (cleaned.fallbackValues && Array.isArray(cleaned.fallbackValues)) {
|
|
1202
1366
|
cleaned.fallbackValues = cleaned.fallbackValues.map(fb => {
|
|
@@ -1219,6 +1383,17 @@ class Configorama {
|
|
|
1219
1383
|
cleanedFb.stringValue = cleanedFb.stringValue.replace(filterMatch, '').trim()
|
|
1220
1384
|
}
|
|
1221
1385
|
}
|
|
1386
|
+
|
|
1387
|
+
// Pre-resolve fallback variable references
|
|
1388
|
+
if (cleanedFb.stringValue && cleanedFb.stringValue.match(/^\$\{[^}]+\}$/)) {
|
|
1389
|
+
const innerVar = cleanedFb.stringValue.slice(2, -1)
|
|
1390
|
+
const fbPreResolved = preResolveFromConfig(innerVar, 'dot.prop')
|
|
1391
|
+
if (fbPreResolved) {
|
|
1392
|
+
cleanedFb.varResolved = fbPreResolved.resolved
|
|
1393
|
+
cleanedFb.varResolvedPath = fbPreResolved.path
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1222
1397
|
return cleanedFb
|
|
1223
1398
|
})
|
|
1224
1399
|
}
|
|
@@ -1253,35 +1428,59 @@ class Configorama {
|
|
|
1253
1428
|
|
|
1254
1429
|
if (item && item.fallbackValues) {
|
|
1255
1430
|
let hasResolvedFallback
|
|
1431
|
+
let defaultValueSrc
|
|
1432
|
+
const isSingleFallback = item.fallbackValues.length === 1
|
|
1256
1433
|
const order = ([stripFilters(item.valueBeforeFallback)]).concat(item.fallbackValues.map((f, i) => {
|
|
1257
1434
|
if (f.fallbackValues) {
|
|
1258
|
-
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
1435
|
+
const [nestedOrder, nestedResolvedFallback, nestedDefaultSrc] = calculateResolveOrder(f)
|
|
1259
1436
|
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
1260
1437
|
hasResolvedFallback = nestedResolvedFallback
|
|
1438
|
+
defaultValueSrc = nestedDefaultSrc
|
|
1261
1439
|
}
|
|
1262
1440
|
return nestedOrder
|
|
1263
1441
|
}
|
|
1264
1442
|
|
|
1443
|
+
const valueStr = stripFilters(f.stringValue || f.variable)
|
|
1444
|
+
|
|
1445
|
+
// Only set default from first resolvable fallback
|
|
1265
1446
|
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1447
|
+
if (f.varResolved !== undefined) {
|
|
1448
|
+
hasResolvedFallback = f.varResolved
|
|
1449
|
+
defaultValueSrc = f.varResolvedPath
|
|
1450
|
+
} else if (!valueStr.match(/^\$\{[^}]+\}$/)) {
|
|
1451
|
+
// Literal value - use as default
|
|
1452
|
+
hasResolvedFallback = valueStr
|
|
1453
|
+
}
|
|
1454
|
+
// If variable can't resolve, don't set - let next fallback try
|
|
1270
1455
|
}
|
|
1271
1456
|
|
|
1272
1457
|
if (!hasResolvedFallback && f.isVariable) {
|
|
1273
1458
|
defaultValueIsVar = f
|
|
1274
1459
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1460
|
+
|
|
1461
|
+
if (f.isResolvedFallback) {
|
|
1462
|
+
if (isSingleFallback) {
|
|
1463
|
+
// Single fallback: show "value (default)"
|
|
1464
|
+
return `${valueStr} (default)`
|
|
1465
|
+
} else {
|
|
1466
|
+
// Multiple fallbacks: show resolved value if available
|
|
1467
|
+
if (f.varResolved !== undefined) {
|
|
1468
|
+
return `${valueStr} = ${f.varResolved}`
|
|
1469
|
+
}
|
|
1470
|
+
// If can't resolve, just show the value without annotation
|
|
1471
|
+
return valueStr
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return valueStr
|
|
1277
1475
|
})).flat()
|
|
1278
1476
|
|
|
1279
|
-
return [order, hasResolvedFallback]
|
|
1477
|
+
return [order, hasResolvedFallback, defaultValueSrc]
|
|
1280
1478
|
}
|
|
1281
|
-
return [[stripFilters(item.variable)], undefined]
|
|
1479
|
+
return [[stripFilters(item.variable)], undefined, undefined]
|
|
1282
1480
|
}
|
|
1283
1481
|
|
|
1284
|
-
const [
|
|
1482
|
+
const lastCleanedItem = cleanedResolveDetails[cleanedResolveDetails.length - 1]
|
|
1483
|
+
const [resolveOrder, hasResolvedFallback, defaultValueSrc] = calculateResolveOrder(lastCleanedItem)
|
|
1285
1484
|
varData.resolveOrder = resolveOrder
|
|
1286
1485
|
|
|
1287
1486
|
if (defaultValueIsVar) {
|
|
@@ -1292,6 +1491,10 @@ class Configorama {
|
|
|
1292
1491
|
varData.defaultValue = hasResolvedFallback
|
|
1293
1492
|
}
|
|
1294
1493
|
|
|
1494
|
+
if (defaultValueSrc) {
|
|
1495
|
+
varData.defaultValueSrc = defaultValueSrc
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1295
1498
|
if (typeof varData.defaultValue === 'undefined') {
|
|
1296
1499
|
varData.isRequired = true
|
|
1297
1500
|
}
|
|
@@ -1304,46 +1507,75 @@ class Configorama {
|
|
|
1304
1507
|
nested.forEach((detail) => {
|
|
1305
1508
|
// console.log('detail', detail)
|
|
1306
1509
|
if (detail.variableType && (detail.variableType === 'file' || detail.variableType === 'text')) {
|
|
1307
|
-
const
|
|
1308
|
-
if (
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
// Split by comma to separate file path from parameters/fallback values
|
|
1312
|
-
const parts = splitCsv(fileContent)
|
|
1313
|
-
let filePath = parts[0].trim()
|
|
1314
|
-
|
|
1315
|
-
// Remove quotes if present
|
|
1316
|
-
filePath = filePath.replace(/^['"]|['"]$/g, '')
|
|
1317
|
-
|
|
1318
|
-
// Normalize path: ensure relative paths start with ./
|
|
1319
|
-
let normalizedPath = filePath
|
|
1320
|
-
if (
|
|
1321
|
-
!filePath.startsWith('./') &&
|
|
1322
|
-
!filePath.startsWith('../') &&
|
|
1323
|
-
!filePath.startsWith('/') &&
|
|
1324
|
-
!filePath.startsWith('~')
|
|
1325
|
-
) {
|
|
1326
|
-
normalizedPath = './' + filePath
|
|
1327
|
-
}
|
|
1510
|
+
const extracted = extractFilePath(detail.variable)
|
|
1511
|
+
if (extracted) {
|
|
1512
|
+
const normalizedPath = normalizePath(extracted.filePath)
|
|
1513
|
+
if (!normalizedPath) return
|
|
1328
1514
|
|
|
1329
|
-
// file .//
|
|
1330
|
-
if (normalizedPath.startsWith('.//')) {
|
|
1331
|
-
normalizedPath = normalizedPath.replace('.//', './')
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
1515
|
// Handle variables in file paths - just record the pattern
|
|
1335
1516
|
if (!fileRefs.includes(normalizedPath)) {
|
|
1336
1517
|
fileRefs.push(normalizedPath)
|
|
1337
1518
|
}
|
|
1338
|
-
|
|
1519
|
+
|
|
1339
1520
|
// Check if path contains variables and create glob pattern
|
|
1340
|
-
|
|
1521
|
+
const containsVariables = !!normalizedPath.match(variableSyntax)
|
|
1522
|
+
let globPattern
|
|
1523
|
+
if (containsVariables) {
|
|
1341
1524
|
// Replace variable syntax ${...} with * for glob pattern
|
|
1342
|
-
|
|
1525
|
+
globPattern = normalizedPath.replace(variableSyntax, '*')
|
|
1343
1526
|
if (!fileGlobPatterns.includes(globPattern)) {
|
|
1344
1527
|
fileGlobPatterns.push(globPattern)
|
|
1345
1528
|
}
|
|
1346
1529
|
}
|
|
1530
|
+
|
|
1531
|
+
// Try to pre-resolve inner variables from originalConfig
|
|
1532
|
+
let resolvedPath = normalizedPath
|
|
1533
|
+
let resolvedVarString = rawValue
|
|
1534
|
+
if (containsVariables) {
|
|
1535
|
+
const pathResult = resolveInnerVariables(normalizedPath, variableSyntax, originalConfig, dotProp.get)
|
|
1536
|
+
const varStringResult = resolveInnerVariables(rawValue, variableSyntax, originalConfig, dotProp.get)
|
|
1537
|
+
|
|
1538
|
+
if (pathResult.didResolve) {
|
|
1539
|
+
resolvedPath = normalizePath(pathResult.resolved) || pathResult.resolved
|
|
1540
|
+
resolvedVarString = varStringResult.resolved
|
|
1541
|
+
preResolvedPaths.add(resolvedPath)
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Build byConfigPath entry
|
|
1546
|
+
const absolutePath = configFilePath
|
|
1547
|
+
? path.resolve(path.dirname(configFilePath), resolvedPath)
|
|
1548
|
+
: resolvedPath
|
|
1549
|
+
const fileExists = configFilePath ? fs.existsSync(absolutePath) : false
|
|
1550
|
+
|
|
1551
|
+
const configPathEntry = {
|
|
1552
|
+
location: configValuePath,
|
|
1553
|
+
filePath: absolutePath,
|
|
1554
|
+
relativePath: resolvedPath,
|
|
1555
|
+
originalVariableString: rawValue,
|
|
1556
|
+
resolvedVariableString: resolvedVarString,
|
|
1557
|
+
containsVariables,
|
|
1558
|
+
exists: fileExists,
|
|
1559
|
+
}
|
|
1560
|
+
if (globPattern) {
|
|
1561
|
+
configPathEntry.pattern = globPattern
|
|
1562
|
+
}
|
|
1563
|
+
byConfigPath.push(configPathEntry)
|
|
1564
|
+
|
|
1565
|
+
// Build references entry (use resolvedPath as key when available)
|
|
1566
|
+
const refKey = resolvedPath
|
|
1567
|
+
if (!referencesMap.has(refKey)) {
|
|
1568
|
+
referencesMap.set(refKey, {
|
|
1569
|
+
resolvedPath: refKey,
|
|
1570
|
+
refs: [],
|
|
1571
|
+
})
|
|
1572
|
+
}
|
|
1573
|
+
const refEntry = referencesMap.get(refKey)
|
|
1574
|
+
refEntry.refs.push({
|
|
1575
|
+
location: configValuePath,
|
|
1576
|
+
value: normalizedPath,
|
|
1577
|
+
originalVariableString: rawValue,
|
|
1578
|
+
})
|
|
1347
1579
|
}
|
|
1348
1580
|
}
|
|
1349
1581
|
})
|
|
@@ -1364,50 +1596,58 @@ class Configorama {
|
|
|
1364
1596
|
const instances = variableData[key]
|
|
1365
1597
|
const firstInstance = instances[0]
|
|
1366
1598
|
|
|
1367
|
-
//
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
if (v.resolveDetails && v.resolveDetails.length > 0) {
|
|
1382
|
-
const outermostDetail = v.resolveDetails[v.resolveDetails.length - 1]
|
|
1383
|
-
if (outermostDetail.variableType === 'dot.prop' || outermostDetail.variableType === 'self') {
|
|
1384
|
-
acc.push(outermostDetail)
|
|
1385
|
-
}
|
|
1599
|
+
// Extract variable name from key (e.g. "${self:service}" -> "self:service")
|
|
1600
|
+
const keyVarName = key.slice(2, -1).split(',')[0].trim()
|
|
1601
|
+
|
|
1602
|
+
// Find the resolveDetail that matches THIS variable (not any self-ref in the string)
|
|
1603
|
+
let matchingDetail = null
|
|
1604
|
+
for (const instance of instances) {
|
|
1605
|
+
if (instance.resolveDetails && instance.resolveDetails.length > 0) {
|
|
1606
|
+
const found = instance.resolveDetails.find((detail) => {
|
|
1607
|
+
const detailVar = detail.valueBeforeFallback || detail.variable
|
|
1608
|
+
return detailVar === keyVarName
|
|
1609
|
+
})
|
|
1610
|
+
if (found && (found.variableType === 'dot.prop' || found.variableType === 'self')) {
|
|
1611
|
+
matchingDetail = found
|
|
1612
|
+
break
|
|
1386
1613
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1389
1616
|
|
|
1390
|
-
|
|
1617
|
+
// Also check defaultValueIsVar
|
|
1618
|
+
if (!matchingDetail && firstInstance.defaultValueIsVar && (
|
|
1619
|
+
firstInstance.defaultValueIsVar.variableType === 'self:' ||
|
|
1620
|
+
firstInstance.defaultValueIsVar.variableType === 'dot.prop'
|
|
1621
|
+
)) {
|
|
1622
|
+
matchingDetail = firstInstance.defaultValueIsVar
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// Check if truly required
|
|
1626
|
+
let isTrulyRequired = false
|
|
1627
|
+
if (matchingDetail) {
|
|
1628
|
+
// Check if the self-reference resolves to a value
|
|
1629
|
+
// Use valueBeforeFallback if present (strips inline fallback like ", false")
|
|
1630
|
+
const varPath = matchingDetail.valueBeforeFallback || matchingDetail.variable
|
|
1631
|
+
const cleanPath = varPath.replace('self:', '')
|
|
1632
|
+
const dotPropValue = dotProp.get(this.originalConfig, cleanPath)
|
|
1633
|
+
if (typeof dotPropValue === 'undefined') {
|
|
1391
1634
|
isTrulyRequired = true
|
|
1392
1635
|
} else {
|
|
1393
|
-
//
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}
|
|
1399
|
-
// Enrich with default value from self-reference
|
|
1400
|
-
firstInstance.defaultValueSrc = cleanPath
|
|
1401
|
-
const niceString = typeof dotPropValue === 'object' ? JSON.stringify(dotPropValue) : dotPropValue
|
|
1402
|
-
const truncatedString = niceString.length > 100 ? niceString.substring(0, 90) + '...' : niceString
|
|
1403
|
-
firstInstance.defaultValue = truncatedString
|
|
1404
|
-
firstInstance.isRequired = false
|
|
1405
|
-
}
|
|
1636
|
+
// Enrich ALL instances with resolved self-reference value (overrides inline fallbacks)
|
|
1637
|
+
instances.forEach((instance) => {
|
|
1638
|
+
instance.defaultValueSrc = cleanPath
|
|
1639
|
+
instance.defaultValue = dotPropValue
|
|
1640
|
+
instance.isRequired = false
|
|
1641
|
+
})
|
|
1406
1642
|
}
|
|
1643
|
+
} else if (typeof firstInstance.defaultValue === 'undefined') {
|
|
1644
|
+
isTrulyRequired = true
|
|
1407
1645
|
}
|
|
1408
1646
|
|
|
1409
1647
|
// Update isRequired based on computed isTrulyRequired
|
|
1410
|
-
|
|
1648
|
+
instances.forEach((instance) => {
|
|
1649
|
+
instance.isRequired = isTrulyRequired
|
|
1650
|
+
})
|
|
1411
1651
|
|
|
1412
1652
|
if (isTrulyRequired) {
|
|
1413
1653
|
requiredCount++
|
|
@@ -1416,81 +1656,65 @@ class Configorama {
|
|
|
1416
1656
|
}
|
|
1417
1657
|
})
|
|
1418
1658
|
|
|
1419
|
-
|
|
1659
|
+
this._cachedMetadata = {
|
|
1420
1660
|
variables: variableData,
|
|
1421
1661
|
uniqueVariables: {},
|
|
1422
1662
|
fileDependencies: {
|
|
1423
1663
|
globPatterns: fileGlobPatterns,
|
|
1424
1664
|
// all: fileRefs,
|
|
1425
1665
|
dynamicPaths: fileRefs.filter(ref => ref.indexOf('*') !== -1 || ref.match(variableSyntax)),
|
|
1426
|
-
//
|
|
1427
|
-
resolvedPaths:
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1666
|
+
// Resolved paths: static paths + pre-resolved dynamic paths
|
|
1667
|
+
resolvedPaths: [
|
|
1668
|
+
...fileRefs.filter(ref => ref.indexOf('*') === -1 && !ref.match(variableSyntax)),
|
|
1669
|
+
...preResolvedPaths
|
|
1670
|
+
],
|
|
1671
|
+
byConfigPath,
|
|
1672
|
+
references: Array.from(referencesMap.values()),
|
|
1432
1673
|
},
|
|
1433
1674
|
summary: {
|
|
1434
1675
|
totalVariables: varKeys.length,
|
|
1435
1676
|
requiredVariables: requiredCount,
|
|
1436
1677
|
variablesWithDefaults: withDefaultsCount
|
|
1437
1678
|
},
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
runFunction(variableString) {
|
|
1441
|
-
// console.log('runFunction', variableString)
|
|
1442
|
-
/* If json object value return it */
|
|
1443
|
-
if (variableString.match(/^\s*{/) && variableString.match(/}\s*$/)) {
|
|
1444
|
-
return variableString
|
|
1445
|
-
}
|
|
1446
|
-
// console.log('runFunction', variableString)
|
|
1447
|
-
var hasFunc = funcRegex.exec(variableString)
|
|
1448
|
-
// TODO finish Function handling. Need to move this down below resolver to resolve inner refs first
|
|
1449
|
-
// console.log('hasFunc', hasFunc)
|
|
1450
|
-
if (!hasFunc || hasFunc && (hasFunc[1] === 'cron' || hasFunc[1] === 'eval')) {
|
|
1451
|
-
return variableString
|
|
1452
|
-
}
|
|
1453
|
-
// test for object
|
|
1454
|
-
const functionName = hasFunc[1]
|
|
1455
|
-
const rawArgs = hasFunc[2]
|
|
1456
|
-
// TODO @DWELLS. Loop through all raw args and parse to correct datatype
|
|
1457
|
-
// argument is object
|
|
1458
|
-
let argsToPass
|
|
1459
|
-
if (rawArgs && rawArgs.match(/^{([^}]+)}$/)) {
|
|
1460
|
-
// console.log('OBJECT', hasFunc[2])
|
|
1461
|
-
// TODO use JSON5
|
|
1462
|
-
argsToPass = [JSON.parse(rawArgs)]
|
|
1463
|
-
} else {
|
|
1464
|
-
// TODO fix how commas + spaces are ned
|
|
1465
|
-
const splitter = splitCsv(rawArgs, ', ')
|
|
1466
|
-
// console.log('splitter', splitter)
|
|
1467
|
-
argsToPass = formatFunctionArgs(splitter)
|
|
1468
|
-
}
|
|
1469
|
-
// console.log('argsToPass runFunction', argsToPass)
|
|
1470
|
-
// TODO check for camelCase version. | toUpperCase messes with function name
|
|
1471
|
-
const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
|
|
1679
|
+
}
|
|
1472
1680
|
|
|
1473
|
-
|
|
1681
|
+
return this._cachedMetadata
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Populate the variables in the given object.
|
|
1685
|
+
* @param objectToPopulate The object to populate variables within.
|
|
1686
|
+
* @returns {Promise.<TResult>|*} A promise resolving to the in-place populated object.
|
|
1687
|
+
*/
|
|
1688
|
+
populateObject(objectToPopulate) {
|
|
1689
|
+
return this.initialCall(() => this.populateObjectImpl(objectToPopulate))
|
|
1690
|
+
}
|
|
1691
|
+
populateObjectImpl(objectToPopulate) {
|
|
1692
|
+
this.callCount = this.callCount + 1
|
|
1474
1693
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
let replaceVal = funcValue
|
|
1479
|
-
if (typeof funcValue === 'string') {
|
|
1480
|
-
const replaceIt = variableString.replace(hasFunc[0], funcValue)
|
|
1481
|
-
replaceVal = cleanVariable(replaceIt, this.variableSyntax, true, `runFunction ${this.callCount}`)
|
|
1694
|
+
if (DEBUG) {
|
|
1695
|
+
deepLog(`objectToPopulate called ${this.callCount} times`, objectToPopulate)
|
|
1696
|
+
// process.exit(0)
|
|
1482
1697
|
}
|
|
1483
1698
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1699
|
+
const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
|
|
1700
|
+
this.leaves = leaves
|
|
1701
|
+
// console.log('leaves', leaves)
|
|
1702
|
+
const populations = this.populateVariables(leaves)
|
|
1703
|
+
// console.log("FILL LEAVES", populations)
|
|
1704
|
+
|
|
1705
|
+
if (populations.length === 0) {
|
|
1706
|
+
if (DEBUG) console.log('Config Population Finished')
|
|
1707
|
+
return Promise.resolve(objectToPopulate)
|
|
1488
1708
|
}
|
|
1489
|
-
|
|
1709
|
+
|
|
1710
|
+
return this.assignProperties(objectToPopulate, populations).then(() => {
|
|
1711
|
+
return this.populateObjectImpl(objectToPopulate)
|
|
1712
|
+
})
|
|
1490
1713
|
}
|
|
1491
|
-
|
|
1492
|
-
//
|
|
1493
|
-
//
|
|
1714
|
+
|
|
1715
|
+
// #######################
|
|
1716
|
+
// ## PROPERTY HANDLING ##
|
|
1717
|
+
// #######################
|
|
1494
1718
|
/**
|
|
1495
1719
|
* The declaration of a terminal property. This declaration includes the path and value of the
|
|
1496
1720
|
* property.
|
|
@@ -1638,40 +1862,9 @@ class Configorama {
|
|
|
1638
1862
|
})
|
|
1639
1863
|
})
|
|
1640
1864
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
* @returns {Promise.<TResult>|*} A promise resolving to the in-place populated object.
|
|
1645
|
-
*/
|
|
1646
|
-
populateObject(objectToPopulate) {
|
|
1647
|
-
return this.initialCall(() => this.populateObjectImpl(objectToPopulate))
|
|
1648
|
-
}
|
|
1649
|
-
populateObjectImpl(objectToPopulate) {
|
|
1650
|
-
this.callCount = this.callCount + 1
|
|
1651
|
-
|
|
1652
|
-
if (DEBUG) {
|
|
1653
|
-
deepLog(`objectToPopulate called ${this.callCount} times`, objectToPopulate)
|
|
1654
|
-
// process.exit(0)
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
|
|
1658
|
-
this.leaves = leaves
|
|
1659
|
-
// console.log('leaves', leaves)
|
|
1660
|
-
const populations = this.populateVariables(leaves)
|
|
1661
|
-
// console.log("FILL LEAVES", populations)
|
|
1662
|
-
|
|
1663
|
-
if (populations.length === 0) {
|
|
1664
|
-
if (DEBUG) console.log('Config Population Finished')
|
|
1665
|
-
return Promise.resolve(objectToPopulate)
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
return this.assignProperties(objectToPopulate, populations).then(() => {
|
|
1669
|
-
return this.populateObjectImpl(objectToPopulate)
|
|
1670
|
-
})
|
|
1671
|
-
}
|
|
1672
|
-
// ##############
|
|
1673
|
-
// ## PROPERTY ##
|
|
1674
|
-
// ##############
|
|
1865
|
+
// ##################
|
|
1866
|
+
// ## MATCH/RENDER ##
|
|
1867
|
+
// ##################
|
|
1675
1868
|
/**
|
|
1676
1869
|
* @typedef {Object} MatchResult
|
|
1677
1870
|
* @property {String} match The original property value that matched the variable syntax
|
|
@@ -1734,7 +1927,17 @@ class Configorama {
|
|
|
1734
1927
|
|
|
1735
1928
|
let result = valueObject.value
|
|
1736
1929
|
for (let i = 0; i < matches.length; i += 1) {
|
|
1737
|
-
|
|
1930
|
+
warnIfNotFound(matches[i].variable, results[i], {
|
|
1931
|
+
patterns: {
|
|
1932
|
+
env: envRefSyntax,
|
|
1933
|
+
opt: optRefSyntax,
|
|
1934
|
+
self: selfRefSyntax,
|
|
1935
|
+
file: fileRefSyntax,
|
|
1936
|
+
deep: deepRefSyntax,
|
|
1937
|
+
text: textRefSyntax
|
|
1938
|
+
},
|
|
1939
|
+
debug: DEBUG
|
|
1940
|
+
})
|
|
1738
1941
|
|
|
1739
1942
|
// Extract metadata from result if present
|
|
1740
1943
|
let actualResult = results[i]
|
|
@@ -1878,6 +2081,10 @@ class Configorama {
|
|
|
1878
2081
|
|
|
1879
2082
|
return result
|
|
1880
2083
|
}
|
|
2084
|
+
|
|
2085
|
+
// ######################
|
|
2086
|
+
// ## VALUE RESOLUTION ##
|
|
2087
|
+
// ######################
|
|
1881
2088
|
/**
|
|
1882
2089
|
* Populate the given value, recursively if root is true
|
|
1883
2090
|
* @param valueObject The value to populate variables within
|
|
@@ -1995,7 +2202,8 @@ class Configorama {
|
|
|
1995
2202
|
const hasFilters = originalSrc.match(this.filterMatch)
|
|
1996
2203
|
let foundFilters = []
|
|
1997
2204
|
if (hasFilters) {
|
|
1998
|
-
foundFilters = hasFilters[
|
|
2205
|
+
foundFilters = hasFilters[0]
|
|
2206
|
+
.replace(/}$/, '') // remove trailing }
|
|
1999
2207
|
.split('|')
|
|
2000
2208
|
.map((filter) => filter.trim())
|
|
2001
2209
|
.filter(Boolean)
|
|
@@ -2150,7 +2358,7 @@ class Configorama {
|
|
|
2150
2358
|
|
|
2151
2359
|
if (nestedVar) {
|
|
2152
2360
|
const fallbackStr = getFallbackString(splitVars, nestedVar)
|
|
2153
|
-
if (!this.opts.
|
|
2361
|
+
if (!this.opts.allowUnknownVariables) {
|
|
2154
2362
|
verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
|
|
2155
2363
|
}
|
|
2156
2364
|
|
|
@@ -2262,14 +2470,13 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2262
2470
|
if (typeof valueToPopulate === 'number' && foundFilters.length) {
|
|
2263
2471
|
runFilters = true
|
|
2264
2472
|
} else if (
|
|
2265
|
-
typeof valueToPopulate === 'string' &&
|
|
2266
|
-
!valueToPopulate.match(deepRefSyntax) &&
|
|
2267
|
-
foundFilters.length &&
|
|
2473
|
+
typeof valueToPopulate === 'string' &&
|
|
2474
|
+
!valueToPopulate.match(deepRefSyntax) &&
|
|
2475
|
+
foundFilters.length &&
|
|
2268
2476
|
!property.match(this.variableSyntax)
|
|
2269
2477
|
) {
|
|
2270
2478
|
runFilters = true
|
|
2271
2479
|
}
|
|
2272
|
-
|
|
2273
2480
|
/* Apply filters if found */
|
|
2274
2481
|
//console.log('> property', property)
|
|
2275
2482
|
if (runFilters) {
|
|
@@ -2389,6 +2596,10 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2389
2596
|
: Promise.resolve(extractedValues.find(isValidValue)) // resolve first valid value, else undefined
|
|
2390
2597
|
})
|
|
2391
2598
|
}
|
|
2599
|
+
|
|
2600
|
+
// ####################
|
|
2601
|
+
// ## SOURCE GETTERS ##
|
|
2602
|
+
// ####################
|
|
2392
2603
|
/**
|
|
2393
2604
|
* Given any variable string, return the value it should be populated with.
|
|
2394
2605
|
* @param variableString The variable string to retrieve a value for.
|
|
@@ -2592,6 +2803,11 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2592
2803
|
return Promise.resolve(encodeUnknown(propertyString))
|
|
2593
2804
|
}
|
|
2594
2805
|
|
|
2806
|
+
if (this.opts.allowUnresolvedVariables) {
|
|
2807
|
+
// Encode unresolved variable to pass through resolution
|
|
2808
|
+
return Promise.resolve(encodeUnknown(propertyString))
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2595
2811
|
if (valueCount.length === 1 && noNestedVars) {
|
|
2596
2812
|
const configFilePathMsg = (this.configFilePath) ? `\nIn file ${this.configFilePath} ` : ''
|
|
2597
2813
|
const fromLine = (propertyString !== valueObject.originalSource) ? `\n From "${valueObject.originalSource}"\n` : ''
|
|
@@ -2754,7 +2970,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2754
2970
|
// console.log('nestedVar', nestedVar)
|
|
2755
2971
|
|
|
2756
2972
|
if (nestedVar) {
|
|
2757
|
-
if (!this.opts.
|
|
2973
|
+
if (!this.opts.allowUnknownVariables) {
|
|
2758
2974
|
verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
|
|
2759
2975
|
}
|
|
2760
2976
|
const fallbackStr = getFallbackString(split, nestedVar)
|
|
@@ -2857,7 +3073,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2857
3073
|
|
|
2858
3074
|
|
|
2859
3075
|
/* Pass through unknown variables */
|
|
2860
|
-
if (this.opts.
|
|
3076
|
+
if (this.opts.allowUnknownVariables || allowSpecialCase) {
|
|
2861
3077
|
// console.log('allowUnknownVars propertyString', propertyString)
|
|
2862
3078
|
const varMatches = propertyString.match(this.variableSyntax)
|
|
2863
3079
|
let allowUnknownVars = propertyString
|
|
@@ -2909,331 +3125,20 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2909
3125
|
})
|
|
2910
3126
|
}
|
|
2911
3127
|
async getValueFromFile(variableString, options) {
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
const splitter = splitCsv(hasParams[2])
|
|
2927
|
-
const argsFound = splitter.map((arg) => {
|
|
2928
|
-
const cleanArg = trim(arg).replace(/^'|"/, '').replace(/'|"$/, '')
|
|
2929
|
-
return cleanArg
|
|
2930
|
-
})
|
|
2931
|
-
// console.log('argsFound', argsFound)
|
|
2932
|
-
|
|
2933
|
-
// If function has more arguments than file path
|
|
2934
|
-
if (argsFound.length && argsFound.length > 1) {
|
|
2935
|
-
matchedFileString = argsFound[0]
|
|
2936
|
-
argsToPass = argsFound.filter((arg, i) => {
|
|
2937
|
-
return i !== 0
|
|
2938
|
-
})
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
// console.log('argsToPass', argsToPass)
|
|
2942
|
-
|
|
2943
|
-
const fileDetails = resolveFilePathFromMatch(matchedFileString, syntax, this.configPath)
|
|
2944
|
-
// console.log('fileDetails', fileDetails)
|
|
2945
|
-
|
|
2946
|
-
const { fullFilePath, resolvedPath, relativePath } = fileDetails
|
|
2947
|
-
|
|
2948
|
-
const exists = fs.existsSync(fullFilePath)
|
|
2949
|
-
|
|
2950
|
-
this.fileRefsFound.push({
|
|
2951
|
-
// location: options.context.path.join('.'),
|
|
2952
|
-
filePath: fullFilePath,
|
|
2953
|
-
relativePath,
|
|
2954
|
-
resolvedVariableString: options.context.value,
|
|
2955
|
-
originalVariableString: options.context.originalSource,
|
|
2956
|
-
containsVariables: options.context.value !== options.context.originalSource,
|
|
2957
|
-
exists,
|
|
2958
|
-
})
|
|
2959
|
-
|
|
2960
|
-
let fileExtension = resolvedPath.split('.')
|
|
2961
|
-
|
|
2962
|
-
fileExtension = fileExtension[fileExtension.length - 1]
|
|
2963
|
-
|
|
2964
|
-
// Validate file exists
|
|
2965
|
-
if (!exists) {
|
|
2966
|
-
const originalVar = options.context && options.context.originalSource
|
|
2967
|
-
|
|
2968
|
-
const findNestedResult = findNestedVariables(
|
|
2969
|
-
originalVar,
|
|
2970
|
-
this.variableSyntax,
|
|
2971
|
-
this.variablesKnownTypes,
|
|
2972
|
-
options.context.path,
|
|
2973
|
-
this.variableTypes
|
|
2974
|
-
)
|
|
2975
|
-
// console.log('findNestedResult', findNestedResult)
|
|
2976
|
-
let hasFallback = false
|
|
2977
|
-
if (findNestedResult) {
|
|
2978
|
-
const varDetails = findNestedResult[0]
|
|
2979
|
-
// console.log('varDetails', varDetails)
|
|
2980
|
-
hasFallback = varDetails.hasFallback
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
// check if original var has fallback value
|
|
2984
|
-
// console.log('NO FILE FOUND', fullFilePath)
|
|
2985
|
-
// console.log('variableString', variableString)
|
|
2986
|
-
|
|
2987
|
-
if (!hasFallback && !this.opts.allowUnknownFileRefs) {
|
|
2988
|
-
const errorMsg = makeBox({
|
|
2989
|
-
title: `File Not Found in ${originalVar}`,
|
|
2990
|
-
minWidth: '100%',
|
|
2991
|
-
text: `Variable ${variableString} cannot resolve due to missing file.
|
|
2992
|
-
|
|
2993
|
-
File not found ${fullFilePath}
|
|
2994
|
-
|
|
2995
|
-
Default fallback value will be used if provided.
|
|
2996
|
-
|
|
2997
|
-
${JSON.stringify(options.context, null, 2)}`,
|
|
2998
|
-
})
|
|
2999
|
-
console.log(errorMsg)
|
|
3000
|
-
}
|
|
3001
|
-
// TODO maybe reject. YAML does not allow for null/undefined values
|
|
3002
|
-
// return Promise.reject(new Error(errorMsg))
|
|
3003
|
-
return Promise.resolve(undefined)
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
let valueToPopulate
|
|
3009
|
-
|
|
3010
|
-
const variableFileContents = fs.readFileSync(fullFilePath, 'utf-8')
|
|
3011
|
-
|
|
3012
|
-
/* handle case for referencing raw JS files to inline them */
|
|
3013
|
-
if (argsToPass.length
|
|
3014
|
-
&& (argsToPass && argsToPass[0] && argsToPass[0].toLowerCase() === 'raw')
|
|
3015
|
-
|| opts.asRawText
|
|
3016
|
-
) {
|
|
3017
|
-
// Encode foo() to foo__PH_PAREN_OPEN__) to avoid function collisions
|
|
3018
|
-
valueToPopulate = encodeJsSyntax(variableFileContents)
|
|
3019
|
-
return Promise.resolve(valueToPopulate)
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3022
|
-
// Process JS files
|
|
3023
|
-
if (fileExtension === 'js' || fileExtension === 'cjs') {
|
|
3024
|
-
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
3025
|
-
const jsFile = require(fullFilePath)
|
|
3026
|
-
let returnValueFunction = jsFile
|
|
3027
|
-
// TODO change how exported functions are referenced
|
|
3028
|
-
const variableArray = variableString.split(':')
|
|
3029
|
-
|
|
3030
|
-
if (variableArray[1]) {
|
|
3031
|
-
let jsModule = variableArray[1]
|
|
3032
|
-
jsModule = jsModule.split('.')[0]
|
|
3033
|
-
returnValueFunction = jsFile[jsModule]
|
|
3034
|
-
}
|
|
3035
|
-
|
|
3036
|
-
if (typeof returnValueFunction !== 'function') {
|
|
3037
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3038
|
-
Check if your javascript is exporting a function that returns a value.`
|
|
3039
|
-
return Promise.reject(new Error(errorMessage))
|
|
3040
|
-
}
|
|
3041
|
-
// TODO update what is passed into function
|
|
3042
|
-
|
|
3043
|
-
const valueForFunction = {
|
|
3044
|
-
originalConfig: this.originalConfig,
|
|
3045
|
-
config: this.config,
|
|
3046
|
-
opts: this.opts,
|
|
3047
|
-
}
|
|
3048
|
-
|
|
3049
|
-
valueToPopulate = returnValueFunction.call(jsFile, valueForFunction, ...argsToPass)
|
|
3050
|
-
|
|
3051
|
-
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
3052
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3053
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3054
|
-
deepProperties.splice(0, 1)
|
|
3055
|
-
// Trim prop keys for starting/trailing spaces
|
|
3056
|
-
deepProperties = deepProperties.map((prop) => {
|
|
3057
|
-
return trim(prop)
|
|
3058
|
-
})
|
|
3059
|
-
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
3060
|
-
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
3061
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3062
|
-
Check if your javascript is returning the correct data.`
|
|
3063
|
-
return Promise.reject(new Error(errorMessage))
|
|
3064
|
-
}
|
|
3065
|
-
return Promise.resolve(deepValueToPopulateResolved)
|
|
3066
|
-
})
|
|
3067
|
-
})
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
if (fileExtension === 'ts') {
|
|
3071
|
-
const { executeTypeScriptFile } = require('./parsers/typescript')
|
|
3072
|
-
let returnValueFunction
|
|
3073
|
-
const variableArray = variableString.split(':')
|
|
3074
|
-
|
|
3075
|
-
try {
|
|
3076
|
-
const tsFile = await executeTypeScriptFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
3077
|
-
// console.log('fullFilePath', fullFilePath)
|
|
3078
|
-
// console.log('tsFile', tsFile)
|
|
3079
|
-
returnValueFunction = tsFile.config || tsFile.default || tsFile
|
|
3080
|
-
|
|
3081
|
-
if (variableArray[1]) {
|
|
3082
|
-
let tsModule = variableArray[1]
|
|
3083
|
-
tsModule = tsModule.split('.')[0]
|
|
3084
|
-
returnValueFunction = tsFile[tsModule]
|
|
3085
|
-
}
|
|
3086
|
-
|
|
3087
|
-
if (typeof returnValueFunction !== 'function') {
|
|
3088
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3089
|
-
Check if your TypeScript is exporting a function that returns a value.`
|
|
3090
|
-
return Promise.reject(new Error(errorMessage))
|
|
3091
|
-
}
|
|
3092
|
-
|
|
3093
|
-
const valueForFunction = {
|
|
3094
|
-
originalConfig: this.originalConfig,
|
|
3095
|
-
config: this.config,
|
|
3096
|
-
opts: this.opts,
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
valueToPopulate = returnValueFunction.call(tsFile, valueForFunction, ...argsToPass)
|
|
3100
|
-
|
|
3101
|
-
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
3102
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3103
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3104
|
-
deepProperties.splice(0, 1)
|
|
3105
|
-
// Trim prop keys for starting/trailing spaces
|
|
3106
|
-
deepProperties = deepProperties.map((prop) => {
|
|
3107
|
-
return trim(prop)
|
|
3108
|
-
})
|
|
3109
|
-
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
3110
|
-
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
3111
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3112
|
-
Check if your TypeScript is returning the correct data.`
|
|
3113
|
-
return Promise.reject(new Error(errorMessage))
|
|
3114
|
-
}
|
|
3115
|
-
return Promise.resolve(deepValueToPopulateResolved)
|
|
3116
|
-
})
|
|
3117
|
-
})
|
|
3118
|
-
} catch (err) {
|
|
3119
|
-
return Promise.reject(new Error(`Error processing TypeScript file: ${err.message}`))
|
|
3120
|
-
}
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
if (fileExtension === 'mjs' || fileExtension === 'esm') {
|
|
3124
|
-
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
3125
|
-
const { executeESMFile } = require('./parsers/esm')
|
|
3126
|
-
let returnValueFunction
|
|
3127
|
-
const variableArray = variableString.split(':')
|
|
3128
|
-
|
|
3129
|
-
try {
|
|
3130
|
-
const esmFile = await executeESMFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
3131
|
-
// console.log('ESM fullFilePath', fullFilePath)
|
|
3132
|
-
// console.log('ESM esmFile', esmFile, 'type:', typeof esmFile)
|
|
3133
|
-
returnValueFunction = esmFile.config || esmFile.default || esmFile
|
|
3134
|
-
|
|
3135
|
-
if (variableArray[1]) {
|
|
3136
|
-
let esmModule = variableArray[1]
|
|
3137
|
-
esmModule = esmModule.split('.')[0]
|
|
3138
|
-
returnValueFunction = esmFile[esmModule]
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3141
|
-
if (typeof returnValueFunction !== 'function') {
|
|
3142
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3143
|
-
Check if your ESM is exporting a function that returns a value.`
|
|
3144
|
-
return Promise.reject(new Error(errorMessage))
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
const valueForFunction = {
|
|
3148
|
-
originalConfig: this.originalConfig,
|
|
3149
|
-
config: this.config,
|
|
3150
|
-
opts: this.opts,
|
|
3151
|
-
}
|
|
3152
|
-
|
|
3153
|
-
valueToPopulate = returnValueFunction.call(esmFile, valueForFunction, ...argsToPass)
|
|
3154
|
-
|
|
3155
|
-
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
3156
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3157
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3158
|
-
deepProperties.splice(0, 1)
|
|
3159
|
-
// Trim prop keys for starting/trailing spaces
|
|
3160
|
-
deepProperties = deepProperties.map((prop) => {
|
|
3161
|
-
return trim(prop)
|
|
3162
|
-
})
|
|
3163
|
-
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
3164
|
-
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
3165
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3166
|
-
Check if your ESM is returning the correct data.`
|
|
3167
|
-
return Promise.reject(new Error(errorMessage))
|
|
3168
|
-
}
|
|
3169
|
-
return Promise.resolve(deepValueToPopulateResolved)
|
|
3170
|
-
})
|
|
3171
|
-
})
|
|
3172
|
-
} catch (err) {
|
|
3173
|
-
return Promise.reject(new Error(`Error processing ESM file: ${err.message}`))
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
|
|
3177
|
-
// Process everything except JS, TS, and ESM
|
|
3178
|
-
if (fileExtension !== 'js' && fileExtension !== 'ts' && fileExtension !== 'mjs' && fileExtension !== 'esm') {
|
|
3179
|
-
/* Read initial file */
|
|
3180
|
-
valueToPopulate = variableFileContents
|
|
3181
|
-
|
|
3182
|
-
// File reference has :subKey lookup. Must dig deeper
|
|
3183
|
-
if (matchedFileString !== variableString) {
|
|
3184
|
-
if (fileExtension === 'yml' || fileExtension === 'yaml') {
|
|
3185
|
-
valueToPopulate = JSON.stringify(YAML.parse(valueToPopulate))
|
|
3186
|
-
}
|
|
3187
|
-
if (fileExtension === 'toml') {
|
|
3188
|
-
valueToPopulate = JSON.stringify(TOML.parse(valueToPopulate))
|
|
3189
|
-
}
|
|
3190
|
-
if (fileExtension === 'ini') {
|
|
3191
|
-
valueToPopulate = INI.toJson(valueToPopulate)
|
|
3192
|
-
}
|
|
3193
|
-
// console.log('deep', variableString)
|
|
3194
|
-
// console.log('matchedFileString', matchedFileString)
|
|
3195
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3196
|
-
// TODO 2025-11-12 add file.path.support instead of just :
|
|
3197
|
-
if (deepProperties.substring(0, 1) !== ':') {
|
|
3198
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}" sub properties
|
|
3199
|
-
Please use ":" to reference sub properties. ${deepProperties}`
|
|
3200
|
-
return Promise.reject(new Error(errorMessage))
|
|
3201
|
-
}
|
|
3202
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3203
|
-
return this.getDeeperValue(deepProperties, valueToPopulate)
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
if (fileExtension === 'yml' || fileExtension === 'yaml') {
|
|
3207
|
-
valueToPopulate = YAML.parse(valueToPopulate)
|
|
3208
|
-
return Promise.resolve(valueToPopulate)
|
|
3209
|
-
}
|
|
3210
|
-
|
|
3211
|
-
if (fileExtension === 'toml') {
|
|
3212
|
-
valueToPopulate = TOML.parse(valueToPopulate)
|
|
3213
|
-
return Promise.resolve(valueToPopulate)
|
|
3214
|
-
}
|
|
3215
|
-
|
|
3216
|
-
if (fileExtension === 'ini') {
|
|
3217
|
-
valueToPopulate = INI.parse(valueToPopulate)
|
|
3218
|
-
return Promise.resolve(valueToPopulate)
|
|
3219
|
-
}
|
|
3220
|
-
|
|
3221
|
-
if (fileExtension === 'json') {
|
|
3222
|
-
valueToPopulate = JSON.parse(valueToPopulate)
|
|
3223
|
-
return Promise.resolve(valueToPopulate)
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
// console.log('fall thru', valueToPopulate)
|
|
3227
|
-
return Promise.resolve(valueToPopulate)
|
|
3228
|
-
}
|
|
3229
|
-
getVariableFromDeep(variableString) {
|
|
3230
|
-
const index = variableString.replace(deepIndexReplacePattern, '')
|
|
3231
|
-
// const index = this.getDeepIndex(variableString)
|
|
3232
|
-
/*
|
|
3233
|
-
console.log('FIND INDEX', index)
|
|
3234
|
-
console.log(this.deep, this.deep[index])
|
|
3235
|
-
/** */
|
|
3236
|
-
return this.deep[index]
|
|
3128
|
+
const ctx = {
|
|
3129
|
+
configPath: this.configPath,
|
|
3130
|
+
fileRefsFound: this.fileRefsFound,
|
|
3131
|
+
variableSyntax: this.variableSyntax,
|
|
3132
|
+
variablesKnownTypes: this.variablesKnownTypes,
|
|
3133
|
+
variableTypes: this.variableTypes,
|
|
3134
|
+
opts: this.opts,
|
|
3135
|
+
originalConfig: this.originalConfig,
|
|
3136
|
+
config: this.config,
|
|
3137
|
+
getDeeperValue: this.getDeeperValue.bind(this),
|
|
3138
|
+
fileRefSyntax: fileRefSyntax,
|
|
3139
|
+
textRefSyntax: textRefSyntax
|
|
3140
|
+
}
|
|
3141
|
+
return getValueFromFileResolver(ctx, variableString, options)
|
|
3237
3142
|
}
|
|
3238
3143
|
getValueFromDeep(variableString, pathValue) {
|
|
3239
3144
|
const variable = this.getVariableFromDeep(variableString)
|
|
@@ -3265,6 +3170,19 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3265
3170
|
}
|
|
3266
3171
|
return ret
|
|
3267
3172
|
}
|
|
3173
|
+
|
|
3174
|
+
// ############################
|
|
3175
|
+
// ## DEEP VARIABLE HANDLING ##
|
|
3176
|
+
// ############################
|
|
3177
|
+
getVariableFromDeep(variableString) {
|
|
3178
|
+
const index = variableString.replace(deepIndexReplacePattern, '')
|
|
3179
|
+
// const index = this.getDeepIndex(variableString)
|
|
3180
|
+
/*
|
|
3181
|
+
console.log('FIND INDEX', index)
|
|
3182
|
+
console.log(this.deep, this.deep[index])
|
|
3183
|
+
/** */
|
|
3184
|
+
return this.deep[index]
|
|
3185
|
+
}
|
|
3268
3186
|
makeDeepVariable(variable, caller) {
|
|
3269
3187
|
// variable = variable.replace("dev", '"dev"')
|
|
3270
3188
|
let index = this.deep.findIndex((item) => variable === item)
|
|
@@ -3289,8 +3207,6 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3289
3207
|
console.log('deepVar', deepVar)
|
|
3290
3208
|
// process.exit(1)
|
|
3291
3209
|
/** */
|
|
3292
|
-
// TODO debugging space removal. Seems like this helps
|
|
3293
|
-
// const deepVar = variableContainer.replace(/\s/g, '').replace(variableString, `deep:${index}`)
|
|
3294
3210
|
return deepVar
|
|
3295
3211
|
}
|
|
3296
3212
|
/**
|
|
@@ -3361,66 +3277,68 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3361
3277
|
return veryDeep
|
|
3362
3278
|
}
|
|
3363
3279
|
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3280
|
+
// ###############
|
|
3281
|
+
// ## UTILITIES ##
|
|
3282
|
+
// ###############
|
|
3283
|
+
initialCall(func) {
|
|
3284
|
+
this.deep = []
|
|
3285
|
+
this.tracker.start()
|
|
3286
|
+
return func().finally(() => {
|
|
3287
|
+
this.tracker.stop()
|
|
3288
|
+
this.deep = []
|
|
3289
|
+
})
|
|
3290
|
+
}
|
|
3291
|
+
runFunction(variableString) {
|
|
3292
|
+
// console.log('runFunction', variableString)
|
|
3293
|
+
/* If json object value return it */
|
|
3294
|
+
if (variableString.match(/^\s*{/) && variableString.match(/}\s*$/)) {
|
|
3295
|
+
return variableString
|
|
3378
3296
|
}
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
console.log(notFoundMsg)
|
|
3386
|
-
}
|
|
3387
|
-
// errors make fallbacks not function. throw new Error(errorMsg)
|
|
3297
|
+
// console.log('runFunction', variableString)
|
|
3298
|
+
var hasFunc = funcRegex.exec(variableString)
|
|
3299
|
+
// TODO finish Function handling. Need to move this down below resolver to resolve inner refs first
|
|
3300
|
+
// console.log('hasFunc', hasFunc)
|
|
3301
|
+
if (!hasFunc || hasFunc && (hasFunc[1] === 'cron' || hasFunc[1] === 'eval')) {
|
|
3302
|
+
return variableString
|
|
3388
3303
|
}
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
}
|
|
3304
|
+
// test for object
|
|
3305
|
+
const functionName = hasFunc[1]
|
|
3306
|
+
const rawArgs = hasFunc[2]
|
|
3307
|
+
// TODO @DWELLS. Loop through all raw args and parse to correct datatype
|
|
3308
|
+
// argument is object
|
|
3309
|
+
let argsToPass
|
|
3310
|
+
if (rawArgs && rawArgs.match(/^{([^}]+)}$/)) {
|
|
3311
|
+
// console.log('OBJECT', hasFunc[2])
|
|
3312
|
+
// TODO use JSON5
|
|
3313
|
+
argsToPass = [JSON.parse(rawArgs)]
|
|
3314
|
+
} else {
|
|
3315
|
+
// TODO fix how commas + spaces are ned
|
|
3316
|
+
const splitter = splitCsv(rawArgs, ', ')
|
|
3317
|
+
// console.log('splitter', splitter)
|
|
3318
|
+
argsToPass = formatFunctionArgs(splitter)
|
|
3319
|
+
}
|
|
3320
|
+
// console.log('argsToPass runFunction', argsToPass)
|
|
3321
|
+
// TODO check for camelCase version. | toUpperCase messes with function name
|
|
3322
|
+
const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
|
|
3409
3323
|
|
|
3410
|
-
|
|
3411
|
-
return (str[str.length -1] === char) ? '' : char
|
|
3412
|
-
}
|
|
3324
|
+
if (!theFunction) throw new Error(`Function "${functionName}" not found`)
|
|
3413
3325
|
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3326
|
+
const funcValue = theFunction(...argsToPass)
|
|
3327
|
+
// console.log('funcValue', funcValue)
|
|
3328
|
+
// console.log('typeof funcValue', typeof funcValue)
|
|
3329
|
+
let replaceVal = funcValue
|
|
3330
|
+
if (typeof funcValue === 'string') {
|
|
3331
|
+
const replaceIt = variableString.replace(hasFunc[0], funcValue)
|
|
3332
|
+
replaceVal = cleanVariable(replaceIt, this.variableSyntax, true, `runFunction ${this.callCount}`)
|
|
3333
|
+
}
|
|
3420
3334
|
|
|
3421
|
-
function
|
|
3422
|
-
|
|
3423
|
-
|
|
3335
|
+
// If wrapped in outer function, recurse
|
|
3336
|
+
const hasMoreFunctions = funcRegex.exec(replaceVal)
|
|
3337
|
+
if (hasMoreFunctions) {
|
|
3338
|
+
return this.runFunction(replaceVal)
|
|
3339
|
+
}
|
|
3340
|
+
return replaceVal
|
|
3341
|
+
}
|
|
3424
3342
|
}
|
|
3425
3343
|
|
|
3426
3344
|
module.exports = Configorama
|