configorama 0.6.11 → 0.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -24
- package/cli.js +3 -3
- package/package.json +1 -1
- package/src/index.js +22 -32
- package/src/main.js +775 -857
- package/src/parsers/yaml.js +3 -47
- package/src/resolvers/valueFromCron.js +3 -1
- package/src/resolvers/valueFromEnv.js +1 -0
- package/src/resolvers/valueFromEval.js +1 -0
- package/src/resolvers/valueFromFile.js +394 -0
- package/src/resolvers/valueFromGit.js +3 -2
- package/src/resolvers/valueFromOptions.js +1 -0
- package/src/resolvers/valueFromString.js +2 -1
- package/src/sync.js +12 -5
- package/src/utils/parsing/arrayToJsonPath.test.js +56 -0
- package/src/utils/{enrichMetadata.js → parsing/enrichMetadata.js} +244 -94
- package/src/utils/{parse.js → parsing/parse.js} +13 -13
- package/src/utils/parsing/preProcess.js +165 -0
- package/src/utils/paths/filePathUtils.js +136 -0
- package/src/utils/paths/filePathUtils.test.js +214 -0
- package/src/utils/paths/findLineForKey.js +47 -0
- package/src/utils/paths/findLineForKey.test.js +126 -0
- package/src/utils/{getFullFilePath.js → paths/getFullFilePath.js} +22 -26
- package/src/utils/{resolveAlias.js → paths/resolveAlias.js} +1 -1
- package/src/utils/regex/index.js +23 -1
- package/src/utils/resolution/preResolveVariable.js +260 -0
- package/src/utils/resolution/preResolveVariable.test.js +98 -0
- package/src/utils/strings/bracketMatcher.js +86 -0
- package/src/utils/strings/bracketMatcher.test.js +135 -0
- package/src/utils/{formatFunctionArgs.js → strings/formatFunctionArgs.js} +3 -2
- package/src/utils/strings/formatFunctionArgs.test.js +77 -0
- package/src/utils/strings/quoteUtils.js +89 -0
- package/src/utils/strings/quoteUtils.test.js +217 -0
- package/src/utils/strings/replaceAll.test.js +82 -0
- package/src/utils/{splitByComma.js → strings/splitByComma.js} +1 -1
- package/src/utils/strings/splitCsv.js +38 -0
- package/src/utils/strings/splitCsv.test.js +96 -0
- package/src/utils/strings/textUtils.test.js +86 -0
- package/src/utils/{configWizard.js → ui/configWizard.js} +212 -60
- package/src/utils/{createEditorLink.js → ui/createEditorLink.js} +11 -2
- package/src/utils/{logs.js → ui/logs.js} +3 -3
- package/src/utils/validation/isValidValue.test.js +64 -0
- package/src/utils/validation/warnIfNotFound.js +52 -0
- package/src/utils/variables/appendDeepVariable.test.js +41 -0
- package/src/utils/{cleanVariable.js → variables/cleanVariable.js} +5 -26
- package/src/utils/{find-nested-variables.js → variables/findNestedVariables.js} +2 -2
- package/src/utils/{find-nested-variables.test.js → variables/findNestedVariables.test.js} +5 -5
- package/src/utils/variables/getVariableType.test.js +109 -0
- package/src/utils/variables/variableUtils.test.js +117 -0
- package/src/utils/isValidValue.js +0 -8
- package/src/utils/splitCsv.js +0 -29
- package/src/utils/trimSurroundingQuotes.js +0 -5
- /package/src/utils/{arrayToJsonPath.js → parsing/arrayToJsonPath.js} +0 -0
- /package/src/utils/{cloudformationSchema.js → parsing/cloudformationSchema.js} +0 -0
- /package/src/utils/{mergeByKeys.js → parsing/mergeByKeys.js} +0 -0
- /package/src/utils/{find-project-root.js → paths/findProjectRoot.js} +0 -0
- /package/src/utils/{resolveAlias.test.js → paths/resolveAlias.test.js} +0 -0
- /package/src/utils/{replaceAll.js → strings/replaceAll.js} +0 -0
- /package/src/utils/{splitByComma.test.js → strings/splitByComma.test.js} +0 -0
- /package/src/utils/{textUtils.js → strings/textUtils.js} +0 -0
- /package/src/utils/{chalk.js → ui/chalk.js} +0 -0
- /package/src/utils/{deep-log.js → ui/deep-log.js} +0 -0
- /package/src/utils/{appendDeepVariable.js → variables/appendDeepVariable.js} +0 -0
- /package/src/utils/{cleanVariable.test.js → variables/cleanVariable.test.js} +0 -0
- /package/src/utils/{getVariableType.js → variables/getVariableType.js} +0 -0
- /package/src/utils/{variableUtils.js → variables/variableUtils.js} +0 -0
|
@@ -1,7 +1,37 @@
|
|
|
1
|
-
const { splitCsv } = require('./splitCsv')
|
|
2
1
|
const dotProp = require('dot-prop')
|
|
3
2
|
const fs = require('fs')
|
|
4
3
|
const path = require('path')
|
|
4
|
+
const { normalizePath, extractFilePath, normalizeFileVariable, resolveInnerVariables } = require('../paths/filePathUtils')
|
|
5
|
+
const { preResolveString, preResolveSingle } = require('../resolution/preResolveVariable')
|
|
6
|
+
|
|
7
|
+
// Type filters that indicate expected value types
|
|
8
|
+
const TYPE_FILTERS = ['Boolean', 'String', 'Number', 'Array', 'Object', 'Json']
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract type filter from filters array
|
|
12
|
+
* @param {Array} filters - Filters array from variable instance
|
|
13
|
+
* @returns {string|undefined} Type filter if found
|
|
14
|
+
*/
|
|
15
|
+
function extractTypeFromFilters(filters) {
|
|
16
|
+
if (!filters || !Array.isArray(filters)) return undefined
|
|
17
|
+
return filters.find(f => TYPE_FILTERS.includes(f))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract description from filters array (help filter)
|
|
22
|
+
* @param {Array} filters - Filters array from variable instance
|
|
23
|
+
* @returns {string|undefined} Description if found
|
|
24
|
+
*/
|
|
25
|
+
function extractDescriptionFromFilters(filters) {
|
|
26
|
+
if (!filters || !Array.isArray(filters)) return undefined
|
|
27
|
+
for (const filter of filters) {
|
|
28
|
+
if (filter && typeof filter === 'string') {
|
|
29
|
+
const helpMatch = filter.match(/^help\(['"](.+)['"]\)$/)
|
|
30
|
+
if (helpMatch) return helpMatch[1]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
5
35
|
|
|
6
36
|
/**
|
|
7
37
|
* Create a standardized occurrence object
|
|
@@ -29,6 +59,9 @@ function createOccurrence(instance, varMatch, options = {}) {
|
|
|
29
59
|
}
|
|
30
60
|
}
|
|
31
61
|
|
|
62
|
+
// Extract type from filters
|
|
63
|
+
const type = extractTypeFromFilters(filters)
|
|
64
|
+
|
|
32
65
|
const occurrence = {
|
|
33
66
|
originalString: instance.originalStringValue,
|
|
34
67
|
varMatch: varMatch,
|
|
@@ -40,6 +73,10 @@ function createOccurrence(instance, varMatch, options = {}) {
|
|
|
40
73
|
hasFallback: options.hasFallback !== undefined ? options.hasFallback : (instance.hasFallback || false),
|
|
41
74
|
}
|
|
42
75
|
|
|
76
|
+
if (type) {
|
|
77
|
+
occurrence.type = type
|
|
78
|
+
}
|
|
79
|
+
|
|
43
80
|
if (description) {
|
|
44
81
|
occurrence.description = description
|
|
45
82
|
}
|
|
@@ -52,53 +89,15 @@ function createOccurrence(instance, varMatch, options = {}) {
|
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
/**
|
|
55
|
-
*
|
|
56
|
-
* @param {string}
|
|
57
|
-
* @
|
|
58
|
-
|
|
59
|
-
function extractFilePath(propertyString) {
|
|
60
|
-
const fileMatch = propertyString.match(/^\$\{(?:file|text)\((.*?)\)/)
|
|
61
|
-
if (!fileMatch || !fileMatch[1]) {
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const fileContent = fileMatch[1].trim()
|
|
66
|
-
const parts = splitCsv(fileContent)
|
|
67
|
-
let filePath = parts[0].trim()
|
|
68
|
-
|
|
69
|
-
// Remove quotes if present
|
|
70
|
-
filePath = filePath.replace(/^['"]|['"]$/g, '')
|
|
71
|
-
|
|
72
|
-
return { filePath }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Normalize a file path (add ./ prefix, fix .//, skip deep refs)
|
|
77
|
-
* @param {string} filePath - The file path to normalize
|
|
78
|
-
* @returns {string|null} Normalized path, or null if should be skipped
|
|
92
|
+
* Get source type for a variable type
|
|
93
|
+
* @param {string} variableType - The variable type (e.g., 'env', 'opt', 'git')
|
|
94
|
+
* @param {Array} variableTypes - Array of variable type definitions
|
|
95
|
+
* @returns {string|undefined} The source type ('user', 'config', 'readonly', 'remote')
|
|
79
96
|
*/
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let normalized = filePath
|
|
87
|
-
|
|
88
|
-
// Add ./ prefix for relative paths
|
|
89
|
-
if (!filePath.startsWith('./') &&
|
|
90
|
-
!filePath.startsWith('../') &&
|
|
91
|
-
!filePath.startsWith('/') &&
|
|
92
|
-
!filePath.startsWith('~')) {
|
|
93
|
-
normalized = './' + filePath
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Fix double slashes
|
|
97
|
-
if (normalized.startsWith('.//')) {
|
|
98
|
-
normalized = normalized.replace('.//', './')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return normalized
|
|
97
|
+
function getSourceForType(variableType, variableTypes) {
|
|
98
|
+
if (!variableTypes || !variableType) return undefined
|
|
99
|
+
const typeDef = variableTypes.find(vt => vt.type === variableType)
|
|
100
|
+
return typeDef?.source
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/**
|
|
@@ -110,21 +109,31 @@ function normalizePath(filePath) {
|
|
|
110
109
|
* @param {object} originalConfig - The original config object (before resolution) for self/dot.prop lookups.
|
|
111
110
|
* @param {string} configPath - The path to the config file.
|
|
112
111
|
* @param {Array} filterNames - Array of known filter names.
|
|
113
|
-
* @
|
|
112
|
+
* @param {object} [resolvedConfig] - The resolved config object (optional, for post-resolution enrichment).
|
|
113
|
+
* @param {object} [options] - CLI options object for opt: resolution.
|
|
114
|
+
* @param {Array} [variableTypes] - Array of variable type definitions with source info.
|
|
115
|
+
* @returns {Promise<object>} Enriched metadata with resolution details and a complete file reference list.
|
|
114
116
|
*/
|
|
115
|
-
function enrichMetadata(
|
|
117
|
+
async function enrichMetadata(
|
|
116
118
|
metadata,
|
|
117
119
|
resolutionTracking,
|
|
118
120
|
variableSyntax,
|
|
119
121
|
fileRefsFound = [],
|
|
120
122
|
originalConfig = {},
|
|
121
123
|
configPath,
|
|
122
|
-
filterNames = []
|
|
124
|
+
filterNames = [],
|
|
125
|
+
resolvedConfig,
|
|
126
|
+
options = {},
|
|
127
|
+
variableTypes = []
|
|
123
128
|
) {
|
|
124
129
|
if (!resolutionTracking) {
|
|
125
130
|
return metadata
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
// Create resolve context early for resolving descriptions
|
|
134
|
+
const configDir = configPath ? path.dirname(configPath) : undefined
|
|
135
|
+
const resolveContext = { config: originalConfig, variableSyntax, configDir, options }
|
|
136
|
+
|
|
128
137
|
const varKeys = Object.keys(metadata.variables)
|
|
129
138
|
|
|
130
139
|
for (const key of varKeys) {
|
|
@@ -138,6 +147,11 @@ function enrichMetadata(
|
|
|
138
147
|
// For each resolveDetail, find the matching resolution history entry
|
|
139
148
|
for (let i = 0; i < varData.resolveDetails.length; i++) {
|
|
140
149
|
const detail = varData.resolveDetails[i]
|
|
150
|
+
// Add source type for this variable type
|
|
151
|
+
const sourceType = getSourceForType(detail.variableType, variableTypes)
|
|
152
|
+
if (sourceType) {
|
|
153
|
+
detail.variableSourceType = sourceType
|
|
154
|
+
}
|
|
141
155
|
const isOutermost = i === varData.resolveDetails.length - 1
|
|
142
156
|
|
|
143
157
|
if (isOutermost && trackingData.resolutionHistory.length > 0) {
|
|
@@ -173,6 +187,17 @@ function enrichMetadata(
|
|
|
173
187
|
}
|
|
174
188
|
}
|
|
175
189
|
}
|
|
190
|
+
|
|
191
|
+
// Add type and description to individual variable instance
|
|
192
|
+
const instanceType = extractTypeFromFilters(varData.filters)
|
|
193
|
+
const instanceDescription = extractDescriptionFromFilters(varData.filters)
|
|
194
|
+
if (instanceType) {
|
|
195
|
+
varData.type = instanceType
|
|
196
|
+
}
|
|
197
|
+
if (instanceDescription) {
|
|
198
|
+
// Resolve any variables in the description (e.g., ${allowedValues} -> actual values)
|
|
199
|
+
varData.description = await preResolveString(instanceDescription, resolveContext)
|
|
200
|
+
}
|
|
176
201
|
}
|
|
177
202
|
}
|
|
178
203
|
|
|
@@ -198,7 +223,8 @@ function enrichMetadata(
|
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
// Update fileDependencies.resolvedPaths with the resolved file refs
|
|
201
|
-
if
|
|
226
|
+
// Only overwrite if we have enriched data, otherwise keep original from collectVariableMetadata
|
|
227
|
+
if (metadata.fileDependencies && resolvedFileRefs.length > 0) {
|
|
202
228
|
metadata.fileDependencies.resolvedPaths = resolvedFileRefs
|
|
203
229
|
}
|
|
204
230
|
|
|
@@ -304,10 +330,14 @@ function enrichMetadata(
|
|
|
304
330
|
}
|
|
305
331
|
}
|
|
306
332
|
|
|
307
|
-
// Update fileDependencies with the enriched data
|
|
333
|
+
// Update fileDependencies with the enriched data (only if we have data)
|
|
308
334
|
if (metadata.fileDependencies) {
|
|
309
|
-
|
|
310
|
-
|
|
335
|
+
if (byConfigPath.length > 0) {
|
|
336
|
+
metadata.fileDependencies.byConfigPath = byConfigPath
|
|
337
|
+
}
|
|
338
|
+
if (references.length > 0) {
|
|
339
|
+
metadata.fileDependencies.references = references
|
|
340
|
+
}
|
|
311
341
|
}
|
|
312
342
|
|
|
313
343
|
// Build uniqueVariables rollup - group by base variable (without fallbacks)
|
|
@@ -316,11 +346,32 @@ function enrichMetadata(
|
|
|
316
346
|
for (const key of varKeys) {
|
|
317
347
|
const varInstances = metadata.variables[key]
|
|
318
348
|
const firstInstance = varInstances[0]
|
|
319
|
-
|
|
349
|
+
|
|
350
|
+
// Extract variable name from key (e.g. "${self:service}" -> "self:service")
|
|
351
|
+
const keyVarName = key.slice(2, -1).split(',')[0].trim()
|
|
352
|
+
|
|
353
|
+
// Find the resolveDetail that matches THIS key's variable (not just outermost)
|
|
354
|
+
let matchingDetail = null
|
|
355
|
+
for (const instance of varInstances) {
|
|
356
|
+
if (instance.resolveDetails && instance.resolveDetails.length > 0) {
|
|
357
|
+
const found = instance.resolveDetails.find((detail) => {
|
|
358
|
+
const detailVar = detail.valueBeforeFallback || detail.variable
|
|
359
|
+
return detailVar === keyVarName
|
|
360
|
+
})
|
|
361
|
+
if (found) {
|
|
362
|
+
matchingDetail = found
|
|
363
|
+
break
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Fallback to last resolveDetail if no match found
|
|
368
|
+
if (!matchingDetail) {
|
|
369
|
+
matchingDetail = firstInstance.resolveDetails[firstInstance.resolveDetails.length - 1]
|
|
370
|
+
}
|
|
320
371
|
|
|
321
372
|
// Get the base variable name without fallback
|
|
322
373
|
// Use valueBeforeFallback if present, otherwise use the variable string
|
|
323
|
-
let baseVar =
|
|
374
|
+
let baseVar = matchingDetail.valueBeforeFallback || matchingDetail.variable
|
|
324
375
|
|
|
325
376
|
// Strip filters from baseVar using known filter names
|
|
326
377
|
// e.g., "opt:stage | toUpperCase | help(...)" -> "opt:stage"
|
|
@@ -338,29 +389,13 @@ function enrichMetadata(
|
|
|
338
389
|
}
|
|
339
390
|
|
|
340
391
|
// Normalize file() and text() references
|
|
341
|
-
|
|
342
|
-
// Strip sub-key accessors like :topLevel, :nested.value, etc.
|
|
343
|
-
baseVar = baseVar.replace(/:[\w.[\]]+$/, '')
|
|
344
|
-
|
|
345
|
-
// Normalize path - remove quotes and ensure it starts with ./
|
|
346
|
-
baseVar = baseVar.replace(/^(file|text)\((.+?)\)/, (match, funcName, filePath) => {
|
|
347
|
-
// Remove surrounding quotes (single or double)
|
|
348
|
-
let cleanPath = filePath.trim().replace(/^["']|["']$/g, '')
|
|
349
|
-
|
|
350
|
-
// Use normalizePath for consistent normalization (handles ./, .// etc)
|
|
351
|
-
const normalized = normalizePath(cleanPath)
|
|
352
|
-
if (normalized) {
|
|
353
|
-
return `${funcName}(${normalized})`
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return match
|
|
357
|
-
})
|
|
358
|
-
}
|
|
392
|
+
baseVar = normalizeFileVariable(baseVar)
|
|
359
393
|
|
|
360
394
|
if (!uniqueVariablesMap.has(baseVar)) {
|
|
361
395
|
uniqueVariablesMap.set(baseVar, {
|
|
362
396
|
variable: baseVar,
|
|
363
|
-
variableType:
|
|
397
|
+
variableType: matchingDetail.variableType,
|
|
398
|
+
variableSourceType: getSourceForType(matchingDetail.variableType, variableTypes),
|
|
364
399
|
occurrences: [],
|
|
365
400
|
innerVariables: [],
|
|
366
401
|
})
|
|
@@ -381,9 +416,25 @@ function enrichMetadata(
|
|
|
381
416
|
// The outermost variable is the last one in resolveDetails
|
|
382
417
|
const outermostDetail = instance.resolveDetails[instance.resolveDetails.length - 1]
|
|
383
418
|
|
|
419
|
+
// Find position of first | (filter separator) in the outermost variable's original string
|
|
420
|
+
// Variables after this position are filter-internal (e.g., inside help('...${var}...'))
|
|
421
|
+
const originalStr = outermostDetail.originalStringValue || ''
|
|
422
|
+
const outerVarStart = outermostDetail.start || 0
|
|
423
|
+
// Find first | that's inside the outermost variable (after its opening ${)
|
|
424
|
+
const filterSeparatorPos = originalStr.indexOf('|', outerVarStart + 2)
|
|
425
|
+
|
|
384
426
|
for (let i = 0; i < instance.resolveDetails.length - 1; i++) {
|
|
385
427
|
const detail = instance.resolveDetails[i]
|
|
386
428
|
|
|
429
|
+
// Check if this variable is inside filter arguments (after the first |)
|
|
430
|
+
// These are meta-variables used to build filter values, not user-configurable variables
|
|
431
|
+
const isInsideFilter = filterSeparatorPos !== -1 && detail.start > filterSeparatorPos
|
|
432
|
+
if (isInsideFilter) {
|
|
433
|
+
// Mark this detail as filter-internal and skip adding to uniqueVariables
|
|
434
|
+
detail.isFilterInnerVariable = true
|
|
435
|
+
continue
|
|
436
|
+
}
|
|
437
|
+
|
|
387
438
|
// Check if this variable is actually INSIDE the outermost variable's boundaries
|
|
388
439
|
// A variable is "inner" only if it's contained within the parent's start/end range
|
|
389
440
|
const isInnerVariable = detail.start >= outermostDetail.start && detail.end <= outermostDetail.end
|
|
@@ -394,23 +445,14 @@ function enrichMetadata(
|
|
|
394
445
|
const siblingBaseVar = detail.valueBeforeFallback || detail.variable
|
|
395
446
|
|
|
396
447
|
// Normalize file/text references for sibling too
|
|
397
|
-
|
|
398
|
-
if (normalizedSiblingVar.match(/^(?:file|text)\(/)) {
|
|
399
|
-
// Strip sub-key accessor (e.g., :foo from file(./_inner.yml):foo)
|
|
400
|
-
normalizedSiblingVar = normalizedSiblingVar.replace(/:[\w.[\]]+$/, '')
|
|
401
|
-
|
|
402
|
-
normalizedSiblingVar = normalizedSiblingVar.replace(/^(file|text)\((.+?)\)/, (match, funcName, filePath) => {
|
|
403
|
-
let cleanPath = filePath.trim().replace(/^["']|["']$/g, '')
|
|
404
|
-
const normalized = normalizePath(cleanPath)
|
|
405
|
-
return normalized ? `${funcName}(${normalized})` : match
|
|
406
|
-
})
|
|
407
|
-
}
|
|
448
|
+
const normalizedSiblingVar = normalizeFileVariable(siblingBaseVar)
|
|
408
449
|
|
|
409
450
|
// Create or get entry for this sibling variable
|
|
410
451
|
if (!uniqueVariablesMap.has(normalizedSiblingVar)) {
|
|
411
452
|
uniqueVariablesMap.set(normalizedSiblingVar, {
|
|
412
453
|
variable: normalizedSiblingVar,
|
|
413
454
|
variableType: detail.variableType,
|
|
455
|
+
variableSourceType: getSourceForType(detail.variableType, variableTypes),
|
|
414
456
|
occurrences: [],
|
|
415
457
|
innerVariables: [],
|
|
416
458
|
})
|
|
@@ -419,11 +461,29 @@ function enrichMetadata(
|
|
|
419
461
|
const siblingEntry = uniqueVariablesMap.get(normalizedSiblingVar)
|
|
420
462
|
|
|
421
463
|
// Add occurrence for this sibling variable
|
|
464
|
+
// Check if this self-reference resolves to a value in originalConfig
|
|
465
|
+
let siblingDefaultValue = detail.hasFallback ? (detail.fallbackValues?.[0]?.stringValue || detail.fallbackValues?.[0]?.variable) : undefined
|
|
466
|
+
let siblingDefaultValueSrc
|
|
467
|
+
let siblingIsRequired = !detail.hasFallback
|
|
468
|
+
|
|
469
|
+
if (detail.variableType === 'self' || detail.variableType === 'dot.prop') {
|
|
470
|
+
const varPath = (detail.valueBeforeFallback || detail.variable).replace('self:', '')
|
|
471
|
+
const resolvedValue = dotProp.get(originalConfig, varPath)
|
|
472
|
+
if (resolvedValue !== undefined) {
|
|
473
|
+
siblingDefaultValue = resolvedValue
|
|
474
|
+
siblingDefaultValueSrc = varPath
|
|
475
|
+
siblingIsRequired = false
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
422
479
|
const siblingOccurrence = createOccurrence(instance, detail.varMatch, {
|
|
423
|
-
isRequired:
|
|
480
|
+
isRequired: siblingIsRequired,
|
|
424
481
|
hasFallback: !!detail.hasFallback,
|
|
425
|
-
defaultValue:
|
|
482
|
+
defaultValue: siblingDefaultValue,
|
|
426
483
|
})
|
|
484
|
+
if (siblingDefaultValueSrc) {
|
|
485
|
+
siblingOccurrence.defaultValueSrc = siblingDefaultValueSrc
|
|
486
|
+
}
|
|
427
487
|
|
|
428
488
|
// Check if this exact occurrence already exists
|
|
429
489
|
const occurrenceExists = siblingEntry.occurrences.some(occ =>
|
|
@@ -512,12 +572,7 @@ function enrichMetadata(
|
|
|
512
572
|
}
|
|
513
573
|
|
|
514
574
|
// Normalize file paths after variable substitution
|
|
515
|
-
|
|
516
|
-
resolvedVariable = resolvedVariable.replace(/^(file|text)\((.+?)\)/, (match, funcName, filePath) => {
|
|
517
|
-
const normalized = normalizePath(filePath)
|
|
518
|
-
return normalized ? `${funcName}(${normalized})` : match
|
|
519
|
-
})
|
|
520
|
-
}
|
|
575
|
+
resolvedVariable = normalizeFileVariable(resolvedVariable)
|
|
521
576
|
|
|
522
577
|
// Update the variable to the resolved version and update map key
|
|
523
578
|
if (resolvedVariable !== baseVar) {
|
|
@@ -577,9 +632,104 @@ function enrichMetadata(
|
|
|
577
632
|
}
|
|
578
633
|
}
|
|
579
634
|
|
|
635
|
+
// Aggregate types and descriptions for each uniqueVariable
|
|
636
|
+
for (const [varKey, entry] of uniqueVariablesMap) {
|
|
637
|
+
// Collect unique types from occurrences
|
|
638
|
+
const types = entry.occurrences
|
|
639
|
+
.map(occ => occ.type)
|
|
640
|
+
.filter((t, i, a) => t && a.indexOf(t) === i)
|
|
641
|
+
if (types.length > 0) {
|
|
642
|
+
entry.types = types
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Collect unique descriptions from occurrences and resolve any variables
|
|
646
|
+
const rawDescriptions = entry.occurrences
|
|
647
|
+
.map(occ => occ.description)
|
|
648
|
+
.filter((d, i, a) => d && a.indexOf(d) === i)
|
|
649
|
+
|
|
650
|
+
if (rawDescriptions.length > 0) {
|
|
651
|
+
// Build map from raw -> resolved description
|
|
652
|
+
const descriptionMap = new Map()
|
|
653
|
+
await Promise.all(
|
|
654
|
+
rawDescriptions.map(async desc => {
|
|
655
|
+
// Resolve any variables in the description
|
|
656
|
+
const resolved = await preResolveString(desc, resolveContext)
|
|
657
|
+
descriptionMap.set(desc, resolved)
|
|
658
|
+
})
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
// Update each occurrence's description with resolved version
|
|
662
|
+
for (const occ of entry.occurrences) {
|
|
663
|
+
if (occ.description && descriptionMap.has(occ.description)) {
|
|
664
|
+
occ.description = descriptionMap.get(occ.description)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
entry.descriptions = Array.from(descriptionMap.values())
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Try to resolve defaultValue for variables that can be pre-resolved
|
|
672
|
+
// This applies to git:, env:, self: variables without existing defaults
|
|
673
|
+
const firstOcc = entry.occurrences[0]
|
|
674
|
+
if (firstOcc && firstOcc.isRequired && !firstOcc.defaultValue) {
|
|
675
|
+
const resolvableTypes = ['git', 'env', 'self', 'dot.prop']
|
|
676
|
+
if (resolvableTypes.includes(entry.variableType)) {
|
|
677
|
+
try {
|
|
678
|
+
const resolved = await preResolveSingle(entry.variable, resolveContext)
|
|
679
|
+
if (resolved !== undefined) {
|
|
680
|
+
const formattedValue = Array.isArray(resolved)
|
|
681
|
+
? resolved.join(', ')
|
|
682
|
+
: (typeof resolved === 'object' ? JSON.stringify(resolved) : String(resolved))
|
|
683
|
+
|
|
684
|
+
// Update occurrences and entry
|
|
685
|
+
entry.occurrences.forEach(occ => {
|
|
686
|
+
occ.defaultValue = formattedValue
|
|
687
|
+
occ.isRequired = false
|
|
688
|
+
})
|
|
689
|
+
entry.resolvedValue = formattedValue
|
|
690
|
+
}
|
|
691
|
+
} catch (e) {
|
|
692
|
+
// Couldn't resolve, leave as required
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
580
698
|
// Convert map to object for metadata
|
|
581
699
|
metadata.uniqueVariables = Object.fromEntries(uniqueVariablesMap)
|
|
582
700
|
|
|
701
|
+
// Add resolvedPropertyValue to resolutionTracking if resolvedConfig provided
|
|
702
|
+
if (resolvedConfig) {
|
|
703
|
+
metadata.resolutionHistory = {}
|
|
704
|
+
for (const pathKey in resolutionTracking) {
|
|
705
|
+
const tracking = resolutionTracking[pathKey]
|
|
706
|
+
const keys = pathKey.split('.')
|
|
707
|
+
let resolvedValue = resolvedConfig
|
|
708
|
+
|
|
709
|
+
for (const key of keys) {
|
|
710
|
+
if (resolvedValue && typeof resolvedValue === 'object') {
|
|
711
|
+
resolvedValue = resolvedValue[key]
|
|
712
|
+
} else {
|
|
713
|
+
resolvedValue = undefined
|
|
714
|
+
break
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Unescape any __CONFIGVAR:...__ placeholders in tracking data
|
|
719
|
+
// (resolutionTracking uses escaped config during resolution)
|
|
720
|
+
const cleanTracking = JSON.parse(
|
|
721
|
+
JSON.stringify(tracking).replace(/__CONFIGVAR:([A-Za-z0-9+/=]+)__/g, (_, encoded) => {
|
|
722
|
+
return Buffer.from(encoded, 'base64').toString('utf8')
|
|
723
|
+
})
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
metadata.resolutionHistory[pathKey] = {
|
|
727
|
+
...cleanTracking,
|
|
728
|
+
resolvedPropertyValue: resolvedValue
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
583
733
|
return metadata
|
|
584
734
|
}
|
|
585
735
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const YAML = require('
|
|
2
|
-
const TOML = require('
|
|
3
|
-
const INI = require('
|
|
4
|
-
const JSON5 = require('
|
|
5
|
-
const { executeTypeScriptFileSync } = require('
|
|
6
|
-
const { executeESMFileSync } = require('
|
|
1
|
+
const YAML = require('../../parsers/yaml')
|
|
2
|
+
const TOML = require('../../parsers/toml')
|
|
3
|
+
const INI = require('../../parsers/ini')
|
|
4
|
+
const JSON5 = require('../../parsers/json5')
|
|
5
|
+
const { executeTypeScriptFileSync } = require('../../parsers/typescript')
|
|
6
|
+
const { executeESMFileSync } = require('../../parsers/esm')
|
|
7
7
|
const cloudFormationSchema = require('./cloudformationSchema')
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -18,7 +18,7 @@ const cloudFormationSchema = require('./cloudformationSchema')
|
|
|
18
18
|
function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}) {
|
|
19
19
|
let configObject
|
|
20
20
|
|
|
21
|
-
if (fileType.match(/\.(yml|yaml)/)) {
|
|
21
|
+
if (fileType.match(/\.(yml|yaml)/i)) {
|
|
22
22
|
try {
|
|
23
23
|
const ymlText = YAML.preProcess(fileContents, varRegex)
|
|
24
24
|
configObject = YAML.parse(ymlText)
|
|
@@ -36,14 +36,14 @@ function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}
|
|
|
36
36
|
configObject = result.data
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
} else if (fileType.match(/\.(toml|tml)/)) {
|
|
39
|
+
} else if (fileType.match(/\.(toml|tml)/i)) {
|
|
40
40
|
configObject = TOML.parse(fileContents)
|
|
41
|
-
} else if (fileType.match(/\.(ini)/)) {
|
|
41
|
+
} else if (fileType.match(/\.(ini)/i)) {
|
|
42
42
|
configObject = INI.parse(fileContents)
|
|
43
|
-
} else if (fileType.match(/\.(json|json5)/)) {
|
|
43
|
+
} else if (fileType.match(/\.(json|json5)/i)) {
|
|
44
44
|
configObject = JSON5.parse(fileContents)
|
|
45
45
|
// TODO detect js syntax and use appropriate parser
|
|
46
|
-
} else if (fileType.match(/\.(js|cjs)/)) {
|
|
46
|
+
} else if (fileType.match(/\.(js|cjs)/i)) {
|
|
47
47
|
let jsFile
|
|
48
48
|
try {
|
|
49
49
|
jsFile = require(filePath)
|
|
@@ -60,7 +60,7 @@ function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}
|
|
|
60
60
|
} catch (err) {
|
|
61
61
|
throw new Error(err)
|
|
62
62
|
}
|
|
63
|
-
} else if (fileType.match(/\.(ts|tsx)/)) {
|
|
63
|
+
} else if (fileType.match(/\.(ts|tsx|mts|cts)/i)) {
|
|
64
64
|
try {
|
|
65
65
|
let jsArgs = opts.dynamicArgs || {}
|
|
66
66
|
if (jsArgs && typeof jsArgs === 'function') {
|
|
@@ -76,7 +76,7 @@ function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}
|
|
|
76
76
|
} catch (err) {
|
|
77
77
|
throw new Error(`Failed to execute TypeScript file ${filePath}: ${err.message}`)
|
|
78
78
|
}
|
|
79
|
-
} else if (fileType.match(/\.(mjs|esm)/)) {
|
|
79
|
+
} else if (fileType.match(/\.(mjs|esm)/i)) {
|
|
80
80
|
try {
|
|
81
81
|
let jsArgs = opts.dynamicArgs || {}
|
|
82
82
|
if (jsArgs && typeof jsArgs === 'function') {
|