configorama 0.6.12 → 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 +690 -778
- 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} +177 -15
- package/src/utils/{parse.js → parsing/parse.js} +13 -13
- package/src/utils/parsing/preProcess.js +165 -0
- package/src/utils/{filePathUtils.js → paths/filePathUtils.js} +3 -2
- 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} +177 -38
- 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/{filePathUtils.test.js → paths/filePathUtils.test.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,24 +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
|
-
const { normalizePath, extractFilePath, resolveInnerVariables } = require('./utils/filePathUtils')
|
|
5
|
+
|
|
6
6
|
/* // disable logs to find broken tests
|
|
7
7
|
console.log = () => {}
|
|
8
8
|
// process.exit(1)
|
|
9
9
|
/** */
|
|
10
10
|
|
|
11
|
+
/* External dependencies */
|
|
11
12
|
const promiseFinallyShim = require('promise.prototype.finally').shim()
|
|
12
|
-
// @TODO only import lodash we need
|
|
13
|
-
|
|
14
13
|
const findUp = require('find-up')
|
|
15
14
|
const traverse = require('traverse')
|
|
16
15
|
const dotProp = require('dot-prop')
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
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')
|
|
26
|
+
|
|
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')
|
|
20
31
|
|
|
21
|
-
/*
|
|
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 */
|
|
22
74
|
const getValueFromString = require('./resolvers/valueFromString')
|
|
23
75
|
const getValueFromNumber = require('./resolvers/valueFromNumber')
|
|
24
76
|
const getValueFromEnv = require('./resolvers/valueFromEnv')
|
|
@@ -26,44 +78,16 @@ const getValueFromOptions = require('./resolvers/valueFromOptions')
|
|
|
26
78
|
const getValueFromCron = require('./resolvers/valueFromCron')
|
|
27
79
|
const getValueFromEval = require('./resolvers/valueFromEval')
|
|
28
80
|
const createGitResolver = require('./resolvers/valueFromGit')
|
|
29
|
-
|
|
81
|
+
const { getValueFromFile: getValueFromFileResolver } = require('./resolvers/valueFromFile')
|
|
82
|
+
|
|
83
|
+
/* Parsers */
|
|
30
84
|
const YAML = require('./parsers/yaml')
|
|
31
85
|
const TOML = require('./parsers/toml')
|
|
32
86
|
const INI = require('./parsers/ini')
|
|
33
87
|
const JSON5 = require('./parsers/json5')
|
|
34
|
-
/* functions */
|
|
35
|
-
const md5Function = require('./functions/md5')
|
|
36
88
|
|
|
37
|
-
/*
|
|
38
|
-
const
|
|
39
|
-
const appendDeepVariable = require('./utils/appendDeepVariable')
|
|
40
|
-
const isValidValue = require('./utils/isValidValue')
|
|
41
|
-
const PromiseTracker = require('./utils/PromiseTracker')
|
|
42
|
-
const handleSignalEvents = require('./utils/handleSignalEvents')
|
|
43
|
-
const formatFunctionArgs = require('./utils/formatFunctionArgs')
|
|
44
|
-
const trimSurroundingQuotes = require('./utils/trimSurroundingQuotes')
|
|
45
|
-
const deepLog = require('./utils/deep-log')
|
|
46
|
-
const { splitByComma } = require('./utils/splitByComma')
|
|
47
|
-
const {
|
|
48
|
-
isArray, isString, isNumber, isObject, isDate, isRegExp, isFunction,
|
|
49
|
-
isEmpty, trim, camelCase, kebabCase, capitalize, split, map, mapValues,
|
|
50
|
-
assign, set, cloneDeep
|
|
51
|
-
} = require('./utils/lodash')
|
|
52
|
-
const { parseFileContents } = require('./utils/parse')
|
|
53
|
-
const { splitCsv } = require('./utils/splitCsv')
|
|
54
|
-
const { replaceAll } = require('./utils/replaceAll')
|
|
55
|
-
const { getTextAfterOccurrence, findNestedVariable } = require('./utils/textUtils')
|
|
56
|
-
const { getFallbackString, verifyVariable } = require('./utils/variableUtils')
|
|
57
|
-
const { encodeUnknown, decodeUnknown } = require('./utils/encoders/unknown-values')
|
|
58
|
-
const { decodeEncodedValue } = require('./utils/encoders')
|
|
59
|
-
const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder } = require('./utils/encoders/js-fixes')
|
|
60
|
-
const { mergeByKeys } = require('./utils/mergeByKeys')
|
|
61
|
-
const { arrayToJsonPath } = require('./utils/arrayToJsonPath')
|
|
62
|
-
const { findNestedVariables } = require('./utils/find-nested-variables')
|
|
63
|
-
const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
|
|
64
|
-
const { logHeader } = require('./utils/logs')
|
|
65
|
-
const { createEditorLink } = require('./utils/createEditorLink')
|
|
66
|
-
const { runConfigWizard } = require('./utils/configWizard')
|
|
89
|
+
/* Functions */
|
|
90
|
+
const md5Function = require('./functions/md5')
|
|
67
91
|
/**
|
|
68
92
|
* Maintainer's notes:
|
|
69
93
|
*
|
|
@@ -89,9 +113,6 @@ const textRefSyntax = RegExp(/^text\((~?[@\{\}\:\$a-zA-Z0-9._\-\/,'" ]+?)\)/g)
|
|
|
89
113
|
const envRefSyntax = RegExp(/^env:/g)
|
|
90
114
|
const optRefSyntax = RegExp(/^opt:/g)
|
|
91
115
|
const selfRefSyntax = RegExp(/^self:/g)
|
|
92
|
-
const funcRegex = /(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
93
|
-
const funcStartOfLineRegex = /^(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
94
|
-
const subFunctionRegex = /(\w+):(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/
|
|
95
116
|
const base64WrapperRegex = /\[_\[([A-Za-z0-9+/=\s]*)\]_\]/g
|
|
96
117
|
const logLines = '─────────────────────────────────────────────────'
|
|
97
118
|
|
|
@@ -102,147 +123,6 @@ let SETUP_MODE = process.argv.includes('--setup') ? true : false
|
|
|
102
123
|
let DEBUG_TYPE = false
|
|
103
124
|
const ENABLE_FUNCTIONS = true
|
|
104
125
|
|
|
105
|
-
function combineRegexes(regexes) {
|
|
106
|
-
// Extract the pattern from each RegExp and join with OR operator
|
|
107
|
-
const patterns = regexes.map(regex => {
|
|
108
|
-
// Get source pattern string without flags
|
|
109
|
-
return regex.source
|
|
110
|
-
}).filter(Boolean)
|
|
111
|
-
// Join patterns with the OR operator and create new RegExp
|
|
112
|
-
return new RegExp(`(${patterns.join('|')})`)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Preprocess config to fix malformed fallback references
|
|
117
|
-
* @param {Object} configObject - The parsed configuration object
|
|
118
|
-
* @param {RegExp} variableSyntax - The variable syntax regex to use
|
|
119
|
-
* @returns {Object} The preprocessed configuration object
|
|
120
|
-
*/
|
|
121
|
-
function preProcess(configObject, variableSyntax) {
|
|
122
|
-
// Known reference prefixes that should be wrapped in ${}
|
|
123
|
-
const refPrefixes = ['self:', 'opt:', 'env:', 'file:', 'text:', 'deep:']
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Fix malformed fallback references in a string
|
|
127
|
-
* @param {string} str - String potentially containing variables
|
|
128
|
-
* @returns {string} String with fixed fallback references
|
|
129
|
-
*/
|
|
130
|
-
function fixFallbacksInString(str) {
|
|
131
|
-
if (typeof str !== 'string') return str
|
|
132
|
-
|
|
133
|
-
let result = str
|
|
134
|
-
// result = result.replace(/\$\{self:/g, '${')
|
|
135
|
-
let changed = true
|
|
136
|
-
|
|
137
|
-
// Keep iterating until no more changes (to handle nested variables)
|
|
138
|
-
while (changed) {
|
|
139
|
-
changed = false
|
|
140
|
-
|
|
141
|
-
// Find innermost ${...} blocks (ones that don't contain other ${)
|
|
142
|
-
let i = 0
|
|
143
|
-
while (i < result.length) {
|
|
144
|
-
if (result[i] === '$' && result[i + 1] === '{') {
|
|
145
|
-
const start = i
|
|
146
|
-
let braceCount = 1
|
|
147
|
-
let j = i + 2
|
|
148
|
-
|
|
149
|
-
// Find the matching closing brace by counting { and }
|
|
150
|
-
while (j < result.length && braceCount > 0) {
|
|
151
|
-
if (result[j] === '{') {
|
|
152
|
-
braceCount++
|
|
153
|
-
} else if (result[j] === '}') {
|
|
154
|
-
braceCount--
|
|
155
|
-
}
|
|
156
|
-
j++
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (braceCount === 0) {
|
|
160
|
-
const end = j
|
|
161
|
-
const match = result.substring(start, end)
|
|
162
|
-
const content = result.substring(start + 2, end - 1)
|
|
163
|
-
|
|
164
|
-
// Only process if there's a comma (indicating fallback syntax)
|
|
165
|
-
if (content.includes(',')) {
|
|
166
|
-
// Split by comma
|
|
167
|
-
const parts = splitByComma(content, variableSyntax)
|
|
168
|
-
|
|
169
|
-
if (parts.length > 1) {
|
|
170
|
-
// Check if the first part has nested ${} - if so, skip this (process inner ones first)
|
|
171
|
-
const firstPart = parts[0]
|
|
172
|
-
if (firstPart.includes('${')) {
|
|
173
|
-
i = start + 2 // Move past ${ to find inner variables
|
|
174
|
-
continue
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Check each part after the first (these are fallback values)
|
|
178
|
-
const fixed = parts.map((part, index) => {
|
|
179
|
-
if (index === 0) {
|
|
180
|
-
return part // Keep the main reference as-is
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const trimmed = part.trim()
|
|
184
|
-
|
|
185
|
-
// Check if this looks like a reference but is not wrapped
|
|
186
|
-
const looksLikeRef = refPrefixes.some(prefix => trimmed.startsWith(prefix))
|
|
187
|
-
const alreadyWrapped = trimmed.startsWith('${') && trimmed.endsWith('}')
|
|
188
|
-
|
|
189
|
-
if (looksLikeRef && !alreadyWrapped) {
|
|
190
|
-
return ` \${${trimmed}}`
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return ` ${trimmed}`
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
const replacement = `\${${fixed.join(',')}}`
|
|
197
|
-
if (replacement !== match) {
|
|
198
|
-
result = result.substring(0, start) + replacement + result.substring(end)
|
|
199
|
-
changed = true
|
|
200
|
-
break // Restart search from beginning
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
i = start + 2 // Move past ${ to continue searching for nested variables
|
|
206
|
-
} else {
|
|
207
|
-
i++
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
i++
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return result
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Recursively traverse and fix the config object
|
|
220
|
-
*/
|
|
221
|
-
function traverseAndFix(obj) {
|
|
222
|
-
if (typeof obj === 'string') {
|
|
223
|
-
return fixFallbacksInString(obj)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (Array.isArray(obj)) {
|
|
227
|
-
return obj.map(item => traverseAndFix(item))
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (obj !== null && typeof obj === 'object') {
|
|
231
|
-
const result = {}
|
|
232
|
-
for (const key in obj) {
|
|
233
|
-
if (obj.hasOwnProperty(key)) {
|
|
234
|
-
result[key] = traverseAndFix(obj[key])
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return result
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return obj
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return traverseAndFix(configObject)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
126
|
class Configorama {
|
|
247
127
|
constructor(fileOrObject, opts) {
|
|
248
128
|
/* attach sig events on async calls */
|
|
@@ -254,17 +134,24 @@ class Configorama {
|
|
|
254
134
|
// Set opts to pass into JS file calls
|
|
255
135
|
this.opts = Object.assign({}, {
|
|
256
136
|
// Allow for unknown variable syntax to pass through without throwing errors
|
|
257
|
-
|
|
137
|
+
allowUnknownVariables: false,
|
|
258
138
|
// Allow undefined to be an end result.
|
|
259
139
|
allowUndefinedValues: false,
|
|
260
140
|
// Allow unknown file refs to pass through without throwing errors
|
|
261
141
|
allowUnknownFileRefs: false,
|
|
142
|
+
// Allow known variable types that can't be resolved to pass through
|
|
143
|
+
allowUnresolvedVariables: false,
|
|
262
144
|
// Return metadata
|
|
263
145
|
returnMetadata: false,
|
|
264
146
|
// Return preResolvedVariableDetails
|
|
265
147
|
returnPreResolvedVariableDetails: false,
|
|
266
148
|
}, options)
|
|
267
149
|
|
|
150
|
+
// Backward compat: allowUnknownVars -> allowUnknownVariables
|
|
151
|
+
if (options.allowUnknownVars !== undefined && options.allowUnknownVariables === undefined) {
|
|
152
|
+
this.opts.allowUnknownVariables = options.allowUnknownVars
|
|
153
|
+
}
|
|
154
|
+
|
|
268
155
|
this.filterCache = {}
|
|
269
156
|
|
|
270
157
|
this.foundVariables = []
|
|
@@ -358,6 +245,7 @@ class Configorama {
|
|
|
358
245
|
*/
|
|
359
246
|
{
|
|
360
247
|
type: 'self',
|
|
248
|
+
source: 'config',
|
|
361
249
|
prefix: 'self',
|
|
362
250
|
syntax: '${self:pathToKeyInConfig}',
|
|
363
251
|
description: `Resolves values from the current config object. Supports sub-properties via :key lookup.`,
|
|
@@ -374,9 +262,10 @@ class Configorama {
|
|
|
374
262
|
*/
|
|
375
263
|
{
|
|
376
264
|
type: 'file',
|
|
265
|
+
source: 'config',
|
|
377
266
|
prefix: 'file',
|
|
378
267
|
syntax: '${file(pathToFile.json)}',
|
|
379
|
-
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.`,
|
|
380
269
|
match: fileRefSyntax,
|
|
381
270
|
resolver: (varString, o, x, pathValue) => {
|
|
382
271
|
return this.getValueFromFile(varString, { context: pathValue })
|
|
@@ -386,6 +275,7 @@ class Configorama {
|
|
|
386
275
|
|
|
387
276
|
{
|
|
388
277
|
type: 'text',
|
|
278
|
+
source: 'config',
|
|
389
279
|
prefix: 'text',
|
|
390
280
|
match: textRefSyntax,
|
|
391
281
|
resolver: (varString, o, x, pathValue) => {
|
|
@@ -421,6 +311,7 @@ class Configorama {
|
|
|
421
311
|
/* Nicer self: references. Match key in object */
|
|
422
312
|
const fallThroughSelfMatcher = {
|
|
423
313
|
type: 'dot.prop',
|
|
314
|
+
source: 'config',
|
|
424
315
|
match: (varString, fullObject, valueObject) => {
|
|
425
316
|
/*
|
|
426
317
|
console.log('fallThroughSelfMatcher varString', varString)
|
|
@@ -542,8 +433,8 @@ class Configorama {
|
|
|
542
433
|
Boolean: (value) => {
|
|
543
434
|
if (typeof value === 'boolean') return value
|
|
544
435
|
const v = String(value).toLowerCase()
|
|
545
|
-
if (['true', '1', 'yes', 'on'].includes(v)) return true
|
|
546
|
-
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
|
|
547
438
|
throw new Error(`Configorama Error: Expected Boolean, got "${value}"`)
|
|
548
439
|
},
|
|
549
440
|
String: (value) => {
|
|
@@ -573,7 +464,9 @@ class Configorama {
|
|
|
573
464
|
// (\|\s*(toUpperCase|toLowerCase|toCamelCase|toKebabCase|capitalize)\s*)+$
|
|
574
465
|
// Updated to support function-style filters like help('text') with nested parens
|
|
575
466
|
// Use a more permissive pattern that matches anything between parens including nested parens
|
|
576
|
-
this.filterMatch = new RegExp(
|
|
467
|
+
this.filterMatch = new RegExp(
|
|
468
|
+
`(\\|\\s*(${Object.keys(this.filters).join('|')})(?:\\s*\\([^)]*(?:\\([^)]*\\))?[^)]*\\))?\\s*)+}?$`
|
|
469
|
+
)
|
|
577
470
|
// console.log('this.filterMatch', this.filterMatch)
|
|
578
471
|
|
|
579
472
|
this.functions = {
|
|
@@ -638,15 +531,9 @@ class Configorama {
|
|
|
638
531
|
this.callCount = 0
|
|
639
532
|
}
|
|
640
533
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return func().finally(() => {
|
|
645
|
-
this.tracker.stop()
|
|
646
|
-
this.deep = []
|
|
647
|
-
})
|
|
648
|
-
}
|
|
649
|
-
|
|
534
|
+
// ################
|
|
535
|
+
// ## PUBLIC API ##
|
|
536
|
+
// ################
|
|
650
537
|
/**
|
|
651
538
|
* Populate all variables in the service, conveniently remove and restore the service attributes
|
|
652
539
|
* that confuse the population methods.
|
|
@@ -658,6 +545,7 @@ class Configorama {
|
|
|
658
545
|
const configoramaOpts = this.opts
|
|
659
546
|
|
|
660
547
|
const showFoundVariables = configoramaOpts && configoramaOpts.dynamicArgs && (configoramaOpts.dynamicArgs.list || configoramaOpts.dynamicArgs.info)
|
|
548
|
+
|
|
661
549
|
|
|
662
550
|
// If we have a file path but no config yet, parse it now
|
|
663
551
|
if (this.configFilePath && !this.config) {
|
|
@@ -669,13 +557,16 @@ class Configorama {
|
|
|
669
557
|
this.opts
|
|
670
558
|
)
|
|
671
559
|
this.configFileContents = ''
|
|
672
|
-
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails) {
|
|
560
|
+
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails || SETUP_MODE) {
|
|
673
561
|
this.configFileContents = fs.readFileSync(this.configFilePath, 'utf8')
|
|
674
562
|
}
|
|
675
563
|
/*
|
|
676
564
|
console.log('before preprocess', configObject)
|
|
677
565
|
/** */
|
|
678
|
-
|
|
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 */
|
|
679
570
|
configObject = preProcess(configObject, this.variableSyntax)
|
|
680
571
|
/*
|
|
681
572
|
console.log('after preprocess', configObject)
|
|
@@ -696,22 +587,24 @@ class Configorama {
|
|
|
696
587
|
const variableSyntax = this.variableSyntax
|
|
697
588
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
698
589
|
|
|
699
|
-
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails) {
|
|
700
|
-
// Use collectVariableMetadata to get variable info (DRY - don't duplicate logic)
|
|
590
|
+
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails || SETUP_MODE) {
|
|
701
591
|
const metadata = this.collectVariableMetadata()
|
|
702
592
|
|
|
703
|
-
const enrich = enrichMetadata(
|
|
593
|
+
const enrich = await enrichMetadata(
|
|
704
594
|
metadata,
|
|
705
595
|
this.resolutionTracking,
|
|
706
596
|
this.variableSyntax,
|
|
707
597
|
this.fileRefsFound,
|
|
708
598
|
this.originalConfig,
|
|
709
599
|
this.configFilePath,
|
|
710
|
-
Object.keys(this.filters)
|
|
600
|
+
Object.keys(this.filters),
|
|
601
|
+
undefined, // resolvedConfig not available yet
|
|
602
|
+
this.opts.options,
|
|
603
|
+
this.variableTypes
|
|
711
604
|
)
|
|
712
605
|
|
|
713
606
|
if (showFoundVariables) {
|
|
714
|
-
|
|
607
|
+
//*
|
|
715
608
|
deepLog('metadata', metadata)
|
|
716
609
|
fs.writeFileSync(`metadata-${path.basename(this.configFilePath)}.json`, JSON.stringify(metadata, null, 2))
|
|
717
610
|
deepLog('enrich', enrich)
|
|
@@ -725,7 +618,10 @@ class Configorama {
|
|
|
725
618
|
const uniqueVarKeys = Object.keys(uniqueVariables)
|
|
726
619
|
|
|
727
620
|
if (this.opts.returnPreResolvedVariableDetails) {
|
|
728
|
-
return
|
|
621
|
+
return Object.assign({}, {
|
|
622
|
+
resolved: false,
|
|
623
|
+
originalConfig: this.originalConfig
|
|
624
|
+
}, enrich)
|
|
729
625
|
}
|
|
730
626
|
|
|
731
627
|
if (!varKeys.length) {
|
|
@@ -751,6 +647,10 @@ class Configorama {
|
|
|
751
647
|
console.log()
|
|
752
648
|
}
|
|
753
649
|
|
|
650
|
+
const lines = this.configFileContents ? this.configFileContents.split('\n') : []
|
|
651
|
+
const fileType = this.configFileType
|
|
652
|
+
const configFilePath = this.configFilePath
|
|
653
|
+
|
|
754
654
|
if (varKeys.length > 0) {
|
|
755
655
|
const fileName = this.configFilePath ? ` in ${this.configFilePath}` : ''
|
|
756
656
|
|
|
@@ -781,8 +681,6 @@ class Configorama {
|
|
|
781
681
|
|
|
782
682
|
logHeader('Variable Details')
|
|
783
683
|
|
|
784
|
-
const lines = this.configFileContents ? this.configFileContents.split('\n') : []
|
|
785
|
-
|
|
786
684
|
const indent = ''
|
|
787
685
|
const boxes = varKeys.map((key, i) => {
|
|
788
686
|
const variableInstances = variableData[key]
|
|
@@ -821,7 +719,12 @@ class Configorama {
|
|
|
821
719
|
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
822
720
|
}
|
|
823
721
|
|
|
824
|
-
|
|
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
|
+
}
|
|
825
728
|
|
|
826
729
|
// Show default value from metadata
|
|
827
730
|
if (typeof firstInstance.defaultValue !== 'undefined') {
|
|
@@ -832,34 +735,39 @@ class Configorama {
|
|
|
832
735
|
|
|
833
736
|
// Show default value source path from metadata
|
|
834
737
|
if (firstInstance.defaultValueSrc) {
|
|
835
|
-
varMsg += `${indent}${keyChalk('Default
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const resolveOrder = firstInstance.resolveOrder.join(', ')
|
|
843
|
-
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
|
+
}
|
|
844
745
|
}
|
|
845
746
|
|
|
846
747
|
// Show path(s) from metadata
|
|
847
|
-
|
|
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)
|
|
848
752
|
let locationLabel = `${indent}${keyChalk('Config Path:'.padEnd(titleText.length, ' '))}`
|
|
849
753
|
let typeText = ''
|
|
850
754
|
if (variableInstances.length > 1) {
|
|
851
755
|
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
852
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}`)
|
|
853
761
|
// Show type filter per path if different
|
|
854
762
|
if (uniqueVar && uniqueVar.occurrences.length > 1) {
|
|
855
763
|
const occurrence = uniqueVar.occurrences.find(occ => occ.path === v.path)
|
|
856
764
|
const pathType = occurrence && occurrence.type
|
|
857
765
|
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
858
766
|
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
859
|
-
return `${prefix}${
|
|
767
|
+
return `${prefix}${pathLink}${typeText}`
|
|
860
768
|
}
|
|
861
769
|
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
862
|
-
return `${prefix}${
|
|
770
|
+
return `${prefix}${pathLink}${typeText}`
|
|
863
771
|
})
|
|
864
772
|
locationRender = pathItems.join('\n')
|
|
865
773
|
locationLabel = `${indent}${keyChalk('Config Paths:'.padEnd(titleText.length, ' '))}`
|
|
@@ -869,36 +777,7 @@ class Configorama {
|
|
|
869
777
|
}
|
|
870
778
|
varMsg += `${locationLabel} ${locationRender}`
|
|
871
779
|
|
|
872
|
-
|
|
873
|
-
const configKey = firstInstance.key
|
|
874
|
-
const line = lines.findIndex((line) => {
|
|
875
|
-
const fileType = this.configFileType
|
|
876
|
-
const escapedKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
877
|
-
// YAML: key: or key :
|
|
878
|
-
if (fileType === '.yml' || fileType === '.yaml') {
|
|
879
|
-
return new RegExp(`^\\s*${escapedKey}\\s*:`).test(line)
|
|
880
|
-
}
|
|
881
|
-
// TOML: key = or key=
|
|
882
|
-
if (fileType === '.toml') {
|
|
883
|
-
return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
|
|
884
|
-
}
|
|
885
|
-
// JSON: "key": or "key" :
|
|
886
|
-
if (fileType === '.json' || fileType === '.json5') {
|
|
887
|
-
return new RegExp(`"${escapedKey}"\\s*:`).test(line)
|
|
888
|
-
}
|
|
889
|
-
// INI: key = or key=
|
|
890
|
-
if (fileType === '.ini') {
|
|
891
|
-
return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
|
|
892
|
-
}
|
|
893
|
-
// JS/TS/ESM: key: or "key": or 'key': or `key`: or [`key`]:
|
|
894
|
-
if (['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(fileType)) {
|
|
895
|
-
return new RegExp(`(?:${escapedKey}|"${escapedKey}"|'${escapedKey}'|\`${escapedKey}\`|\\[\`${escapedKey}\`\\])\\s*:`).test(line)
|
|
896
|
-
}
|
|
897
|
-
// Default fallback: try YAML-style
|
|
898
|
-
return line.includes(`${configKey}:`)
|
|
899
|
-
})
|
|
900
|
-
const lineNumber = line !== -1 ? line + 1 : 0
|
|
901
|
-
|
|
780
|
+
const lineNumber = findLineForKey(firstInstance.key, lines, fileType)
|
|
902
781
|
|
|
903
782
|
return {
|
|
904
783
|
content: {
|
|
@@ -928,6 +807,276 @@ class Configorama {
|
|
|
928
807
|
// process.exit(1)
|
|
929
808
|
}
|
|
930
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
|
+
|
|
931
1080
|
|
|
932
1081
|
// WALK through CLI prompt if --setup flag is set
|
|
933
1082
|
if (SETUP_MODE) {
|
|
@@ -935,20 +1084,29 @@ class Configorama {
|
|
|
935
1084
|
// deepLog('enrich', enrich)
|
|
936
1085
|
const userInputs = await runConfigWizard(enrich, this.originalConfig, this.configFilePath)
|
|
937
1086
|
|
|
938
|
-
console.log('\n')
|
|
939
1087
|
logHeader('User Inputs Summary')
|
|
1088
|
+
console.log()
|
|
940
1089
|
console.log(JSON.stringify(userInputs, null, 2))
|
|
941
1090
|
|
|
942
1091
|
// TODO set values
|
|
943
1092
|
|
|
944
1093
|
// Apply user inputs to options and environment
|
|
945
1094
|
if (userInputs.options) {
|
|
946
|
-
Object.assign(this.
|
|
1095
|
+
Object.assign(this.options, userInputs.options)
|
|
947
1096
|
}
|
|
948
1097
|
if (userInputs.env) {
|
|
949
1098
|
Object.assign(process.env, userInputs.env)
|
|
950
1099
|
}
|
|
951
|
-
|
|
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
|
+
}
|
|
952
1110
|
|
|
953
1111
|
console.log()
|
|
954
1112
|
logHeader('Resolving Configuration')
|
|
@@ -962,7 +1120,8 @@ class Configorama {
|
|
|
962
1120
|
|
|
963
1121
|
/* Exit early if list or info flag is set */
|
|
964
1122
|
if (showFoundVariables) {
|
|
965
|
-
|
|
1123
|
+
// TODO re-enable this
|
|
1124
|
+
// process.exit(0)
|
|
966
1125
|
}
|
|
967
1126
|
}
|
|
968
1127
|
|
|
@@ -1096,12 +1255,18 @@ class Configorama {
|
|
|
1096
1255
|
* @returns {object} Metadata object containing variables, fileRefs, and summary
|
|
1097
1256
|
*/
|
|
1098
1257
|
collectVariableMetadata() {
|
|
1258
|
+
// Return cached metadata if already computed
|
|
1259
|
+
if (this._cachedMetadata) {
|
|
1260
|
+
return this._cachedMetadata
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1099
1263
|
const variableSyntax = this.variableSyntax
|
|
1100
1264
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
1101
1265
|
const variableTypes = this.variableTypes
|
|
1102
1266
|
const filterMatch = this.filterMatch
|
|
1103
1267
|
const configFilePath = this.configFilePath
|
|
1104
|
-
|
|
1268
|
+
// Use rawOriginalConfig for metadata display (truly original, no escaping)
|
|
1269
|
+
const originalConfig = this.rawOriginalConfig || this.originalConfig
|
|
1105
1270
|
const foundVariables = []
|
|
1106
1271
|
const variableData = {}
|
|
1107
1272
|
const fileRefs = []
|
|
@@ -1111,7 +1276,7 @@ class Configorama {
|
|
|
1111
1276
|
const referencesMap = new Map()
|
|
1112
1277
|
let matchCount = 1
|
|
1113
1278
|
|
|
1114
|
-
traverse(
|
|
1279
|
+
traverse(originalConfig).forEach(function (rawValue) {
|
|
1115
1280
|
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
1116
1281
|
const configValuePath = this.path.join('.')
|
|
1117
1282
|
/* Skip Fn::Sub variables */
|
|
@@ -1152,6 +1317,21 @@ class Configorama {
|
|
|
1152
1317
|
|
|
1153
1318
|
const key = keyWithoutFilters
|
|
1154
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
|
+
|
|
1155
1335
|
// Strip filters from resolveDetails
|
|
1156
1336
|
const cleanedResolveDetails = nested.map(detail => {
|
|
1157
1337
|
const cleaned = { ...detail }
|
|
@@ -1173,6 +1353,14 @@ class Configorama {
|
|
|
1173
1353
|
cleaned.varString = cleaned.varString.replace(filterMatch, '').trim()
|
|
1174
1354
|
}
|
|
1175
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
|
+
|
|
1176
1364
|
// Also clean fallbackValues if present
|
|
1177
1365
|
if (cleaned.fallbackValues && Array.isArray(cleaned.fallbackValues)) {
|
|
1178
1366
|
cleaned.fallbackValues = cleaned.fallbackValues.map(fb => {
|
|
@@ -1195,6 +1383,17 @@ class Configorama {
|
|
|
1195
1383
|
cleanedFb.stringValue = cleanedFb.stringValue.replace(filterMatch, '').trim()
|
|
1196
1384
|
}
|
|
1197
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
|
+
|
|
1198
1397
|
return cleanedFb
|
|
1199
1398
|
})
|
|
1200
1399
|
}
|
|
@@ -1229,35 +1428,59 @@ class Configorama {
|
|
|
1229
1428
|
|
|
1230
1429
|
if (item && item.fallbackValues) {
|
|
1231
1430
|
let hasResolvedFallback
|
|
1431
|
+
let defaultValueSrc
|
|
1432
|
+
const isSingleFallback = item.fallbackValues.length === 1
|
|
1232
1433
|
const order = ([stripFilters(item.valueBeforeFallback)]).concat(item.fallbackValues.map((f, i) => {
|
|
1233
1434
|
if (f.fallbackValues) {
|
|
1234
|
-
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
1435
|
+
const [nestedOrder, nestedResolvedFallback, nestedDefaultSrc] = calculateResolveOrder(f)
|
|
1235
1436
|
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
1236
1437
|
hasResolvedFallback = nestedResolvedFallback
|
|
1438
|
+
defaultValueSrc = nestedDefaultSrc
|
|
1237
1439
|
}
|
|
1238
1440
|
return nestedOrder
|
|
1239
1441
|
}
|
|
1240
1442
|
|
|
1443
|
+
const valueStr = stripFilters(f.stringValue || f.variable)
|
|
1444
|
+
|
|
1445
|
+
// Only set default from first resolvable fallback
|
|
1241
1446
|
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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
|
|
1246
1455
|
}
|
|
1247
1456
|
|
|
1248
1457
|
if (!hasResolvedFallback && f.isVariable) {
|
|
1249
1458
|
defaultValueIsVar = f
|
|
1250
1459
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
|
1253
1475
|
})).flat()
|
|
1254
1476
|
|
|
1255
|
-
return [order, hasResolvedFallback]
|
|
1477
|
+
return [order, hasResolvedFallback, defaultValueSrc]
|
|
1256
1478
|
}
|
|
1257
|
-
return [[stripFilters(item.variable)], undefined]
|
|
1479
|
+
return [[stripFilters(item.variable)], undefined, undefined]
|
|
1258
1480
|
}
|
|
1259
1481
|
|
|
1260
|
-
const [
|
|
1482
|
+
const lastCleanedItem = cleanedResolveDetails[cleanedResolveDetails.length - 1]
|
|
1483
|
+
const [resolveOrder, hasResolvedFallback, defaultValueSrc] = calculateResolveOrder(lastCleanedItem)
|
|
1261
1484
|
varData.resolveOrder = resolveOrder
|
|
1262
1485
|
|
|
1263
1486
|
if (defaultValueIsVar) {
|
|
@@ -1268,6 +1491,10 @@ class Configorama {
|
|
|
1268
1491
|
varData.defaultValue = hasResolvedFallback
|
|
1269
1492
|
}
|
|
1270
1493
|
|
|
1494
|
+
if (defaultValueSrc) {
|
|
1495
|
+
varData.defaultValueSrc = defaultValueSrc
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1271
1498
|
if (typeof varData.defaultValue === 'undefined') {
|
|
1272
1499
|
varData.isRequired = true
|
|
1273
1500
|
}
|
|
@@ -1369,50 +1596,58 @@ class Configorama {
|
|
|
1369
1596
|
const instances = variableData[key]
|
|
1370
1597
|
const firstInstance = instances[0]
|
|
1371
1598
|
|
|
1372
|
-
//
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
if (v.resolveDetails && v.resolveDetails.length > 0) {
|
|
1387
|
-
const outermostDetail = v.resolveDetails[v.resolveDetails.length - 1]
|
|
1388
|
-
if (outermostDetail.variableType === 'dot.prop' || outermostDetail.variableType === 'self') {
|
|
1389
|
-
acc.push(outermostDetail)
|
|
1390
|
-
}
|
|
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
|
|
1391
1613
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
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
|
+
}
|
|
1394
1624
|
|
|
1395
|
-
|
|
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') {
|
|
1396
1634
|
isTrulyRequired = true
|
|
1397
1635
|
} else {
|
|
1398
|
-
//
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
// Enrich with default value from self-reference
|
|
1405
|
-
firstInstance.defaultValueSrc = cleanPath
|
|
1406
|
-
const niceString = typeof dotPropValue === 'object' ? JSON.stringify(dotPropValue) : dotPropValue
|
|
1407
|
-
const truncatedString = niceString.length > 100 ? niceString.substring(0, 90) + '...' : niceString
|
|
1408
|
-
firstInstance.defaultValue = truncatedString
|
|
1409
|
-
firstInstance.isRequired = false
|
|
1410
|
-
}
|
|
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
|
+
})
|
|
1411
1642
|
}
|
|
1643
|
+
} else if (typeof firstInstance.defaultValue === 'undefined') {
|
|
1644
|
+
isTrulyRequired = true
|
|
1412
1645
|
}
|
|
1413
1646
|
|
|
1414
1647
|
// Update isRequired based on computed isTrulyRequired
|
|
1415
|
-
|
|
1648
|
+
instances.forEach((instance) => {
|
|
1649
|
+
instance.isRequired = isTrulyRequired
|
|
1650
|
+
})
|
|
1416
1651
|
|
|
1417
1652
|
if (isTrulyRequired) {
|
|
1418
1653
|
requiredCount++
|
|
@@ -1421,7 +1656,7 @@ class Configorama {
|
|
|
1421
1656
|
}
|
|
1422
1657
|
})
|
|
1423
1658
|
|
|
1424
|
-
|
|
1659
|
+
this._cachedMetadata = {
|
|
1425
1660
|
variables: variableData,
|
|
1426
1661
|
uniqueVariables: {},
|
|
1427
1662
|
fileDependencies: {
|
|
@@ -1442,61 +1677,44 @@ class Configorama {
|
|
|
1442
1677
|
variablesWithDefaults: withDefaultsCount
|
|
1443
1678
|
},
|
|
1444
1679
|
}
|
|
1680
|
+
|
|
1681
|
+
return this._cachedMetadata
|
|
1445
1682
|
}
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
const functionName = hasFunc[1]
|
|
1461
|
-
const rawArgs = hasFunc[2]
|
|
1462
|
-
// TODO @DWELLS. Loop through all raw args and parse to correct datatype
|
|
1463
|
-
// argument is object
|
|
1464
|
-
let argsToPass
|
|
1465
|
-
if (rawArgs && rawArgs.match(/^{([^}]+)}$/)) {
|
|
1466
|
-
// console.log('OBJECT', hasFunc[2])
|
|
1467
|
-
// TODO use JSON5
|
|
1468
|
-
argsToPass = [JSON.parse(rawArgs)]
|
|
1469
|
-
} else {
|
|
1470
|
-
// TODO fix how commas + spaces are ned
|
|
1471
|
-
const splitter = splitCsv(rawArgs, ', ')
|
|
1472
|
-
// console.log('splitter', splitter)
|
|
1473
|
-
argsToPass = formatFunctionArgs(splitter)
|
|
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
|
|
1693
|
+
|
|
1694
|
+
if (DEBUG) {
|
|
1695
|
+
deepLog(`objectToPopulate called ${this.callCount} times`, objectToPopulate)
|
|
1696
|
+
// process.exit(0)
|
|
1474
1697
|
}
|
|
1475
|
-
// console.log('argsToPass runFunction', argsToPass)
|
|
1476
|
-
// TODO check for camelCase version. | toUpperCase messes with function name
|
|
1477
|
-
const theFunction = this.functions[functionName] || this.functions[functionName.toLowerCase()]
|
|
1478
1698
|
|
|
1479
|
-
|
|
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)
|
|
1480
1704
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
let replaceVal = funcValue
|
|
1485
|
-
if (typeof funcValue === 'string') {
|
|
1486
|
-
const replaceIt = variableString.replace(hasFunc[0], funcValue)
|
|
1487
|
-
replaceVal = cleanVariable(replaceIt, this.variableSyntax, true, `runFunction ${this.callCount}`)
|
|
1705
|
+
if (populations.length === 0) {
|
|
1706
|
+
if (DEBUG) console.log('Config Population Finished')
|
|
1707
|
+
return Promise.resolve(objectToPopulate)
|
|
1488
1708
|
}
|
|
1489
1709
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
return this.runFunction(replaceVal)
|
|
1494
|
-
}
|
|
1495
|
-
return replaceVal
|
|
1710
|
+
return this.assignProperties(objectToPopulate, populations).then(() => {
|
|
1711
|
+
return this.populateObjectImpl(objectToPopulate)
|
|
1712
|
+
})
|
|
1496
1713
|
}
|
|
1497
|
-
|
|
1498
|
-
//
|
|
1499
|
-
//
|
|
1714
|
+
|
|
1715
|
+
// #######################
|
|
1716
|
+
// ## PROPERTY HANDLING ##
|
|
1717
|
+
// #######################
|
|
1500
1718
|
/**
|
|
1501
1719
|
* The declaration of a terminal property. This declaration includes the path and value of the
|
|
1502
1720
|
* property.
|
|
@@ -1644,40 +1862,9 @@ class Configorama {
|
|
|
1644
1862
|
})
|
|
1645
1863
|
})
|
|
1646
1864
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
* @returns {Promise.<TResult>|*} A promise resolving to the in-place populated object.
|
|
1651
|
-
*/
|
|
1652
|
-
populateObject(objectToPopulate) {
|
|
1653
|
-
return this.initialCall(() => this.populateObjectImpl(objectToPopulate))
|
|
1654
|
-
}
|
|
1655
|
-
populateObjectImpl(objectToPopulate) {
|
|
1656
|
-
this.callCount = this.callCount + 1
|
|
1657
|
-
|
|
1658
|
-
if (DEBUG) {
|
|
1659
|
-
deepLog(`objectToPopulate called ${this.callCount} times`, objectToPopulate)
|
|
1660
|
-
// process.exit(0)
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
const leaves = this.getProperties(objectToPopulate, true, objectToPopulate)
|
|
1664
|
-
this.leaves = leaves
|
|
1665
|
-
// console.log('leaves', leaves)
|
|
1666
|
-
const populations = this.populateVariables(leaves)
|
|
1667
|
-
// console.log("FILL LEAVES", populations)
|
|
1668
|
-
|
|
1669
|
-
if (populations.length === 0) {
|
|
1670
|
-
if (DEBUG) console.log('Config Population Finished')
|
|
1671
|
-
return Promise.resolve(objectToPopulate)
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
return this.assignProperties(objectToPopulate, populations).then(() => {
|
|
1675
|
-
return this.populateObjectImpl(objectToPopulate)
|
|
1676
|
-
})
|
|
1677
|
-
}
|
|
1678
|
-
// ##############
|
|
1679
|
-
// ## PROPERTY ##
|
|
1680
|
-
// ##############
|
|
1865
|
+
// ##################
|
|
1866
|
+
// ## MATCH/RENDER ##
|
|
1867
|
+
// ##################
|
|
1681
1868
|
/**
|
|
1682
1869
|
* @typedef {Object} MatchResult
|
|
1683
1870
|
* @property {String} match The original property value that matched the variable syntax
|
|
@@ -1740,7 +1927,17 @@ class Configorama {
|
|
|
1740
1927
|
|
|
1741
1928
|
let result = valueObject.value
|
|
1742
1929
|
for (let i = 0; i < matches.length; i += 1) {
|
|
1743
|
-
|
|
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
|
+
})
|
|
1744
1941
|
|
|
1745
1942
|
// Extract metadata from result if present
|
|
1746
1943
|
let actualResult = results[i]
|
|
@@ -1884,6 +2081,10 @@ class Configorama {
|
|
|
1884
2081
|
|
|
1885
2082
|
return result
|
|
1886
2083
|
}
|
|
2084
|
+
|
|
2085
|
+
// ######################
|
|
2086
|
+
// ## VALUE RESOLUTION ##
|
|
2087
|
+
// ######################
|
|
1887
2088
|
/**
|
|
1888
2089
|
* Populate the given value, recursively if root is true
|
|
1889
2090
|
* @param valueObject The value to populate variables within
|
|
@@ -2001,7 +2202,8 @@ class Configorama {
|
|
|
2001
2202
|
const hasFilters = originalSrc.match(this.filterMatch)
|
|
2002
2203
|
let foundFilters = []
|
|
2003
2204
|
if (hasFilters) {
|
|
2004
|
-
foundFilters = hasFilters[
|
|
2205
|
+
foundFilters = hasFilters[0]
|
|
2206
|
+
.replace(/}$/, '') // remove trailing }
|
|
2005
2207
|
.split('|')
|
|
2006
2208
|
.map((filter) => filter.trim())
|
|
2007
2209
|
.filter(Boolean)
|
|
@@ -2156,7 +2358,7 @@ class Configorama {
|
|
|
2156
2358
|
|
|
2157
2359
|
if (nestedVar) {
|
|
2158
2360
|
const fallbackStr = getFallbackString(splitVars, nestedVar)
|
|
2159
|
-
if (!this.opts.
|
|
2361
|
+
if (!this.opts.allowUnknownVariables) {
|
|
2160
2362
|
verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
|
|
2161
2363
|
}
|
|
2162
2364
|
|
|
@@ -2268,14 +2470,13 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2268
2470
|
if (typeof valueToPopulate === 'number' && foundFilters.length) {
|
|
2269
2471
|
runFilters = true
|
|
2270
2472
|
} else if (
|
|
2271
|
-
typeof valueToPopulate === 'string' &&
|
|
2272
|
-
!valueToPopulate.match(deepRefSyntax) &&
|
|
2273
|
-
foundFilters.length &&
|
|
2473
|
+
typeof valueToPopulate === 'string' &&
|
|
2474
|
+
!valueToPopulate.match(deepRefSyntax) &&
|
|
2475
|
+
foundFilters.length &&
|
|
2274
2476
|
!property.match(this.variableSyntax)
|
|
2275
2477
|
) {
|
|
2276
2478
|
runFilters = true
|
|
2277
2479
|
}
|
|
2278
|
-
|
|
2279
2480
|
/* Apply filters if found */
|
|
2280
2481
|
//console.log('> property', property)
|
|
2281
2482
|
if (runFilters) {
|
|
@@ -2395,6 +2596,10 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2395
2596
|
: Promise.resolve(extractedValues.find(isValidValue)) // resolve first valid value, else undefined
|
|
2396
2597
|
})
|
|
2397
2598
|
}
|
|
2599
|
+
|
|
2600
|
+
// ####################
|
|
2601
|
+
// ## SOURCE GETTERS ##
|
|
2602
|
+
// ####################
|
|
2398
2603
|
/**
|
|
2399
2604
|
* Given any variable string, return the value it should be populated with.
|
|
2400
2605
|
* @param variableString The variable string to retrieve a value for.
|
|
@@ -2598,6 +2803,11 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2598
2803
|
return Promise.resolve(encodeUnknown(propertyString))
|
|
2599
2804
|
}
|
|
2600
2805
|
|
|
2806
|
+
if (this.opts.allowUnresolvedVariables) {
|
|
2807
|
+
// Encode unresolved variable to pass through resolution
|
|
2808
|
+
return Promise.resolve(encodeUnknown(propertyString))
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2601
2811
|
if (valueCount.length === 1 && noNestedVars) {
|
|
2602
2812
|
const configFilePathMsg = (this.configFilePath) ? `\nIn file ${this.configFilePath} ` : ''
|
|
2603
2813
|
const fromLine = (propertyString !== valueObject.originalSource) ? `\n From "${valueObject.originalSource}"\n` : ''
|
|
@@ -2760,7 +2970,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2760
2970
|
// console.log('nestedVar', nestedVar)
|
|
2761
2971
|
|
|
2762
2972
|
if (nestedVar) {
|
|
2763
|
-
if (!this.opts.
|
|
2973
|
+
if (!this.opts.allowUnknownVariables) {
|
|
2764
2974
|
verifyVariable(nestedVar, valueObject, this.variableTypes, this.config)
|
|
2765
2975
|
}
|
|
2766
2976
|
const fallbackStr = getFallbackString(split, nestedVar)
|
|
@@ -2863,7 +3073,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2863
3073
|
|
|
2864
3074
|
|
|
2865
3075
|
/* Pass through unknown variables */
|
|
2866
|
-
if (this.opts.
|
|
3076
|
+
if (this.opts.allowUnknownVariables || allowSpecialCase) {
|
|
2867
3077
|
// console.log('allowUnknownVars propertyString', propertyString)
|
|
2868
3078
|
const varMatches = propertyString.match(this.variableSyntax)
|
|
2869
3079
|
let allowUnknownVars = propertyString
|
|
@@ -2915,331 +3125,20 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2915
3125
|
})
|
|
2916
3126
|
}
|
|
2917
3127
|
async getValueFromFile(variableString, options) {
|
|
2918
|
-
const
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
const splitter = splitCsv(hasParams[2])
|
|
2933
|
-
const argsFound = splitter.map((arg) => {
|
|
2934
|
-
const cleanArg = trim(arg).replace(/^'|"/, '').replace(/'|"$/, '')
|
|
2935
|
-
return cleanArg
|
|
2936
|
-
})
|
|
2937
|
-
// console.log('argsFound', argsFound)
|
|
2938
|
-
|
|
2939
|
-
// If function has more arguments than file path
|
|
2940
|
-
if (argsFound.length && argsFound.length > 1) {
|
|
2941
|
-
matchedFileString = argsFound[0]
|
|
2942
|
-
argsToPass = argsFound.filter((arg, i) => {
|
|
2943
|
-
return i !== 0
|
|
2944
|
-
})
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
// console.log('argsToPass', argsToPass)
|
|
2948
|
-
|
|
2949
|
-
const fileDetails = resolveFilePathFromMatch(matchedFileString, syntax, this.configPath)
|
|
2950
|
-
// console.log('fileDetails', fileDetails)
|
|
2951
|
-
|
|
2952
|
-
const { fullFilePath, resolvedPath, relativePath } = fileDetails
|
|
2953
|
-
|
|
2954
|
-
const exists = fs.existsSync(fullFilePath)
|
|
2955
|
-
|
|
2956
|
-
this.fileRefsFound.push({
|
|
2957
|
-
// location: options.context.path.join('.'),
|
|
2958
|
-
filePath: fullFilePath,
|
|
2959
|
-
relativePath,
|
|
2960
|
-
resolvedVariableString: options.context.value,
|
|
2961
|
-
originalVariableString: options.context.originalSource,
|
|
2962
|
-
containsVariables: options.context.value !== options.context.originalSource,
|
|
2963
|
-
exists,
|
|
2964
|
-
})
|
|
2965
|
-
|
|
2966
|
-
let fileExtension = resolvedPath.split('.')
|
|
2967
|
-
|
|
2968
|
-
fileExtension = fileExtension[fileExtension.length - 1]
|
|
2969
|
-
|
|
2970
|
-
// Validate file exists
|
|
2971
|
-
if (!exists) {
|
|
2972
|
-
const originalVar = options.context && options.context.originalSource
|
|
2973
|
-
|
|
2974
|
-
const findNestedResult = findNestedVariables(
|
|
2975
|
-
originalVar,
|
|
2976
|
-
this.variableSyntax,
|
|
2977
|
-
this.variablesKnownTypes,
|
|
2978
|
-
options.context.path,
|
|
2979
|
-
this.variableTypes
|
|
2980
|
-
)
|
|
2981
|
-
// console.log('findNestedResult', findNestedResult)
|
|
2982
|
-
let hasFallback = false
|
|
2983
|
-
if (findNestedResult) {
|
|
2984
|
-
const varDetails = findNestedResult[0]
|
|
2985
|
-
// console.log('varDetails', varDetails)
|
|
2986
|
-
hasFallback = varDetails.hasFallback
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2989
|
-
// check if original var has fallback value
|
|
2990
|
-
// console.log('NO FILE FOUND', fullFilePath)
|
|
2991
|
-
// console.log('variableString', variableString)
|
|
2992
|
-
|
|
2993
|
-
if (!hasFallback && !this.opts.allowUnknownFileRefs) {
|
|
2994
|
-
const errorMsg = makeBox({
|
|
2995
|
-
title: `File Not Found in ${originalVar}`,
|
|
2996
|
-
minWidth: '100%',
|
|
2997
|
-
text: `Variable ${variableString} cannot resolve due to missing file.
|
|
2998
|
-
|
|
2999
|
-
File not found ${fullFilePath}
|
|
3000
|
-
|
|
3001
|
-
Default fallback value will be used if provided.
|
|
3002
|
-
|
|
3003
|
-
${JSON.stringify(options.context, null, 2)}`,
|
|
3004
|
-
})
|
|
3005
|
-
console.log(errorMsg)
|
|
3006
|
-
}
|
|
3007
|
-
// TODO maybe reject. YAML does not allow for null/undefined values
|
|
3008
|
-
// return Promise.reject(new Error(errorMsg))
|
|
3009
|
-
return Promise.resolve(undefined)
|
|
3010
|
-
}
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
let valueToPopulate
|
|
3015
|
-
|
|
3016
|
-
const variableFileContents = fs.readFileSync(fullFilePath, 'utf-8')
|
|
3017
|
-
|
|
3018
|
-
/* handle case for referencing raw JS files to inline them */
|
|
3019
|
-
if (argsToPass.length
|
|
3020
|
-
&& (argsToPass && argsToPass[0] && argsToPass[0].toLowerCase() === 'raw')
|
|
3021
|
-
|| opts.asRawText
|
|
3022
|
-
) {
|
|
3023
|
-
// Encode foo() to foo__PH_PAREN_OPEN__) to avoid function collisions
|
|
3024
|
-
valueToPopulate = encodeJsSyntax(variableFileContents)
|
|
3025
|
-
return Promise.resolve(valueToPopulate)
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3028
|
-
// Process JS files
|
|
3029
|
-
if (fileExtension === 'js' || fileExtension === 'cjs') {
|
|
3030
|
-
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
3031
|
-
const jsFile = require(fullFilePath)
|
|
3032
|
-
let returnValueFunction = jsFile
|
|
3033
|
-
// TODO change how exported functions are referenced
|
|
3034
|
-
const variableArray = variableString.split(':')
|
|
3035
|
-
|
|
3036
|
-
if (variableArray[1]) {
|
|
3037
|
-
let jsModule = variableArray[1]
|
|
3038
|
-
jsModule = jsModule.split('.')[0]
|
|
3039
|
-
returnValueFunction = jsFile[jsModule]
|
|
3040
|
-
}
|
|
3041
|
-
|
|
3042
|
-
if (typeof returnValueFunction !== 'function') {
|
|
3043
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3044
|
-
Check if your javascript is exporting a function that returns a value.`
|
|
3045
|
-
return Promise.reject(new Error(errorMessage))
|
|
3046
|
-
}
|
|
3047
|
-
// TODO update what is passed into function
|
|
3048
|
-
|
|
3049
|
-
const valueForFunction = {
|
|
3050
|
-
originalConfig: this.originalConfig,
|
|
3051
|
-
config: this.config,
|
|
3052
|
-
opts: this.opts,
|
|
3053
|
-
}
|
|
3054
|
-
|
|
3055
|
-
valueToPopulate = returnValueFunction.call(jsFile, valueForFunction, ...argsToPass)
|
|
3056
|
-
|
|
3057
|
-
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
3058
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3059
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3060
|
-
deepProperties.splice(0, 1)
|
|
3061
|
-
// Trim prop keys for starting/trailing spaces
|
|
3062
|
-
deepProperties = deepProperties.map((prop) => {
|
|
3063
|
-
return trim(prop)
|
|
3064
|
-
})
|
|
3065
|
-
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
3066
|
-
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
3067
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3068
|
-
Check if your javascript is returning the correct data.`
|
|
3069
|
-
return Promise.reject(new Error(errorMessage))
|
|
3070
|
-
}
|
|
3071
|
-
return Promise.resolve(deepValueToPopulateResolved)
|
|
3072
|
-
})
|
|
3073
|
-
})
|
|
3074
|
-
}
|
|
3075
|
-
|
|
3076
|
-
if (fileExtension === 'ts') {
|
|
3077
|
-
const { executeTypeScriptFile } = require('./parsers/typescript')
|
|
3078
|
-
let returnValueFunction
|
|
3079
|
-
const variableArray = variableString.split(':')
|
|
3080
|
-
|
|
3081
|
-
try {
|
|
3082
|
-
const tsFile = await executeTypeScriptFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
3083
|
-
// console.log('fullFilePath', fullFilePath)
|
|
3084
|
-
// console.log('tsFile', tsFile)
|
|
3085
|
-
returnValueFunction = tsFile.config || tsFile.default || tsFile
|
|
3086
|
-
|
|
3087
|
-
if (variableArray[1]) {
|
|
3088
|
-
let tsModule = variableArray[1]
|
|
3089
|
-
tsModule = tsModule.split('.')[0]
|
|
3090
|
-
returnValueFunction = tsFile[tsModule]
|
|
3091
|
-
}
|
|
3092
|
-
|
|
3093
|
-
if (typeof returnValueFunction !== 'function') {
|
|
3094
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3095
|
-
Check if your TypeScript is exporting a function that returns a value.`
|
|
3096
|
-
return Promise.reject(new Error(errorMessage))
|
|
3097
|
-
}
|
|
3098
|
-
|
|
3099
|
-
const valueForFunction = {
|
|
3100
|
-
originalConfig: this.originalConfig,
|
|
3101
|
-
config: this.config,
|
|
3102
|
-
opts: this.opts,
|
|
3103
|
-
}
|
|
3104
|
-
|
|
3105
|
-
valueToPopulate = returnValueFunction.call(tsFile, valueForFunction, ...argsToPass)
|
|
3106
|
-
|
|
3107
|
-
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
3108
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3109
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3110
|
-
deepProperties.splice(0, 1)
|
|
3111
|
-
// Trim prop keys for starting/trailing spaces
|
|
3112
|
-
deepProperties = deepProperties.map((prop) => {
|
|
3113
|
-
return trim(prop)
|
|
3114
|
-
})
|
|
3115
|
-
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
3116
|
-
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
3117
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3118
|
-
Check if your TypeScript is returning the correct data.`
|
|
3119
|
-
return Promise.reject(new Error(errorMessage))
|
|
3120
|
-
}
|
|
3121
|
-
return Promise.resolve(deepValueToPopulateResolved)
|
|
3122
|
-
})
|
|
3123
|
-
})
|
|
3124
|
-
} catch (err) {
|
|
3125
|
-
return Promise.reject(new Error(`Error processing TypeScript file: ${err.message}`))
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
|
-
if (fileExtension === 'mjs' || fileExtension === 'esm') {
|
|
3130
|
-
// Possible alt importer tool https://github.com/humanwhocodes/module-importer
|
|
3131
|
-
const { executeESMFile } = require('./parsers/esm')
|
|
3132
|
-
let returnValueFunction
|
|
3133
|
-
const variableArray = variableString.split(':')
|
|
3134
|
-
|
|
3135
|
-
try {
|
|
3136
|
-
const esmFile = await executeESMFile(fullFilePath, { dynamicArgs: () => argsToPass })
|
|
3137
|
-
// console.log('ESM fullFilePath', fullFilePath)
|
|
3138
|
-
// console.log('ESM esmFile', esmFile, 'type:', typeof esmFile)
|
|
3139
|
-
returnValueFunction = esmFile.config || esmFile.default || esmFile
|
|
3140
|
-
|
|
3141
|
-
if (variableArray[1]) {
|
|
3142
|
-
let esmModule = variableArray[1]
|
|
3143
|
-
esmModule = esmModule.split('.')[0]
|
|
3144
|
-
returnValueFunction = esmFile[esmModule]
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
|
-
if (typeof returnValueFunction !== 'function') {
|
|
3148
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3149
|
-
Check if your ESM is exporting a function that returns a value.`
|
|
3150
|
-
return Promise.reject(new Error(errorMessage))
|
|
3151
|
-
}
|
|
3152
|
-
|
|
3153
|
-
const valueForFunction = {
|
|
3154
|
-
originalConfig: this.originalConfig,
|
|
3155
|
-
config: this.config,
|
|
3156
|
-
opts: this.opts,
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
valueToPopulate = returnValueFunction.call(esmFile, valueForFunction, ...argsToPass)
|
|
3160
|
-
|
|
3161
|
-
return Promise.resolve(valueToPopulate).then((valueToPopulateResolved) => {
|
|
3162
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3163
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3164
|
-
deepProperties.splice(0, 1)
|
|
3165
|
-
// Trim prop keys for starting/trailing spaces
|
|
3166
|
-
deepProperties = deepProperties.map((prop) => {
|
|
3167
|
-
return trim(prop)
|
|
3168
|
-
})
|
|
3169
|
-
return this.getDeeperValue(deepProperties, valueToPopulateResolved).then((deepValueToPopulateResolved) => {
|
|
3170
|
-
if (typeof deepValueToPopulateResolved === 'undefined') {
|
|
3171
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}".
|
|
3172
|
-
Check if your ESM is returning the correct data.`
|
|
3173
|
-
return Promise.reject(new Error(errorMessage))
|
|
3174
|
-
}
|
|
3175
|
-
return Promise.resolve(deepValueToPopulateResolved)
|
|
3176
|
-
})
|
|
3177
|
-
})
|
|
3178
|
-
} catch (err) {
|
|
3179
|
-
return Promise.reject(new Error(`Error processing ESM file: ${err.message}`))
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
|
|
3183
|
-
// Process everything except JS, TS, and ESM
|
|
3184
|
-
if (fileExtension !== 'js' && fileExtension !== 'ts' && fileExtension !== 'mjs' && fileExtension !== 'esm') {
|
|
3185
|
-
/* Read initial file */
|
|
3186
|
-
valueToPopulate = variableFileContents
|
|
3187
|
-
|
|
3188
|
-
// File reference has :subKey lookup. Must dig deeper
|
|
3189
|
-
if (matchedFileString !== variableString) {
|
|
3190
|
-
if (fileExtension === 'yml' || fileExtension === 'yaml') {
|
|
3191
|
-
valueToPopulate = JSON.stringify(YAML.parse(valueToPopulate))
|
|
3192
|
-
}
|
|
3193
|
-
if (fileExtension === 'toml') {
|
|
3194
|
-
valueToPopulate = JSON.stringify(TOML.parse(valueToPopulate))
|
|
3195
|
-
}
|
|
3196
|
-
if (fileExtension === 'ini') {
|
|
3197
|
-
valueToPopulate = INI.toJson(valueToPopulate)
|
|
3198
|
-
}
|
|
3199
|
-
// console.log('deep', variableString)
|
|
3200
|
-
// console.log('matchedFileString', matchedFileString)
|
|
3201
|
-
let deepProperties = variableString.replace(matchedFileString, '')
|
|
3202
|
-
// TODO 2025-11-12 add file.path.support instead of just :
|
|
3203
|
-
if (deepProperties.substring(0, 1) !== ':') {
|
|
3204
|
-
const errorMessage = `Invalid variable syntax when referencing file "${relativePath}" sub properties
|
|
3205
|
-
Please use ":" to reference sub properties. ${deepProperties}`
|
|
3206
|
-
return Promise.reject(new Error(errorMessage))
|
|
3207
|
-
}
|
|
3208
|
-
deepProperties = deepProperties.slice(1).split('.')
|
|
3209
|
-
return this.getDeeperValue(deepProperties, valueToPopulate)
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
|
-
if (fileExtension === 'yml' || fileExtension === 'yaml') {
|
|
3213
|
-
valueToPopulate = YAML.parse(valueToPopulate)
|
|
3214
|
-
return Promise.resolve(valueToPopulate)
|
|
3215
|
-
}
|
|
3216
|
-
|
|
3217
|
-
if (fileExtension === 'toml') {
|
|
3218
|
-
valueToPopulate = TOML.parse(valueToPopulate)
|
|
3219
|
-
return Promise.resolve(valueToPopulate)
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
if (fileExtension === 'ini') {
|
|
3223
|
-
valueToPopulate = INI.parse(valueToPopulate)
|
|
3224
|
-
return Promise.resolve(valueToPopulate)
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
if (fileExtension === 'json') {
|
|
3228
|
-
valueToPopulate = JSON.parse(valueToPopulate)
|
|
3229
|
-
return Promise.resolve(valueToPopulate)
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
// console.log('fall thru', valueToPopulate)
|
|
3233
|
-
return Promise.resolve(valueToPopulate)
|
|
3234
|
-
}
|
|
3235
|
-
getVariableFromDeep(variableString) {
|
|
3236
|
-
const index = variableString.replace(deepIndexReplacePattern, '')
|
|
3237
|
-
// const index = this.getDeepIndex(variableString)
|
|
3238
|
-
/*
|
|
3239
|
-
console.log('FIND INDEX', index)
|
|
3240
|
-
console.log(this.deep, this.deep[index])
|
|
3241
|
-
/** */
|
|
3242
|
-
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)
|
|
3243
3142
|
}
|
|
3244
3143
|
getValueFromDeep(variableString, pathValue) {
|
|
3245
3144
|
const variable = this.getVariableFromDeep(variableString)
|
|
@@ -3271,6 +3170,19 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3271
3170
|
}
|
|
3272
3171
|
return ret
|
|
3273
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
|
+
}
|
|
3274
3186
|
makeDeepVariable(variable, caller) {
|
|
3275
3187
|
// variable = variable.replace("dev", '"dev"')
|
|
3276
3188
|
let index = this.deep.findIndex((item) => variable === item)
|
|
@@ -3295,8 +3207,6 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3295
3207
|
console.log('deepVar', deepVar)
|
|
3296
3208
|
// process.exit(1)
|
|
3297
3209
|
/** */
|
|
3298
|
-
// TODO debugging space removal. Seems like this helps
|
|
3299
|
-
// const deepVar = variableContainer.replace(/\s/g, '').replace(variableString, `deep:${index}`)
|
|
3300
3210
|
return deepVar
|
|
3301
3211
|
}
|
|
3302
3212
|
/**
|
|
@@ -3367,66 +3277,68 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3367
3277
|
return veryDeep
|
|
3368
3278
|
}
|
|
3369
3279
|
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
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
|
|
3384
3296
|
}
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
console.log(notFoundMsg)
|
|
3392
|
-
}
|
|
3393
|
-
// 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
|
|
3394
3303
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
}
|
|
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()]
|
|
3415
3323
|
|
|
3416
|
-
|
|
3417
|
-
return (str[str.length -1] === char) ? '' : char
|
|
3418
|
-
}
|
|
3324
|
+
if (!theFunction) throw new Error(`Function "${functionName}" not found`)
|
|
3419
3325
|
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
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
|
+
}
|
|
3426
3334
|
|
|
3427
|
-
function
|
|
3428
|
-
|
|
3429
|
-
|
|
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
|
+
}
|
|
3430
3342
|
}
|
|
3431
3343
|
|
|
3432
3344
|
module.exports = Configorama
|