configorama 0.9.15 → 0.9.17
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/index.d.ts +4 -0
- package/package.json +1 -1
- package/src/display.js +485 -0
- package/src/index.js +2 -0
- package/src/main.js +59 -892
- package/src/metadata.js +451 -0
- package/src/resolvers/valueFromGit.js +3 -2
- package/src/utils/BoundedMap.js +25 -0
- package/src/utils/BoundedMap.test.js +91 -0
- package/src/utils/parsing/parse.js +51 -1
- package/src/utils/paths/findLineForKey.js +134 -1
- package/src/utils/regex/index.js +2 -2
- package/src/utils/strings/replaceAll.js +2 -1
- package/types/src/display.d.ts +62 -0
- package/types/src/display.d.ts.map +1 -0
- package/types/src/index.d.ts +8 -0
- package/types/src/index.d.ts.map +1 -1
- package/types/src/main.d.ts +2 -16
- package/types/src/main.d.ts.map +1 -1
- package/types/src/metadata.d.ts +26 -0
- package/types/src/metadata.d.ts.map +1 -0
- package/types/src/resolvers/valueFromGit.d.ts.map +1 -1
- package/types/src/utils/BoundedMap.d.ts +10 -0
- package/types/src/utils/BoundedMap.d.ts.map +1 -0
- package/types/src/utils/parsing/parse.d.ts.map +1 -1
- package/types/src/utils/paths/findLineForKey.d.ts +9 -0
- package/types/src/utils/paths/findLineForKey.d.ts.map +1 -1
- package/types/src/utils/strings/replaceAll.d.ts.map +1 -1
- package/types/src/resolvers/valueFromSelf.d.ts +0 -1
- package/types/src/resolvers/valueFromSelf.d.ts.map +0 -1
package/index.d.ts
CHANGED
|
@@ -55,6 +55,10 @@ interface ConfigoramaSettings {
|
|
|
55
55
|
dynamicArgs?: object | Function
|
|
56
56
|
/** Return both config and metadata about variables found */
|
|
57
57
|
returnMetadata?: boolean
|
|
58
|
+
/** Suppress env-stage-loader logs when useDotenv/useDotEnv is enabled. Defaults to true for API calls. */
|
|
59
|
+
dotEnvSilent?: boolean
|
|
60
|
+
/** Enable env-stage-loader debug logs when useDotenv/useDotEnv is enabled */
|
|
61
|
+
dotEnvDebug?: boolean
|
|
58
62
|
/** Keys to merge in arrays of objects */
|
|
59
63
|
mergeKeys?: string[]
|
|
60
64
|
/** Map of file paths to override */
|
package/package.json
CHANGED
package/src/display.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
// CLI display formatting for variable info, verify, and discovery output
|
|
2
|
+
// Pure functions that write directly to stdout
|
|
3
|
+
|
|
4
|
+
const chalk = require('./utils/ui/chalk')
|
|
5
|
+
const { logHeader } = require('./utils/ui/logs')
|
|
6
|
+
const { makeStackedBoxes } = require('@davidwells/box-logger')
|
|
7
|
+
const { findLineForKey } = require('./utils/paths/findLineForKey')
|
|
8
|
+
const { createEditorLink } = require('./utils/ui/createEditorLink')
|
|
9
|
+
const { isSensitiveVariable } = require('./utils/ui/configWizard')
|
|
10
|
+
|
|
11
|
+
const SPACING = ' '
|
|
12
|
+
const TITLE_TEXT = `Variable:${SPACING}`
|
|
13
|
+
const VALUE_HEX = '#899499'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Display "No Variables Found" message
|
|
17
|
+
* @param {string} configFilePath
|
|
18
|
+
* @param {RegExp} variableSyntax
|
|
19
|
+
* @param {Object} variableTypes
|
|
20
|
+
*/
|
|
21
|
+
function displayNoVariablesFound(configFilePath, variableSyntax, variableTypes) {
|
|
22
|
+
logHeader('No Variables Found in Config')
|
|
23
|
+
if (configFilePath) {
|
|
24
|
+
console.log(`File: ${configFilePath}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(`\nVariable syntax: `, variableSyntax)
|
|
28
|
+
|
|
29
|
+
const varTypes = Object.keys(variableTypes)
|
|
30
|
+
if (varTypes.length) {
|
|
31
|
+
const exclude = ['dot.prop', 'deep']
|
|
32
|
+
console.log('\nAllowed variable types:')
|
|
33
|
+
varTypes.forEach((v) => {
|
|
34
|
+
const vData = variableTypes[v]
|
|
35
|
+
if (exclude.includes(vData.type)) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
console.log(` - ${vData.type}: `, vData.match)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
console.log()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Display variable details in stacked box format
|
|
46
|
+
* @param {Object} params
|
|
47
|
+
* @param {string[]} params.varKeys
|
|
48
|
+
* @param {Object} params.variableData
|
|
49
|
+
* @param {Object} params.uniqueVariables
|
|
50
|
+
* @param {RegExp} params.varPrefixPattern
|
|
51
|
+
* @param {RegExp} params.varSuffixPattern
|
|
52
|
+
* @param {string[]} params.lines
|
|
53
|
+
* @param {string} params.fileType
|
|
54
|
+
* @param {string} params.configFilePath
|
|
55
|
+
*/
|
|
56
|
+
function displayVariableDetails({ varKeys, variableData, uniqueVariables, varPrefixPattern, varSuffixPattern, lines, fileType, configFilePath }) {
|
|
57
|
+
if (!varKeys.length) return
|
|
58
|
+
|
|
59
|
+
const getBaseVarName = (key) => key.replace(varPrefixPattern, '').replace(varSuffixPattern, '').split(',')[0].trim()
|
|
60
|
+
|
|
61
|
+
const fileName = configFilePath ? ` in ${configFilePath}` : ''
|
|
62
|
+
|
|
63
|
+
logHeader(`Found ${varKeys.length} Variables${fileName}`)
|
|
64
|
+
|
|
65
|
+
// deepLog('variableData', variableData)
|
|
66
|
+
|
|
67
|
+
if (varKeys.length) {
|
|
68
|
+
console.log()
|
|
69
|
+
const longestKey = varKeys.reduce((acc, k) => {
|
|
70
|
+
return Math.max(acc, k.length)
|
|
71
|
+
}, 0)
|
|
72
|
+
|
|
73
|
+
// Use uniqueVariables for simpler reference counting
|
|
74
|
+
const referenceData = varKeys.map((k) => {
|
|
75
|
+
const varName = getBaseVarName(k)
|
|
76
|
+
const uniqueVar = uniqueVariables[varName]
|
|
77
|
+
const refCount = uniqueVar ? uniqueVar.occurrences.length : variableData[k].length
|
|
78
|
+
const placesWord = refCount > 1 ? 'places' : 'place'
|
|
79
|
+
return `- ${k.padEnd(longestKey).padEnd(longestKey + 10)} referenced ${refCount} ${placesWord}`
|
|
80
|
+
}).join('\n')
|
|
81
|
+
|
|
82
|
+
console.log(`${referenceData}\n`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
logHeader('Variable Details')
|
|
86
|
+
|
|
87
|
+
const keyChalk = chalk.whiteBright
|
|
88
|
+
const valueChalk = chalk.hex(VALUE_HEX)
|
|
89
|
+
|
|
90
|
+
const indent = ''
|
|
91
|
+
const boxes = varKeys.map((key, i) => {
|
|
92
|
+
const variableInstances = variableData[key]
|
|
93
|
+
// console.log('variableInstances', variableInstances)
|
|
94
|
+
const firstInstance = variableInstances[0]
|
|
95
|
+
|
|
96
|
+
// Get uniqueVariable data for description and other metadata
|
|
97
|
+
const varName = getBaseVarName(key)
|
|
98
|
+
const uniqueVar = uniqueVariables[varName]
|
|
99
|
+
|
|
100
|
+
// Build display message from enriched metadata
|
|
101
|
+
let varMsg = ''
|
|
102
|
+
let requiredMessage = ''
|
|
103
|
+
|
|
104
|
+
// Show required status from metadata
|
|
105
|
+
if (firstInstance.isRequired) {
|
|
106
|
+
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Show type filter if present (Boolean, String, Number, etc.)
|
|
110
|
+
if (uniqueVar && uniqueVar.types && uniqueVar.types.length > 0) {
|
|
111
|
+
const typeLabel = `${indent}${keyChalk('Type:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
112
|
+
varMsg += `${typeLabel} ${valueChalk(uniqueVar.types.join(', '))}\n`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Show description from uniqueVariables if available
|
|
116
|
+
if (uniqueVar && uniqueVar.descriptions && uniqueVar.descriptions.length > 0) {
|
|
117
|
+
const descText = `${indent}${keyChalk('Description:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
118
|
+
const combinedDesc = uniqueVar.descriptions.join('. ')
|
|
119
|
+
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Show resolve order from metadata
|
|
123
|
+
if (firstInstance.resolveOrder.length > 1) {
|
|
124
|
+
varMsg += `${indent}${keyChalk('Resolve Order:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
125
|
+
const resolveOrder = firstInstance.resolveOrder.join(', ')
|
|
126
|
+
varMsg += ` ${valueChalk(resolveOrder)}\n`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Show default value from metadata
|
|
130
|
+
if (typeof firstInstance.defaultValue !== 'undefined') {
|
|
131
|
+
const defaultValueRender = firstInstance.defaultValue === '' ? '""' : firstInstance.defaultValue
|
|
132
|
+
const defaultValueText = `${indent}${keyChalk('Default value:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
133
|
+
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}\n`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Show default value source path from metadata
|
|
137
|
+
if (firstInstance.defaultValueSrc) {
|
|
138
|
+
varMsg += `${indent}${keyChalk('Default path:'.padEnd(TITLE_TEXT.length, ' '))} `
|
|
139
|
+
const defaultPathLine = findLineForKey(firstInstance.defaultValueSrc, lines, fileType)
|
|
140
|
+
if (defaultPathLine) {
|
|
141
|
+
varMsg += `${createEditorLink(configFilePath, defaultPathLine, 1, firstInstance.defaultValueSrc, 'gray')}\n`
|
|
142
|
+
} else {
|
|
143
|
+
varMsg += `${valueChalk(firstInstance.defaultValueSrc)}\n`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Show path(s) from metadata
|
|
148
|
+
const configPathLine = findLineForKey(variableInstances[0].path, lines, fileType)
|
|
149
|
+
let locationRender = configPathLine
|
|
150
|
+
? createEditorLink(configFilePath, configPathLine, 1, variableInstances[0].path, 'gray')
|
|
151
|
+
: valueChalk(variableInstances[0].path)
|
|
152
|
+
let locationLabel = `${indent}${keyChalk('Config Path:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
153
|
+
let typeText = ''
|
|
154
|
+
if (variableInstances.length > 1) {
|
|
155
|
+
const pathIndent = ' '.repeat(TITLE_TEXT.length + 1)
|
|
156
|
+
const pathItems = variableInstances.map((v, idx) => {
|
|
157
|
+
const pathLine = findLineForKey(v.path, lines, fileType)
|
|
158
|
+
const pathLink = pathLine
|
|
159
|
+
? createEditorLink(configFilePath, pathLine, 1, `- ${v.path}`, 'gray')
|
|
160
|
+
: valueChalk(`- ${v.path}`)
|
|
161
|
+
// Show type filter per path if different
|
|
162
|
+
if (uniqueVar && uniqueVar.occurrences.length > 1) {
|
|
163
|
+
const occurrence = uniqueVar.occurrences.find(occ => occ.path === v.path)
|
|
164
|
+
const pathType = occurrence && occurrence.type
|
|
165
|
+
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
166
|
+
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
167
|
+
return `${prefix}${pathLink}${typeText}`
|
|
168
|
+
}
|
|
169
|
+
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
170
|
+
return `${prefix}${pathLink}${typeText}`
|
|
171
|
+
})
|
|
172
|
+
locationRender = pathItems.join('\n')
|
|
173
|
+
locationLabel = `${indent}${keyChalk('Config Paths:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
174
|
+
} else {
|
|
175
|
+
const pathType = firstInstance.type
|
|
176
|
+
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
177
|
+
}
|
|
178
|
+
varMsg += `${locationLabel} ${locationRender}`
|
|
179
|
+
|
|
180
|
+
const lineNumber = findLineForKey(firstInstance.key, lines, fileType)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
content: {
|
|
184
|
+
left: varMsg,
|
|
185
|
+
backgroundColor: 'red',
|
|
186
|
+
width: '100%',
|
|
187
|
+
},
|
|
188
|
+
title: {
|
|
189
|
+
left: `▷ ${lineNumber ? createEditorLink(configFilePath, lineNumber, 1, key) : key}`,
|
|
190
|
+
right: lineNumber ? createEditorLink(configFilePath, lineNumber, 1, `${requiredMessage} ${lineNumber ? `Line: ${lineNumber.toString().padEnd(2, ' ')}` : ''}`, 'gray') : '',
|
|
191
|
+
center: typeText,
|
|
192
|
+
paddingBottom: 1,
|
|
193
|
+
paddingTop: (i === 0) ? 1 : 0,
|
|
194
|
+
truncate: true,
|
|
195
|
+
},
|
|
196
|
+
width: '100%',
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
console.log(makeStackedBoxes(boxes, {
|
|
201
|
+
borderText: 'Variable Details. Click on titles to open in editor.',
|
|
202
|
+
borderColor: 'gray',
|
|
203
|
+
minWidth: '96%',
|
|
204
|
+
borderStyle: 'bold',
|
|
205
|
+
disableTitleSeparator: true,
|
|
206
|
+
}))
|
|
207
|
+
// process.exit(1)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Display unique variables in stacked box format
|
|
212
|
+
* @param {Object} params
|
|
213
|
+
* @param {string[]} params.uniqueVarKeys
|
|
214
|
+
* @param {Object} params.uniqueVariables
|
|
215
|
+
* @param {string[]} params.lines
|
|
216
|
+
* @param {string} params.fileType
|
|
217
|
+
* @param {string} params.configFilePath
|
|
218
|
+
*/
|
|
219
|
+
function displayUniqueVariables({ uniqueVarKeys, uniqueVariables, lines, fileType, configFilePath }) {
|
|
220
|
+
const keyChalk = chalk.whiteBright
|
|
221
|
+
const valueChalk = chalk.hex(VALUE_HEX)
|
|
222
|
+
|
|
223
|
+
// New unique variable makeStackedBoxes display
|
|
224
|
+
const uniqueBoxes = uniqueVarKeys.map((varName, i) => {
|
|
225
|
+
const uniqueVar = uniqueVariables[varName]
|
|
226
|
+
const occurrences = uniqueVar.occurrences || []
|
|
227
|
+
const firstOcc = occurrences[0] || {}
|
|
228
|
+
|
|
229
|
+
let varMsg = ''
|
|
230
|
+
let requiredMessage = ''
|
|
231
|
+
|
|
232
|
+
// Show required status from computed isRequired (accounts for resolved self-refs)
|
|
233
|
+
const isRequired = occurrences.some(occ => occ.isRequired)
|
|
234
|
+
if (isRequired) {
|
|
235
|
+
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Show type filter if present
|
|
239
|
+
if (uniqueVar.types && uniqueVar.types.length > 0) {
|
|
240
|
+
const typeLabel = `${keyChalk('Type:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
241
|
+
varMsg += `${typeLabel} ${valueChalk(uniqueVar.types.join(', '))}\n`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Show description
|
|
245
|
+
if (uniqueVar.descriptions && uniqueVar.descriptions.length > 0) {
|
|
246
|
+
const descText = `${keyChalk('Description:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
247
|
+
const combinedDesc = uniqueVar.descriptions.join('. ')
|
|
248
|
+
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Show default value only if it's a true fallback, not a pre-resolved value
|
|
252
|
+
// Redact sensitive values like API keys, secrets, tokens
|
|
253
|
+
const isSensitive = isSensitiveVariable(varName)
|
|
254
|
+
const hasActualDefault = firstOcc.hasFallback && typeof firstOcc.defaultValue !== 'undefined'
|
|
255
|
+
if (hasActualDefault) {
|
|
256
|
+
const defaultValueRender = isSensitive ? '********' : (firstOcc.defaultValue === '' ? '""' : firstOcc.defaultValue)
|
|
257
|
+
const defaultValueText = `${keyChalk('Default value:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
258
|
+
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}\n`
|
|
259
|
+
} else if (uniqueVar.resolvedValue !== undefined) {
|
|
260
|
+
// Show pre-resolved current value (e.g., from env, git)
|
|
261
|
+
const resolvedRender = isSensitive ? '********' : (uniqueVar.resolvedValue === '' ? '""' : uniqueVar.resolvedValue)
|
|
262
|
+
const resolvedText = `${keyChalk('Current value:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
263
|
+
const envIndicator = uniqueVar.variableType === 'env' ? ` ${chalk.red('(currently set env var)')}` : ''
|
|
264
|
+
varMsg += `${resolvedText} ${valueChalk(resolvedRender)}${envIndicator}\n`
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Show default value source path
|
|
268
|
+
if (firstOcc.defaultValueSrc) {
|
|
269
|
+
varMsg += `${keyChalk('Default path:'.padEnd(TITLE_TEXT.length, ' '))} `
|
|
270
|
+
const defaultPathLine = findLineForKey(firstOcc.defaultValueSrc, lines, fileType)
|
|
271
|
+
if (defaultPathLine) {
|
|
272
|
+
varMsg += `${createEditorLink(configFilePath, defaultPathLine, 1, firstOcc.defaultValueSrc, 'gray')}\n`
|
|
273
|
+
} else {
|
|
274
|
+
varMsg += `${valueChalk(firstOcc.defaultValueSrc)}\n`
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Show config path(s) from occurrences
|
|
279
|
+
let locationRender
|
|
280
|
+
let locationLabel
|
|
281
|
+
if (occurrences.length > 1) {
|
|
282
|
+
const pathIndent = ' '.repeat(TITLE_TEXT.length + 1)
|
|
283
|
+
const pathItems = occurrences.map((occ, idx) => {
|
|
284
|
+
const pathLine = findLineForKey(occ.path, lines, fileType)
|
|
285
|
+
const pathLink = pathLine
|
|
286
|
+
? createEditorLink(configFilePath, pathLine, 1, `- ${occ.path}`, 'gray')
|
|
287
|
+
: valueChalk(`- ${occ.path}`)
|
|
288
|
+
const typeText = occ.type ? ` ${chalk.dim(`Type: ${occ.type}`)}` : ''
|
|
289
|
+
const prefix = idx === 0 ? '' : `${pathIndent}`
|
|
290
|
+
return `${prefix}${pathLink}${typeText}`
|
|
291
|
+
})
|
|
292
|
+
locationRender = pathItems.join('\n')
|
|
293
|
+
locationLabel = `${keyChalk('Config Paths:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
294
|
+
} else {
|
|
295
|
+
const pathLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
296
|
+
locationRender = pathLine
|
|
297
|
+
? createEditorLink(configFilePath, pathLine, 1, firstOcc.path, 'gray')
|
|
298
|
+
: valueChalk(firstOcc.path)
|
|
299
|
+
locationLabel = `${keyChalk('Config Path:'.padEnd(TITLE_TEXT.length, ' '))}`
|
|
300
|
+
}
|
|
301
|
+
varMsg += `${locationLabel} ${locationRender}`
|
|
302
|
+
|
|
303
|
+
// Find first line number for title
|
|
304
|
+
const lineNumber = findLineForKey(firstOcc.path, lines, fileType)
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
content: {
|
|
308
|
+
left: varMsg,
|
|
309
|
+
backgroundColor: 'red',
|
|
310
|
+
width: '100%',
|
|
311
|
+
},
|
|
312
|
+
title: {
|
|
313
|
+
left: `▷ ${firstOcc.varMatch}`,
|
|
314
|
+
right: `${requiredMessage} ${lineNumber ? `Line: ${lineNumber.toString().padEnd(2, ' ')}` : ''}`,
|
|
315
|
+
paddingBottom: 1,
|
|
316
|
+
paddingTop: (i === 0) ? 1 : 0,
|
|
317
|
+
truncate: true,
|
|
318
|
+
},
|
|
319
|
+
width: '100%',
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
console.log(makeStackedBoxes(uniqueBoxes, {
|
|
324
|
+
borderText: 'Unique Variables',
|
|
325
|
+
borderColor: 'gray',
|
|
326
|
+
minWidth: '96%',
|
|
327
|
+
borderStyle: 'bold',
|
|
328
|
+
disableTitleSeparator: true,
|
|
329
|
+
}))
|
|
330
|
+
console.log()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Display configurable variables grouped by source type
|
|
335
|
+
* @param {Object} params
|
|
336
|
+
* @param {string[]} params.uniqueVarKeys
|
|
337
|
+
* @param {Object} params.uniqueVariables
|
|
338
|
+
* @param {string[]} params.lines
|
|
339
|
+
* @param {string} params.fileType
|
|
340
|
+
* @param {string} params.configFilePath
|
|
341
|
+
*/
|
|
342
|
+
function displayConfigurableVariables({ uniqueVarKeys, uniqueVariables, lines, fileType, configFilePath }) {
|
|
343
|
+
// Unique variables that require setup (excludes readonly source types)
|
|
344
|
+
const CONFIGURABLE_SOURCES = ['user', 'config', 'remote']
|
|
345
|
+
const configurableVarKeys = []
|
|
346
|
+
|
|
347
|
+
for (const varName of uniqueVarKeys) {
|
|
348
|
+
const uniqueVar = uniqueVariables[varName]
|
|
349
|
+
// Include if source type is user, config, or remote (not readonly)
|
|
350
|
+
if (CONFIGURABLE_SOURCES.includes(uniqueVar.variableSourceType)) {
|
|
351
|
+
configurableVarKeys.push(varName)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!configurableVarKeys.length) return
|
|
356
|
+
|
|
357
|
+
const keyChalk = chalk.whiteBright
|
|
358
|
+
const valueChalk = chalk.hex(VALUE_HEX)
|
|
359
|
+
|
|
360
|
+
// Group by source type
|
|
361
|
+
const bySource = {
|
|
362
|
+
user: [],
|
|
363
|
+
config: [],
|
|
364
|
+
remote: [],
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const varName of configurableVarKeys) {
|
|
368
|
+
const v = uniqueVariables[varName]
|
|
369
|
+
const sourceType = v.variableSourceType || 'user'
|
|
370
|
+
if (bySource[sourceType]) {
|
|
371
|
+
bySource[sourceType].push({ varName, ...v })
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const configurableBoxes = []
|
|
376
|
+
|
|
377
|
+
for (const [sourceType, vars] of Object.entries(bySource)) {
|
|
378
|
+
if (vars.length === 0) continue
|
|
379
|
+
|
|
380
|
+
for (let i = 0; i < vars.length; i++) {
|
|
381
|
+
const v = vars[i]
|
|
382
|
+
const occurrences = v.occurrences || []
|
|
383
|
+
const firstOcc = occurrences[0] || {}
|
|
384
|
+
|
|
385
|
+
let varMsg = ''
|
|
386
|
+
let requiredMessage = ''
|
|
387
|
+
|
|
388
|
+
// Show required status from computed isRequired (accounts for resolved self-refs)
|
|
389
|
+
const isRequired = occurrences.some(occ => occ.isRequired)
|
|
390
|
+
if (isRequired) {
|
|
391
|
+
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Show description if present (directly under title, not as key/value)
|
|
395
|
+
if (v.descriptions && v.descriptions.length > 0) {
|
|
396
|
+
varMsg += `${chalk.dim(v.descriptions.join('. '))}\n\n`
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Show type filter if defined (String, Number, etc.)
|
|
400
|
+
const varType = (v.types && v.types[0]) || firstOcc.type
|
|
401
|
+
if (varType) {
|
|
402
|
+
varMsg += `${keyChalk('Type:'.padEnd(TITLE_TEXT.length, ' '))} ${valueChalk(varType)}\n`
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Show current/default value (redact sensitive values)
|
|
406
|
+
const isSensitive = isSensitiveVariable(v.varName)
|
|
407
|
+
if (v.resolvedValue !== undefined) {
|
|
408
|
+
const resolvedRender = isSensitive ? '********' : (v.resolvedValue === '' ? '""' : v.resolvedValue)
|
|
409
|
+
varMsg += `${keyChalk('Current value:'.padEnd(TITLE_TEXT.length, ' '))} ${valueChalk(resolvedRender)}\n`
|
|
410
|
+
} else if (firstOcc.hasFallback && firstOcc.defaultValue !== undefined) {
|
|
411
|
+
const defaultRender = isSensitive ? '********' : (firstOcc.defaultValue === '' ? '""' : firstOcc.defaultValue)
|
|
412
|
+
varMsg += `${keyChalk('Default value:'.padEnd(TITLE_TEXT.length, ' '))} ${valueChalk(defaultRender)}\n`
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Show config path(s)
|
|
416
|
+
let locationRender
|
|
417
|
+
let locationLabel
|
|
418
|
+
if (occurrences.length > 1) {
|
|
419
|
+
const pathIndent = ' '.repeat(TITLE_TEXT.length + 1)
|
|
420
|
+
const pathItems = occurrences.map((occ, idx) => {
|
|
421
|
+
const pathLine = findLineForKey(occ.path, lines, fileType)
|
|
422
|
+
const pathLink = pathLine
|
|
423
|
+
? createEditorLink(configFilePath, pathLine, 1, `- ${occ.path}`, VALUE_HEX)
|
|
424
|
+
: valueChalk(`- ${occ.path}`)
|
|
425
|
+
const prefix = idx === 0 ? '' : `${pathIndent}`
|
|
426
|
+
return `${prefix}${pathLink}`
|
|
427
|
+
})
|
|
428
|
+
locationRender = pathItems.join('\n')
|
|
429
|
+
locationLabel = 'Config Paths:'
|
|
430
|
+
} else {
|
|
431
|
+
const pathLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
432
|
+
locationRender = pathLine
|
|
433
|
+
? createEditorLink(configFilePath, pathLine, 1, firstOcc.path, VALUE_HEX)
|
|
434
|
+
: valueChalk(firstOcc.path)
|
|
435
|
+
locationLabel = 'Config Path:'
|
|
436
|
+
}
|
|
437
|
+
varMsg += `${keyChalk(locationLabel.padEnd(TITLE_TEXT.length, ' '))} ${locationRender}`
|
|
438
|
+
|
|
439
|
+
// Get type for center heading (reuse varType from above)
|
|
440
|
+
const typeText = varType ? chalk.dim(`Type: ${varType}`) : ''
|
|
441
|
+
|
|
442
|
+
// Get line number for first occurrence
|
|
443
|
+
const firstOccLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
444
|
+
const varTitle = firstOcc.varMatch || v.varName
|
|
445
|
+
const requiredSuffix = requiredMessage ? ` - ${requiredMessage}` : ''
|
|
446
|
+
const titleLink = firstOccLine
|
|
447
|
+
? createEditorLink(configFilePath, firstOccLine, 1, `▷ ${varTitle}`) + requiredSuffix
|
|
448
|
+
: `▷ ${varTitle}${requiredSuffix}`
|
|
449
|
+
|
|
450
|
+
configurableBoxes.push({
|
|
451
|
+
content: {
|
|
452
|
+
left: varMsg,
|
|
453
|
+
width: '100%',
|
|
454
|
+
},
|
|
455
|
+
title: {
|
|
456
|
+
left: titleLink,
|
|
457
|
+
// center: typeText,
|
|
458
|
+
right: chalk.dim(`${v.variableType}`),
|
|
459
|
+
paddingBottom: 1,
|
|
460
|
+
paddingTop: (configurableBoxes.length === 0) ? 1 : 0,
|
|
461
|
+
truncate: true,
|
|
462
|
+
},
|
|
463
|
+
width: '100%',
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (configurableBoxes.length > 0) {
|
|
469
|
+
console.log(makeStackedBoxes(configurableBoxes, {
|
|
470
|
+
borderText: `Configurable Variables (${configurableVarKeys.length})`,
|
|
471
|
+
borderColor: 'yellow',
|
|
472
|
+
minWidth: '96%',
|
|
473
|
+
borderStyle: 'bold',
|
|
474
|
+
disableTitleSeparator: true,
|
|
475
|
+
}))
|
|
476
|
+
console.log()
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
module.exports = {
|
|
481
|
+
displayNoVariablesFound,
|
|
482
|
+
displayVariableDetails,
|
|
483
|
+
displayUniqueVariables,
|
|
484
|
+
displayConfigurableVariables,
|
|
485
|
+
}
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,8 @@ const { buildVariableSyntax } = require('./utils/variables/variableUtils')
|
|
|
15
15
|
* @property {boolean} [allowUndefinedValues] - allow undefined values to pass through without throwing errors
|
|
16
16
|
* @property {Object|Function} [dynamicArgs] - values passed into .js config files if user using javascript config
|
|
17
17
|
* @property {boolean} [returnMetadata] - return both config and metadata about variables found
|
|
18
|
+
* @property {boolean} [dotEnvSilent] - suppress env-stage-loader logs when useDotenv/useDotEnv is enabled
|
|
19
|
+
* @property {boolean} [dotEnvDebug] - enable env-stage-loader debug logs when useDotenv/useDotEnv is enabled
|
|
18
20
|
* @property {string[]} [mergeKeys] - keys to merge in arrays of objects
|
|
19
21
|
* @property {Object.<string, string>} [filePathOverrides] - map of file paths to override
|
|
20
22
|
*/
|