configorama 0.9.14 → 0.9.16
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 +55 -878
- 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/src/main.js
CHANGED
|
@@ -10,7 +10,6 @@ console.log = () => {}
|
|
|
10
10
|
const findUp = require('find-up')
|
|
11
11
|
const traverse = require('traverse')
|
|
12
12
|
const dotProp = require('dot-prop')
|
|
13
|
-
const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
|
|
14
13
|
/* Utils - root */
|
|
15
14
|
const {
|
|
16
15
|
isArray, isString, isNumber, isObject, isDate, isRegExp, isFunction,
|
|
@@ -30,8 +29,7 @@ const { parseFileContents } = require('./utils/parsing/parse')
|
|
|
30
29
|
const { mergeByKeys } = require('./utils/parsing/mergeByKeys')
|
|
31
30
|
const { arrayToJsonPath } = require('./utils/parsing/arrayToJsonPath')
|
|
32
31
|
/* Utils - paths */
|
|
33
|
-
const {
|
|
34
|
-
const { findLineForKey } = require('./utils/paths/findLineForKey')
|
|
32
|
+
const { findLineByPath } = require('./utils/paths/findLineForKey')
|
|
35
33
|
/* Utils - regex */
|
|
36
34
|
const { combineRegexes, funcRegex, fileRefSyntax, textRefSyntax } = require('./utils/regex')
|
|
37
35
|
/* Utils - strings */
|
|
@@ -46,8 +44,11 @@ const { splitOnPipe } = require('./utils/strings/splitOnPipe')
|
|
|
46
44
|
const chalk = require('./utils/ui/chalk')
|
|
47
45
|
const deepLog = require('./utils/ui/deep-log')
|
|
48
46
|
const { logHeader } = require('./utils/ui/logs')
|
|
49
|
-
const {
|
|
50
|
-
|
|
47
|
+
const { runConfigWizard } = require('./utils/ui/configWizard')
|
|
48
|
+
/* Display */
|
|
49
|
+
const { displayNoVariablesFound, displayVariableDetails, displayUniqueVariables, displayConfigurableVariables } = require('./display')
|
|
50
|
+
/* Metadata */
|
|
51
|
+
const { collectVariableMetadata: collectMetadata } = require('./metadata')
|
|
51
52
|
/* Utils - validation */
|
|
52
53
|
const { warnIfNotFound, isValidValue } = require('./utils/validation/warnIfNotFound')
|
|
53
54
|
/* Utils - variables */
|
|
@@ -127,6 +128,10 @@ class Configorama {
|
|
|
127
128
|
returnMetadata: false,
|
|
128
129
|
// Return preResolvedVariableDetails
|
|
129
130
|
returnPreResolvedVariableDetails: false,
|
|
131
|
+
// Suppress env-stage-loader's normal dotenv loading logs by default.
|
|
132
|
+
// CLI users can still see them with --verbose or dotEnvSilent: false.
|
|
133
|
+
dotEnvSilent: !VERBOSE,
|
|
134
|
+
dotEnvDebug: false,
|
|
130
135
|
}, options)
|
|
131
136
|
|
|
132
137
|
// Backward compat: allowUnknownVars -> allowUnknownVariableTypes
|
|
@@ -159,6 +164,8 @@ class Configorama {
|
|
|
159
164
|
|
|
160
165
|
// Track variable resolutions for metadata (keyed by path)
|
|
161
166
|
this.resolutionTracking = {}
|
|
167
|
+
// Only track per-call metadata when returnMetadata is requested
|
|
168
|
+
this._trackCalls = !!(this.settings.returnMetadata)
|
|
162
169
|
|
|
163
170
|
// Detect file type early to determine default syntax
|
|
164
171
|
let detectedFileType = null
|
|
@@ -739,457 +746,25 @@ class Configorama {
|
|
|
739
746
|
}
|
|
740
747
|
|
|
741
748
|
if (!varKeys.length) {
|
|
742
|
-
|
|
743
|
-
if (this.configFilePath) {
|
|
744
|
-
console.log(`File: ${this.configFilePath}`)
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
console.log(`\nVariable syntax: `, variableSyntax)
|
|
748
|
-
|
|
749
|
-
const varTypes = Object.keys(this.variableTypes)
|
|
750
|
-
if (varTypes.length) {
|
|
751
|
-
const exclude = ['dot.prop', 'deep']
|
|
752
|
-
console.log('\nAllowed variable types:')
|
|
753
|
-
varTypes.forEach((v) => {
|
|
754
|
-
const vData = this.variableTypes[v]
|
|
755
|
-
if (exclude.includes(vData.type)) {
|
|
756
|
-
return
|
|
757
|
-
}
|
|
758
|
-
console.log(` - ${vData.type}: `, vData.match)
|
|
759
|
-
})
|
|
760
|
-
}
|
|
761
|
-
console.log()
|
|
749
|
+
displayNoVariablesFound(this.configFilePath, variableSyntax, this.variableTypes)
|
|
762
750
|
}
|
|
763
751
|
|
|
764
752
|
const lines = this.configFileContents ? this.configFileContents.split('\n') : []
|
|
765
753
|
const fileType = this.configFileType
|
|
766
754
|
const configFilePath = this.configFilePath
|
|
767
755
|
|
|
768
|
-
|
|
769
|
-
const fileName = this.configFilePath ? ` in ${this.configFilePath}` : ''
|
|
770
|
-
|
|
771
|
-
// Extract base variable name from varMatch key (e.g., '${env:FOO, default}' -> 'env:FOO')
|
|
772
|
-
const getBaseVarName = (key) => key.replace(this.varPrefixPattern, '').replace(this.varSuffixPattern, '').split(',')[0].trim()
|
|
773
|
-
|
|
774
|
-
logHeader(`Found ${varKeys.length} Variables${fileName}`)
|
|
775
|
-
|
|
776
|
-
// deepLog('variableData', variableData)
|
|
777
|
-
|
|
778
|
-
if (varKeys.length) {
|
|
779
|
-
console.log()
|
|
780
|
-
const longestKey = varKeys.reduce((acc, k) => {
|
|
781
|
-
return Math.max(acc, k.length)
|
|
782
|
-
}, 0)
|
|
783
|
-
|
|
784
|
-
// Use uniqueVariables for simpler reference counting
|
|
785
|
-
const referenceData = varKeys.map((k) => {
|
|
786
|
-
const varName = getBaseVarName(k)
|
|
787
|
-
const uniqueVar = uniqueVariables[varName]
|
|
788
|
-
const refCount = uniqueVar ? uniqueVar.occurrences.length : variableData[k].length
|
|
789
|
-
const placesWord = refCount > 1 ? 'places' : 'place'
|
|
790
|
-
return `- ${k.padEnd(longestKey).padEnd(longestKey + 10)} referenced ${refCount} ${placesWord}`
|
|
791
|
-
}).join('\n')
|
|
792
|
-
|
|
793
|
-
console.log(`${referenceData}\n`)
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
logHeader('Variable Details')
|
|
797
|
-
|
|
798
|
-
const indent = ''
|
|
799
|
-
const boxes = varKeys.map((key, i) => {
|
|
800
|
-
const variableInstances = variableData[key]
|
|
801
|
-
// console.log('variableInstances', variableInstances)
|
|
802
|
-
const firstInstance = variableInstances[0]
|
|
803
|
-
|
|
804
|
-
// Get uniqueVariable data for description and other metadata
|
|
805
|
-
const varName = getBaseVarName(key)
|
|
806
|
-
const uniqueVar = uniqueVariables[varName]
|
|
807
|
-
|
|
808
|
-
// Build display message from enriched metadata
|
|
809
|
-
const spacing = ' '
|
|
810
|
-
const titleText = `Variable:${spacing}`
|
|
811
|
-
const VALUE_HEX = '#899499'
|
|
812
|
-
const keyChalk = chalk.whiteBright
|
|
813
|
-
const valueChalk = chalk.hex(VALUE_HEX)
|
|
814
|
-
|
|
815
|
-
let varMsg = ''
|
|
816
|
-
let requiredMessage = ''
|
|
817
|
-
|
|
818
|
-
// Show required status from metadata
|
|
819
|
-
if (firstInstance.isRequired) {
|
|
820
|
-
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Show type filter if present (Boolean, String, Number, etc.)
|
|
824
|
-
if (uniqueVar && uniqueVar.types && uniqueVar.types.length > 0) {
|
|
825
|
-
const typeLabel = `${indent}${keyChalk('Type:'.padEnd(titleText.length, ' '))}`
|
|
826
|
-
varMsg += `${typeLabel} ${valueChalk(uniqueVar.types.join(', '))}\n`
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Show description from uniqueVariables if available
|
|
830
|
-
if (uniqueVar && uniqueVar.descriptions && uniqueVar.descriptions.length > 0) {
|
|
831
|
-
const descText = `${indent}${keyChalk('Description:'.padEnd(titleText.length, ' '))}`
|
|
832
|
-
const combinedDesc = uniqueVar.descriptions.join('. ')
|
|
833
|
-
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
// Show resolve order from metadata
|
|
837
|
-
if (firstInstance.resolveOrder.length > 1) {
|
|
838
|
-
varMsg += `${indent}${keyChalk('Resolve Order:'.padEnd(titleText.length, ' '))}`
|
|
839
|
-
const resolveOrder = firstInstance.resolveOrder.join(', ')
|
|
840
|
-
varMsg += ` ${valueChalk(resolveOrder)}\n`
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Show default value from metadata
|
|
844
|
-
if (typeof firstInstance.defaultValue !== 'undefined') {
|
|
845
|
-
const defaultValueRender = firstInstance.defaultValue === '' ? '""' : firstInstance.defaultValue
|
|
846
|
-
const defaultValueText = `${indent}${keyChalk('Default value:'.padEnd(titleText.length, ' '))}`
|
|
847
|
-
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}\n`
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// Show default value source path from metadata
|
|
851
|
-
if (firstInstance.defaultValueSrc) {
|
|
852
|
-
varMsg += `${indent}${keyChalk('Default path:'.padEnd(titleText.length, ' '))} `
|
|
853
|
-
const defaultPathLine = findLineForKey(firstInstance.defaultValueSrc, lines, fileType)
|
|
854
|
-
if (defaultPathLine) {
|
|
855
|
-
varMsg += `${createEditorLink(configFilePath, defaultPathLine, 1, firstInstance.defaultValueSrc, 'gray')}\n`
|
|
856
|
-
} else {
|
|
857
|
-
varMsg += `${valueChalk(firstInstance.defaultValueSrc)}\n`
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// Show path(s) from metadata
|
|
862
|
-
const configPathLine = findLineForKey(variableInstances[0].path, lines, fileType)
|
|
863
|
-
let locationRender = configPathLine
|
|
864
|
-
? createEditorLink(configFilePath, configPathLine, 1, variableInstances[0].path, 'gray')
|
|
865
|
-
: valueChalk(variableInstances[0].path)
|
|
866
|
-
let locationLabel = `${indent}${keyChalk('Config Path:'.padEnd(titleText.length, ' '))}`
|
|
867
|
-
let typeText = ''
|
|
868
|
-
if (variableInstances.length > 1) {
|
|
869
|
-
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
870
|
-
const pathItems = variableInstances.map((v, idx) => {
|
|
871
|
-
const pathLine = findLineForKey(v.path, lines, fileType)
|
|
872
|
-
const pathLink = pathLine
|
|
873
|
-
? createEditorLink(configFilePath, pathLine, 1, `- ${v.path}`, 'gray')
|
|
874
|
-
: valueChalk(`- ${v.path}`)
|
|
875
|
-
// Show type filter per path if different
|
|
876
|
-
if (uniqueVar && uniqueVar.occurrences.length > 1) {
|
|
877
|
-
const occurrence = uniqueVar.occurrences.find(occ => occ.path === v.path)
|
|
878
|
-
const pathType = occurrence && occurrence.type
|
|
879
|
-
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
880
|
-
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
881
|
-
return `${prefix}${pathLink}${typeText}`
|
|
882
|
-
}
|
|
883
|
-
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
884
|
-
return `${prefix}${pathLink}${typeText}`
|
|
885
|
-
})
|
|
886
|
-
locationRender = pathItems.join('\n')
|
|
887
|
-
locationLabel = `${indent}${keyChalk('Config Paths:'.padEnd(titleText.length, ' '))}`
|
|
888
|
-
} else {
|
|
889
|
-
const pathType = firstInstance.type
|
|
890
|
-
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
891
|
-
}
|
|
892
|
-
varMsg += `${locationLabel} ${locationRender}`
|
|
893
|
-
|
|
894
|
-
const lineNumber = findLineForKey(firstInstance.key, lines, fileType)
|
|
756
|
+
const displayParams = { lines, fileType, configFilePath, uniqueVariables, uniqueVarKeys }
|
|
895
757
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
},
|
|
902
|
-
title: {
|
|
903
|
-
left: `▷ ${lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, key) : key}`,
|
|
904
|
-
right: lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, `${requiredMessage} ${lineNumber ? `Line: ${lineNumber.toString().padEnd(2, ' ')}` : ''}`, 'gray') : '',
|
|
905
|
-
center: typeText,
|
|
906
|
-
paddingBottom: 1,
|
|
907
|
-
paddingTop: (i === 0) ? 1 : 0,
|
|
908
|
-
truncate: true,
|
|
909
|
-
},
|
|
910
|
-
width: '100%',
|
|
911
|
-
}
|
|
912
|
-
})
|
|
913
|
-
|
|
914
|
-
console.log(makeStackedBoxes(boxes, {
|
|
915
|
-
borderText: 'Variable Details. Click on titles to open in editor.',
|
|
916
|
-
borderColor: 'gray',
|
|
917
|
-
minWidth: '96%',
|
|
918
|
-
borderStyle: 'bold',
|
|
919
|
-
disableTitleSeparator: true,
|
|
920
|
-
}))
|
|
921
|
-
// process.exit(1)
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// New unique variable makeStackedBoxes display
|
|
925
|
-
const uniqueBoxes = uniqueVarKeys.map((varName, i) => {
|
|
926
|
-
const uniqueVar = uniqueVariables[varName]
|
|
927
|
-
const occurrences = uniqueVar.occurrences || []
|
|
928
|
-
const firstOcc = occurrences[0] || {}
|
|
929
|
-
|
|
930
|
-
const spacing = ' '
|
|
931
|
-
const titleText = `Variable:${spacing}`
|
|
932
|
-
const VALUE_HEX = '#899499'
|
|
933
|
-
const keyChalk = chalk.whiteBright
|
|
934
|
-
const valueChalk = chalk.hex(VALUE_HEX)
|
|
935
|
-
|
|
936
|
-
let varMsg = ''
|
|
937
|
-
let requiredMessage = ''
|
|
938
|
-
|
|
939
|
-
// Show required status from computed isRequired (accounts for resolved self-refs)
|
|
940
|
-
const isRequired = occurrences.some(occ => occ.isRequired)
|
|
941
|
-
if (isRequired) {
|
|
942
|
-
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Show type filter if present
|
|
946
|
-
if (uniqueVar.types && uniqueVar.types.length > 0) {
|
|
947
|
-
const typeLabel = `${keyChalk('Type:'.padEnd(titleText.length, ' '))}`
|
|
948
|
-
varMsg += `${typeLabel} ${valueChalk(uniqueVar.types.join(', '))}\n`
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Show description
|
|
952
|
-
if (uniqueVar.descriptions && uniqueVar.descriptions.length > 0) {
|
|
953
|
-
const descText = `${keyChalk('Description:'.padEnd(titleText.length, ' '))}`
|
|
954
|
-
const combinedDesc = uniqueVar.descriptions.join('. ')
|
|
955
|
-
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// Show default value only if it's a true fallback, not a pre-resolved value
|
|
959
|
-
// Redact sensitive values like API keys, secrets, tokens
|
|
960
|
-
const isSensitive = isSensitiveVariable(varName)
|
|
961
|
-
const hasActualDefault = firstOcc.hasFallback && typeof firstOcc.defaultValue !== 'undefined'
|
|
962
|
-
if (hasActualDefault) {
|
|
963
|
-
const defaultValueRender = isSensitive ? '********' : (firstOcc.defaultValue === '' ? '""' : firstOcc.defaultValue)
|
|
964
|
-
const defaultValueText = `${keyChalk('Default value:'.padEnd(titleText.length, ' '))}`
|
|
965
|
-
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}\n`
|
|
966
|
-
} else if (uniqueVar.resolvedValue !== undefined) {
|
|
967
|
-
// Show pre-resolved current value (e.g., from env, git)
|
|
968
|
-
const resolvedRender = isSensitive ? '********' : (uniqueVar.resolvedValue === '' ? '""' : uniqueVar.resolvedValue)
|
|
969
|
-
const resolvedText = `${keyChalk('Current value:'.padEnd(titleText.length, ' '))}`
|
|
970
|
-
const envIndicator = uniqueVar.variableType === 'env' ? ` ${chalk.red('(currently set env var)')}` : ''
|
|
971
|
-
varMsg += `${resolvedText} ${valueChalk(resolvedRender)}${envIndicator}\n`
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// Show default value source path
|
|
975
|
-
if (firstOcc.defaultValueSrc) {
|
|
976
|
-
varMsg += `${keyChalk('Default path:'.padEnd(titleText.length, ' '))} `
|
|
977
|
-
const defaultPathLine = findLineForKey(firstOcc.defaultValueSrc, lines, fileType)
|
|
978
|
-
if (defaultPathLine) {
|
|
979
|
-
varMsg += `${createEditorLink(configFilePath, defaultPathLine, 1, firstOcc.defaultValueSrc, 'gray')}\n`
|
|
980
|
-
} else {
|
|
981
|
-
varMsg += `${valueChalk(firstOcc.defaultValueSrc)}\n`
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// Show config path(s) from occurrences
|
|
986
|
-
let locationRender
|
|
987
|
-
let locationLabel
|
|
988
|
-
if (occurrences.length > 1) {
|
|
989
|
-
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
990
|
-
const pathItems = occurrences.map((occ, idx) => {
|
|
991
|
-
const pathLine = findLineForKey(occ.path, lines, fileType)
|
|
992
|
-
const pathLink = pathLine
|
|
993
|
-
? createEditorLink(configFilePath, pathLine, 1, `- ${occ.path}`, 'gray')
|
|
994
|
-
: valueChalk(`- ${occ.path}`)
|
|
995
|
-
const typeText = occ.type ? ` ${chalk.dim(`Type: ${occ.type}`)}` : ''
|
|
996
|
-
const prefix = idx === 0 ? '' : `${pathIndent}`
|
|
997
|
-
return `${prefix}${pathLink}${typeText}`
|
|
998
|
-
})
|
|
999
|
-
locationRender = pathItems.join('\n')
|
|
1000
|
-
locationLabel = `${keyChalk('Config Paths:'.padEnd(titleText.length, ' '))}`
|
|
1001
|
-
} else {
|
|
1002
|
-
const pathLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
1003
|
-
locationRender = pathLine
|
|
1004
|
-
? createEditorLink(configFilePath, pathLine, 1, firstOcc.path, 'gray')
|
|
1005
|
-
: valueChalk(firstOcc.path)
|
|
1006
|
-
locationLabel = `${keyChalk('Config Path:'.padEnd(titleText.length, ' '))}`
|
|
1007
|
-
}
|
|
1008
|
-
varMsg += `${locationLabel} ${locationRender}`
|
|
1009
|
-
|
|
1010
|
-
// Find first line number for title
|
|
1011
|
-
const lineNumber = findLineForKey(firstOcc.path, lines, fileType)
|
|
1012
|
-
|
|
1013
|
-
return {
|
|
1014
|
-
content: {
|
|
1015
|
-
left: varMsg,
|
|
1016
|
-
backgroundColor: 'red',
|
|
1017
|
-
width: '100%',
|
|
1018
|
-
},
|
|
1019
|
-
title: {
|
|
1020
|
-
left: `▷ ${firstOcc.varMatch}`,
|
|
1021
|
-
right: `${requiredMessage} ${lineNumber ? `Line: ${lineNumber.toString().padEnd(2, ' ')}` : ''}`,
|
|
1022
|
-
paddingBottom: 1,
|
|
1023
|
-
paddingTop: (i === 0) ? 1 : 0,
|
|
1024
|
-
truncate: true,
|
|
1025
|
-
},
|
|
1026
|
-
width: '100%',
|
|
1027
|
-
}
|
|
758
|
+
displayVariableDetails({
|
|
759
|
+
varKeys, variableData, uniqueVariables,
|
|
760
|
+
varPrefixPattern: this.varPrefixPattern,
|
|
761
|
+
varSuffixPattern: this.varSuffixPattern,
|
|
762
|
+
lines, fileType, configFilePath,
|
|
1028
763
|
})
|
|
1029
764
|
|
|
1030
|
-
|
|
1031
|
-
borderText: 'Unique Variables',
|
|
1032
|
-
borderColor: 'gray',
|
|
1033
|
-
minWidth: '96%',
|
|
1034
|
-
borderStyle: 'bold',
|
|
1035
|
-
disableTitleSeparator: true,
|
|
1036
|
-
}))
|
|
1037
|
-
console.log()
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
// Unique variables that require setup (excludes readonly source types)
|
|
1041
|
-
const CONFIGURABLE_SOURCES = ['user', 'config', 'remote']
|
|
1042
|
-
const configurableVariables = {}
|
|
1043
|
-
const configurableVarKeys = []
|
|
1044
|
-
|
|
1045
|
-
for (const varName of uniqueVarKeys) {
|
|
1046
|
-
const uniqueVar = uniqueVariables[varName]
|
|
1047
|
-
// Include if source type is user, config, or remote (not readonly)
|
|
1048
|
-
if (CONFIGURABLE_SOURCES.includes(uniqueVar.variableSourceType)) {
|
|
1049
|
-
configurableVariables[varName] = uniqueVar
|
|
1050
|
-
configurableVarKeys.push(varName)
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
// Display configurable variables by source type
|
|
1055
|
-
if (configurableVarKeys.length > 0) {
|
|
1056
|
-
const spacing = ' '
|
|
1057
|
-
const titleText = `Variable:${spacing}`
|
|
1058
|
-
const VALUE_HEX = '#899499'
|
|
1059
|
-
const keyChalk = chalk.whiteBright
|
|
1060
|
-
const valueChalk = chalk.hex(VALUE_HEX)
|
|
1061
|
-
|
|
1062
|
-
// Group by source type
|
|
1063
|
-
const bySource = {
|
|
1064
|
-
user: [],
|
|
1065
|
-
config: [],
|
|
1066
|
-
remote: [],
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
for (const varName of configurableVarKeys) {
|
|
1070
|
-
const v = configurableVariables[varName]
|
|
1071
|
-
const sourceType = v.variableSourceType || 'user'
|
|
1072
|
-
if (bySource[sourceType]) {
|
|
1073
|
-
bySource[sourceType].push({ varName, ...v })
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
const sourceLabels = {
|
|
1078
|
-
user: 'User Input Required',
|
|
1079
|
-
config: 'Config References',
|
|
1080
|
-
remote: 'Remote Services',
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
const sourceColors = {
|
|
1084
|
-
user: 'yellow',
|
|
1085
|
-
config: 'cyan',
|
|
1086
|
-
remote: 'magenta',
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
const configurableBoxes = []
|
|
1090
|
-
|
|
1091
|
-
for (const [sourceType, vars] of Object.entries(bySource)) {
|
|
1092
|
-
if (vars.length === 0) continue
|
|
765
|
+
displayUniqueVariables(displayParams)
|
|
1093
766
|
|
|
1094
|
-
|
|
1095
|
-
const v = vars[i]
|
|
1096
|
-
const occurrences = v.occurrences || []
|
|
1097
|
-
const firstOcc = occurrences[0] || {}
|
|
1098
|
-
|
|
1099
|
-
let varMsg = ''
|
|
1100
|
-
let requiredMessage = ''
|
|
1101
|
-
|
|
1102
|
-
// Show required status from computed isRequired (accounts for resolved self-refs)
|
|
1103
|
-
const isRequired = occurrences.some(occ => occ.isRequired)
|
|
1104
|
-
if (isRequired) {
|
|
1105
|
-
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// Show description if present (directly under title, not as key/value)
|
|
1109
|
-
if (v.descriptions && v.descriptions.length > 0) {
|
|
1110
|
-
varMsg += `${chalk.dim(v.descriptions.join('. '))}\n\n`
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// Show type filter if defined (String, Number, etc.)
|
|
1114
|
-
const varType = (v.types && v.types[0]) || firstOcc.type
|
|
1115
|
-
if (varType) {
|
|
1116
|
-
varMsg += `${keyChalk('Type:'.padEnd(titleText.length, ' '))} ${valueChalk(varType)}\n`
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
// Show current/default value (redact sensitive values)
|
|
1120
|
-
const isSensitive = isSensitiveVariable(v.varName)
|
|
1121
|
-
if (v.resolvedValue !== undefined) {
|
|
1122
|
-
const resolvedRender = isSensitive ? '********' : (v.resolvedValue === '' ? '""' : v.resolvedValue)
|
|
1123
|
-
varMsg += `${keyChalk('Current value:'.padEnd(titleText.length, ' '))} ${valueChalk(resolvedRender)}\n`
|
|
1124
|
-
} else if (firstOcc.hasFallback && firstOcc.defaultValue !== undefined) {
|
|
1125
|
-
const defaultRender = isSensitive ? '********' : (firstOcc.defaultValue === '' ? '""' : firstOcc.defaultValue)
|
|
1126
|
-
varMsg += `${keyChalk('Default value:'.padEnd(titleText.length, ' '))} ${valueChalk(defaultRender)}\n`
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// Show config path(s)
|
|
1130
|
-
let locationRender
|
|
1131
|
-
let locationLabel
|
|
1132
|
-
if (occurrences.length > 1) {
|
|
1133
|
-
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
1134
|
-
const pathItems = occurrences.map((occ, idx) => {
|
|
1135
|
-
const pathLine = findLineForKey(occ.path, lines, fileType)
|
|
1136
|
-
const pathLink = pathLine
|
|
1137
|
-
? createEditorLink(configFilePath, pathLine, 1, `- ${occ.path}`, VALUE_HEX)
|
|
1138
|
-
: valueChalk(`- ${occ.path}`)
|
|
1139
|
-
const prefix = idx === 0 ? '' : `${pathIndent}`
|
|
1140
|
-
return `${prefix}${pathLink}`
|
|
1141
|
-
})
|
|
1142
|
-
locationRender = pathItems.join('\n')
|
|
1143
|
-
locationLabel = 'Config Paths:'
|
|
1144
|
-
} else {
|
|
1145
|
-
const pathLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
1146
|
-
locationRender = pathLine
|
|
1147
|
-
? createEditorLink(configFilePath, pathLine, 1, firstOcc.path, VALUE_HEX)
|
|
1148
|
-
: valueChalk(firstOcc.path)
|
|
1149
|
-
locationLabel = 'Config Path:'
|
|
1150
|
-
}
|
|
1151
|
-
varMsg += `${keyChalk(locationLabel.padEnd(titleText.length, ' '))} ${locationRender}`
|
|
1152
|
-
|
|
1153
|
-
// Get type for center heading (reuse varType from above)
|
|
1154
|
-
const typeText = varType ? chalk.dim(`Type: ${varType}`) : ''
|
|
1155
|
-
|
|
1156
|
-
// Get line number for first occurrence
|
|
1157
|
-
const firstOccLine = findLineForKey(firstOcc.path, lines, fileType)
|
|
1158
|
-
const varTitle = firstOcc.varMatch || v.varName
|
|
1159
|
-
const requiredSuffix = requiredMessage ? ` - ${requiredMessage}` : ''
|
|
1160
|
-
const titleLink = firstOccLine
|
|
1161
|
-
? createEditorLink(configFilePath, firstOccLine, 1, `▷ ${varTitle}`) + requiredSuffix
|
|
1162
|
-
: `▷ ${varTitle}${requiredSuffix}`
|
|
1163
|
-
|
|
1164
|
-
configurableBoxes.push({
|
|
1165
|
-
content: {
|
|
1166
|
-
left: varMsg,
|
|
1167
|
-
width: '100%',
|
|
1168
|
-
},
|
|
1169
|
-
title: {
|
|
1170
|
-
left: titleLink,
|
|
1171
|
-
// center: typeText,
|
|
1172
|
-
right: chalk.dim(`${v.variableType}`),
|
|
1173
|
-
paddingBottom: 1,
|
|
1174
|
-
paddingTop: (configurableBoxes.length === 0) ? 1 : 0,
|
|
1175
|
-
truncate: true,
|
|
1176
|
-
},
|
|
1177
|
-
width: '100%',
|
|
1178
|
-
})
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (configurableBoxes.length > 0) {
|
|
1183
|
-
console.log(makeStackedBoxes(configurableBoxes, {
|
|
1184
|
-
borderText: `Configurable Variables (${configurableVarKeys.length})`,
|
|
1185
|
-
borderColor: 'yellow',
|
|
1186
|
-
minWidth: '96%',
|
|
1187
|
-
borderStyle: 'bold',
|
|
1188
|
-
disableTitleSeparator: true,
|
|
1189
|
-
}))
|
|
1190
|
-
console.log()
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
767
|
+
displayConfigurableVariables(displayParams)
|
|
1193
768
|
|
|
1194
769
|
|
|
1195
770
|
// WALK through CLI prompt if --setup flag is set
|
|
@@ -1261,8 +836,8 @@ class Configorama {
|
|
|
1261
836
|
const stage = cliOpts.stage || providerStage || process.env.NODE_ENV || 'dev'
|
|
1262
837
|
/* Load env variables into process.env */
|
|
1263
838
|
require('env-stage-loader')({
|
|
1264
|
-
|
|
1265
|
-
|
|
839
|
+
silent: this.settings.dotEnvSilent,
|
|
840
|
+
debug: this.settings.dotEnvDebug,
|
|
1266
841
|
env: stage,
|
|
1267
842
|
// defaultEnv: 'prod',
|
|
1268
843
|
// ignoreFiles: ['.env']
|
|
@@ -1423,426 +998,19 @@ class Configorama {
|
|
|
1423
998
|
return this._cachedMetadata
|
|
1424
999
|
}
|
|
1425
1000
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
const preResolvedPaths = new Set()
|
|
1438
|
-
const byConfigPath = []
|
|
1439
|
-
const referencesMap = new Map()
|
|
1440
|
-
let matchCount = 1
|
|
1441
|
-
|
|
1442
|
-
traverse(originalConfig).forEach(function (rawValue) {
|
|
1443
|
-
if (typeof rawValue === 'string' && rawValue.match(variableSyntax)) {
|
|
1444
|
-
const configValuePath = this.path.join('.')
|
|
1445
|
-
/* Skip Fn::Sub variables */
|
|
1446
|
-
if (configValuePath.endsWith('Fn::Sub')) {
|
|
1447
|
-
return
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
const nested = findNestedVariables(
|
|
1451
|
-
rawValue,
|
|
1452
|
-
variableSyntax,
|
|
1453
|
-
variablesKnownTypes,
|
|
1454
|
-
configValuePath,
|
|
1455
|
-
variableTypes
|
|
1456
|
-
)
|
|
1457
|
-
|
|
1458
|
-
const lastItem = nested[nested.length - 1]
|
|
1459
|
-
const lastKeyPath = this.path[this.path.length - 1]
|
|
1460
|
-
const itemKey = (lastKeyPath.match(/[\d+]$/)) ? `${this.path[this.path.length - 2]}[${lastKeyPath}]` : lastKeyPath
|
|
1461
|
-
|
|
1462
|
-
// Extract filters from varMatch
|
|
1463
|
-
const originalSrc = lastItem.varMatch || ''
|
|
1464
|
-
const hasFilters = filterMatch && originalSrc.match(filterMatch)
|
|
1465
|
-
let foundFilters = []
|
|
1466
|
-
let keyWithoutFilters = originalSrc
|
|
1467
|
-
|
|
1468
|
-
if (hasFilters) {
|
|
1469
|
-
// Extract filter names from the match (e.g., "| String}" -> ["String"])
|
|
1470
|
-
const filterPart = hasFilters[0].replace(/}?$/, '') // Remove trailing }
|
|
1471
|
-
foundFilters = splitOnPipe(filterPart)
|
|
1472
|
-
.map((filter) => filter.trim())
|
|
1473
|
-
.filter(Boolean)
|
|
1474
|
-
|
|
1475
|
-
// Remove filters from the key (replace "| String}" with suffix)
|
|
1476
|
-
// Also clean up any trailing whitespace before the closing brace
|
|
1477
|
-
keyWithoutFilters = originalSrc.replace(filterMatch, this.varSuffix).replace(this.varSuffixWithSpacePattern, this.varSuffix)
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
const key = keyWithoutFilters
|
|
1481
|
-
|
|
1482
|
-
// Helper to pre-resolve a variable from config
|
|
1483
|
-
const preResolveFromConfig = (varString, varType) => {
|
|
1484
|
-
if (!varString) return undefined
|
|
1485
|
-
// Handle self: prefix
|
|
1486
|
-
const varPath = varString.startsWith('self:') ? varString.slice(5) : varString
|
|
1487
|
-
// Only pre-resolve dot.prop and self references
|
|
1488
|
-
if (varType === 'dot.prop' || varType === 'self') {
|
|
1489
|
-
const value = dotProp.get(originalConfig, varPath)
|
|
1490
|
-
if (value !== undefined && typeof value !== 'object') {
|
|
1491
|
-
return { resolved: value, path: varPath }
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
return undefined
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// Strip filters from resolveDetails
|
|
1498
|
-
const cleanedResolveDetails = nested.map(detail => {
|
|
1499
|
-
const cleaned = { ...detail }
|
|
1500
|
-
if (cleaned.varMatch && filterMatch) {
|
|
1501
|
-
const match = cleaned.varMatch.match(filterMatch)
|
|
1502
|
-
if (match) {
|
|
1503
|
-
cleaned.varMatch = cleaned.varMatch.replace(filterMatch, '').replace(/\s+$/, '') + this.varSuffix
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
if (cleaned.variable && filterMatch) {
|
|
1507
|
-
const match = cleaned.variable.match(filterMatch)
|
|
1508
|
-
if (match) {
|
|
1509
|
-
cleaned.variable = cleaned.variable.replace(filterMatch, '').replace(/\s+$/, '')
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
if (cleaned.varString && filterMatch) {
|
|
1513
|
-
const match = cleaned.varString.match(filterMatch)
|
|
1514
|
-
if (match) {
|
|
1515
|
-
cleaned.varString = cleaned.varString.replace(filterMatch, '').trim()
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// Pre-resolve dot.prop and self references
|
|
1520
|
-
const preResolved = preResolveFromConfig(cleaned.varString || cleaned.variable, cleaned.variableType)
|
|
1521
|
-
if (preResolved) {
|
|
1522
|
-
cleaned.varResolved = preResolved.resolved
|
|
1523
|
-
cleaned.varResolvedPath = preResolved.path
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
// Also clean fallbackValues if present
|
|
1527
|
-
if (cleaned.fallbackValues && Array.isArray(cleaned.fallbackValues)) {
|
|
1528
|
-
cleaned.fallbackValues = cleaned.fallbackValues.map(fb => {
|
|
1529
|
-
const cleanedFb = { ...fb }
|
|
1530
|
-
if (cleanedFb.varMatch && filterMatch) {
|
|
1531
|
-
const match = cleanedFb.varMatch.match(filterMatch)
|
|
1532
|
-
if (match) {
|
|
1533
|
-
cleanedFb.varMatch = cleanedFb.varMatch.replace(filterMatch, '').trim()
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
if (cleanedFb.variable && filterMatch) {
|
|
1537
|
-
const match = cleanedFb.variable.match(filterMatch)
|
|
1538
|
-
if (match) {
|
|
1539
|
-
cleanedFb.variable = cleanedFb.variable.replace(filterMatch, '').trim()
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
if (cleanedFb.stringValue && filterMatch) {
|
|
1543
|
-
const match = cleanedFb.stringValue.match(filterMatch)
|
|
1544
|
-
if (match) {
|
|
1545
|
-
cleanedFb.stringValue = cleanedFb.stringValue.replace(filterMatch, '').trim()
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
// Pre-resolve fallback variable references
|
|
1550
|
-
if (cleanedFb.stringValue && cleanedFb.stringValue.match(/^\$\{[^}]+\}$/)) {
|
|
1551
|
-
const innerVar = cleanedFb.stringValue.slice(2, -1)
|
|
1552
|
-
const fbPreResolved = preResolveFromConfig(innerVar, 'dot.prop')
|
|
1553
|
-
if (fbPreResolved) {
|
|
1554
|
-
cleanedFb.varResolved = fbPreResolved.resolved
|
|
1555
|
-
cleanedFb.varResolvedPath = fbPreResolved.path
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
return cleanedFb
|
|
1560
|
-
})
|
|
1561
|
-
}
|
|
1562
|
-
return cleaned
|
|
1563
|
-
})
|
|
1564
|
-
|
|
1565
|
-
const varData = {
|
|
1566
|
-
filters: foundFilters.length > 0 ? foundFilters : undefined,
|
|
1567
|
-
path: configValuePath,
|
|
1568
|
-
key: itemKey,
|
|
1569
|
-
originalStringValue: rawValue,
|
|
1570
|
-
variable: keyWithoutFilters,
|
|
1571
|
-
variableWithFilters: originalSrc,
|
|
1572
|
-
isRequired: false,
|
|
1573
|
-
defaultValue: undefined,
|
|
1574
|
-
defaultValueIsVar: undefined,
|
|
1575
|
-
defaultValueSrc: undefined,
|
|
1576
|
-
hasFallback: false,
|
|
1577
|
-
matchIndex: matchCount++,
|
|
1578
|
-
resolveOrder: [],
|
|
1579
|
-
resolveDetails: cleanedResolveDetails,
|
|
1580
|
-
}
|
|
1581
|
-
let defaultValueIsVar = false
|
|
1582
|
-
|
|
1583
|
-
function calculateResolveOrder(item) {
|
|
1584
|
-
// Helper to strip filters from variable strings
|
|
1585
|
-
const stripFilters = (str) => {
|
|
1586
|
-
if (!str || !filterMatch) return str
|
|
1587
|
-
const match = str.match(filterMatch)
|
|
1588
|
-
if (match) {
|
|
1589
|
-
return str.replace(filterMatch, '').trim()
|
|
1590
|
-
}
|
|
1591
|
-
return str
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
if (item && item.fallbackValues) {
|
|
1595
|
-
let hasResolvedFallback
|
|
1596
|
-
let defaultValueSrc
|
|
1597
|
-
const isSingleFallback = item.fallbackValues.length === 1
|
|
1598
|
-
const order = ([stripFilters(item.valueBeforeFallback)]).concat(item.fallbackValues.map((f, i) => {
|
|
1599
|
-
if (f.fallbackValues) {
|
|
1600
|
-
const [nestedOrder, nestedResolvedFallback, nestedDefaultSrc] = calculateResolveOrder(f)
|
|
1601
|
-
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
1602
|
-
hasResolvedFallback = nestedResolvedFallback
|
|
1603
|
-
defaultValueSrc = nestedDefaultSrc
|
|
1604
|
-
}
|
|
1605
|
-
return nestedOrder
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
const valueStr = stripFilters(f.stringValue || f.variable)
|
|
1609
|
-
|
|
1610
|
-
// Only set default from first resolvable fallback
|
|
1611
|
-
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
1612
|
-
if (f.varResolved !== undefined) {
|
|
1613
|
-
hasResolvedFallback = f.varResolved
|
|
1614
|
-
defaultValueSrc = f.varResolvedPath
|
|
1615
|
-
} else if (!valueStr.match(/^\$\{[^}]+\}$/)) {
|
|
1616
|
-
// Literal value - use as default
|
|
1617
|
-
hasResolvedFallback = valueStr
|
|
1618
|
-
}
|
|
1619
|
-
// If variable can't resolve, don't set - let next fallback try
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
if (!hasResolvedFallback && f.isVariable) {
|
|
1623
|
-
defaultValueIsVar = f
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
if (f.isResolvedFallback) {
|
|
1627
|
-
if (isSingleFallback) {
|
|
1628
|
-
// Single fallback: show "value (default)"
|
|
1629
|
-
return `${valueStr} (default)`
|
|
1630
|
-
} else {
|
|
1631
|
-
// Multiple fallbacks: show resolved value if available
|
|
1632
|
-
if (f.varResolved !== undefined) {
|
|
1633
|
-
return `${valueStr} = ${f.varResolved}`
|
|
1634
|
-
}
|
|
1635
|
-
// If can't resolve, just show the value without annotation
|
|
1636
|
-
return valueStr
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
return valueStr
|
|
1640
|
-
})).flat()
|
|
1641
|
-
|
|
1642
|
-
return [order, hasResolvedFallback, defaultValueSrc]
|
|
1643
|
-
}
|
|
1644
|
-
return [[stripFilters(item.variable)], undefined, undefined]
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
const lastCleanedItem = cleanedResolveDetails[cleanedResolveDetails.length - 1]
|
|
1648
|
-
const [resolveOrder, hasResolvedFallback, defaultValueSrc] = calculateResolveOrder(lastCleanedItem)
|
|
1649
|
-
varData.resolveOrder = resolveOrder
|
|
1650
|
-
|
|
1651
|
-
if (defaultValueIsVar) {
|
|
1652
|
-
varData.defaultValueIsVar = defaultValueIsVar
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
if (typeof hasResolvedFallback !== 'undefined') {
|
|
1656
|
-
varData.defaultValue = hasResolvedFallback
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
if (defaultValueSrc) {
|
|
1660
|
-
varData.defaultValueSrc = defaultValueSrc
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
if (typeof varData.defaultValue === 'undefined') {
|
|
1664
|
-
varData.isRequired = true
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
if (varData.resolveOrder.length > 1) {
|
|
1668
|
-
varData.hasFallback = true
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
// Extract file references
|
|
1672
|
-
nested.forEach((detail) => {
|
|
1673
|
-
// console.log('detail', detail)
|
|
1674
|
-
if (detail.variableType && (detail.variableType === 'file' || detail.variableType === 'text')) {
|
|
1675
|
-
const extracted = extractFilePath(detail.variable)
|
|
1676
|
-
if (extracted) {
|
|
1677
|
-
const normalizedPath = normalizePath(extracted.filePath)
|
|
1678
|
-
if (!normalizedPath) return
|
|
1679
|
-
|
|
1680
|
-
// Handle variables in file paths - just record the pattern
|
|
1681
|
-
if (!fileRefs.includes(normalizedPath)) {
|
|
1682
|
-
fileRefs.push(normalizedPath)
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
// Check if path contains variables and create glob pattern
|
|
1686
|
-
const containsVariables = !!normalizedPath.match(variableSyntax)
|
|
1687
|
-
let globPattern
|
|
1688
|
-
if (containsVariables) {
|
|
1689
|
-
// Replace variable syntax ${...} with * for glob pattern
|
|
1690
|
-
globPattern = normalizedPath.replace(variableSyntax, '*')
|
|
1691
|
-
if (!fileGlobPatterns.includes(globPattern)) {
|
|
1692
|
-
fileGlobPatterns.push(globPattern)
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
// Try to pre-resolve inner variables from originalConfig
|
|
1697
|
-
let resolvedPath = normalizedPath
|
|
1698
|
-
let resolvedVarString = rawValue
|
|
1699
|
-
if (containsVariables) {
|
|
1700
|
-
const pathResult = resolveInnerVariables(normalizedPath, variableSyntax, originalConfig, dotProp.get)
|
|
1701
|
-
const varStringResult = resolveInnerVariables(rawValue, variableSyntax, originalConfig, dotProp.get)
|
|
1702
|
-
|
|
1703
|
-
if (pathResult.didResolve) {
|
|
1704
|
-
resolvedPath = normalizePath(pathResult.resolved) || pathResult.resolved
|
|
1705
|
-
resolvedVarString = varStringResult.resolved
|
|
1706
|
-
preResolvedPaths.add(resolvedPath)
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
// Build byConfigPath entry
|
|
1711
|
-
const absolutePath = configFilePath
|
|
1712
|
-
? path.resolve(path.dirname(configFilePath), resolvedPath)
|
|
1713
|
-
: resolvedPath
|
|
1714
|
-
const fileExists = configFilePath ? fs.existsSync(absolutePath) : false
|
|
1715
|
-
|
|
1716
|
-
const configPathEntry = {
|
|
1717
|
-
location: configValuePath,
|
|
1718
|
-
filePath: absolutePath,
|
|
1719
|
-
relativePath: resolvedPath,
|
|
1720
|
-
originalVariableString: rawValue,
|
|
1721
|
-
resolvedVariableString: resolvedVarString,
|
|
1722
|
-
containsVariables,
|
|
1723
|
-
exists: fileExists,
|
|
1724
|
-
}
|
|
1725
|
-
if (globPattern) {
|
|
1726
|
-
configPathEntry.pattern = globPattern
|
|
1727
|
-
}
|
|
1728
|
-
byConfigPath.push(configPathEntry)
|
|
1729
|
-
|
|
1730
|
-
// Build references entry (use resolvedPath as key when available)
|
|
1731
|
-
const refKey = resolvedPath
|
|
1732
|
-
if (!referencesMap.has(refKey)) {
|
|
1733
|
-
referencesMap.set(refKey, {
|
|
1734
|
-
resolvedPath: refKey,
|
|
1735
|
-
refs: [],
|
|
1736
|
-
})
|
|
1737
|
-
}
|
|
1738
|
-
const refEntry = referencesMap.get(refKey)
|
|
1739
|
-
refEntry.refs.push({
|
|
1740
|
-
location: configValuePath,
|
|
1741
|
-
value: normalizedPath,
|
|
1742
|
-
originalVariableString: rawValue,
|
|
1743
|
-
})
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
})
|
|
1747
|
-
|
|
1748
|
-
variableData[key] = (variableData[key] || []).concat(varData)
|
|
1749
|
-
foundVariables.push(rawValue)
|
|
1750
|
-
}
|
|
1751
|
-
})
|
|
1752
|
-
|
|
1753
|
-
// Make foundVariables array unique
|
|
1754
|
-
const finalFoundVariables = [...new Set(foundVariables)]
|
|
1755
|
-
const varKeys = Object.keys(variableData)
|
|
1756
|
-
|
|
1757
|
-
// Calculate summary using same logic as CLI display
|
|
1758
|
-
let requiredCount = 0
|
|
1759
|
-
let withDefaultsCount = 0
|
|
1760
|
-
varKeys.forEach((key) => {
|
|
1761
|
-
const instances = variableData[key]
|
|
1762
|
-
const firstInstance = instances[0]
|
|
1763
|
-
|
|
1764
|
-
// Extract variable name from key (e.g. "${self:service}" -> "self:service")
|
|
1765
|
-
const keyVarName = key.slice(2, -1).split(',')[0].trim()
|
|
1766
|
-
|
|
1767
|
-
// Find the resolveDetail that matches THIS variable (not any self-ref in the string)
|
|
1768
|
-
let matchingDetail = null
|
|
1769
|
-
for (const instance of instances) {
|
|
1770
|
-
if (instance.resolveDetails && instance.resolveDetails.length > 0) {
|
|
1771
|
-
const found = instance.resolveDetails.find((detail) => {
|
|
1772
|
-
const detailVar = detail.valueBeforeFallback || detail.variable
|
|
1773
|
-
return detailVar === keyVarName
|
|
1774
|
-
})
|
|
1775
|
-
if (found && (found.variableType === 'dot.prop' || found.variableType === 'self')) {
|
|
1776
|
-
matchingDetail = found
|
|
1777
|
-
break
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
// Also check defaultValueIsVar
|
|
1783
|
-
if (!matchingDetail && firstInstance.defaultValueIsVar && (
|
|
1784
|
-
firstInstance.defaultValueIsVar.variableType === 'self:' ||
|
|
1785
|
-
firstInstance.defaultValueIsVar.variableType === 'dot.prop'
|
|
1786
|
-
)) {
|
|
1787
|
-
matchingDetail = firstInstance.defaultValueIsVar
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
// Check if truly required
|
|
1791
|
-
let isTrulyRequired = false
|
|
1792
|
-
if (matchingDetail) {
|
|
1793
|
-
// Check if the self-reference resolves to a value
|
|
1794
|
-
// Use valueBeforeFallback if present (strips inline fallback like ", false")
|
|
1795
|
-
const varPath = matchingDetail.valueBeforeFallback || matchingDetail.variable
|
|
1796
|
-
const cleanPath = varPath.replace('self:', '')
|
|
1797
|
-
const dotPropValue = dotProp.get(this.originalConfig, cleanPath)
|
|
1798
|
-
if (typeof dotPropValue === 'undefined') {
|
|
1799
|
-
isTrulyRequired = true
|
|
1800
|
-
} else {
|
|
1801
|
-
// Enrich ALL instances with resolved self-reference value (overrides inline fallbacks)
|
|
1802
|
-
instances.forEach((instance) => {
|
|
1803
|
-
instance.defaultValueSrc = cleanPath
|
|
1804
|
-
instance.defaultValue = dotPropValue
|
|
1805
|
-
instance.isRequired = false
|
|
1806
|
-
})
|
|
1807
|
-
}
|
|
1808
|
-
} else if (typeof firstInstance.defaultValue === 'undefined') {
|
|
1809
|
-
isTrulyRequired = true
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
// Update isRequired based on computed isTrulyRequired
|
|
1813
|
-
instances.forEach((instance) => {
|
|
1814
|
-
instance.isRequired = isTrulyRequired
|
|
1815
|
-
})
|
|
1816
|
-
|
|
1817
|
-
if (isTrulyRequired) {
|
|
1818
|
-
requiredCount++
|
|
1819
|
-
} else {
|
|
1820
|
-
withDefaultsCount++
|
|
1821
|
-
}
|
|
1001
|
+
this._cachedMetadata = collectMetadata({
|
|
1002
|
+
variableSyntax: this.variableSyntax,
|
|
1003
|
+
variablesKnownTypes: this.variablesKnownTypes,
|
|
1004
|
+
variableTypes: this.variableTypes,
|
|
1005
|
+
filterMatch: this.filterMatch,
|
|
1006
|
+
configFilePath: this.configFilePath,
|
|
1007
|
+
// Use rawOriginalConfig for metadata display (truly original, no escaping)
|
|
1008
|
+
displayConfig: this.rawOriginalConfig || this.originalConfig,
|
|
1009
|
+
originalConfig: this.originalConfig,
|
|
1010
|
+
varSuffix: this.varSuffix,
|
|
1011
|
+
varSuffixWithSpacePattern: this.varSuffixWithSpacePattern,
|
|
1822
1012
|
})
|
|
1823
1013
|
|
|
1824
|
-
this._cachedMetadata = {
|
|
1825
|
-
variables: variableData,
|
|
1826
|
-
uniqueVariables: {},
|
|
1827
|
-
fileDependencies: {
|
|
1828
|
-
globPatterns: fileGlobPatterns,
|
|
1829
|
-
// all: fileRefs,
|
|
1830
|
-
dynamicPaths: fileRefs.filter(ref => ref.indexOf('*') !== -1 || ref.match(variableSyntax)),
|
|
1831
|
-
// Resolved paths: static paths + pre-resolved dynamic paths
|
|
1832
|
-
resolvedPaths: [
|
|
1833
|
-
...fileRefs.filter(ref => ref.indexOf('*') === -1 && !ref.match(variableSyntax)),
|
|
1834
|
-
...preResolvedPaths
|
|
1835
|
-
],
|
|
1836
|
-
byConfigPath,
|
|
1837
|
-
references: Array.from(referencesMap.values()),
|
|
1838
|
-
},
|
|
1839
|
-
summary: {
|
|
1840
|
-
totalVariables: varKeys.length,
|
|
1841
|
-
requiredVariables: requiredCount,
|
|
1842
|
-
variablesWithDefaults: withDefaultsCount
|
|
1843
|
-
},
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
1014
|
return this._cachedMetadata
|
|
1847
1015
|
}
|
|
1848
1016
|
/**
|
|
@@ -1917,7 +1085,7 @@ class Configorama {
|
|
|
1917
1085
|
if (!context) context = []
|
|
1918
1086
|
let results = _results
|
|
1919
1087
|
if (!results) results = []
|
|
1920
|
-
|
|
1088
|
+
|
|
1921
1089
|
const addContext = (value, key) => {
|
|
1922
1090
|
return this.getProperties(root, false, value, context.concat(key), results)
|
|
1923
1091
|
}
|
|
@@ -2381,7 +1549,7 @@ class Configorama {
|
|
|
2381
1549
|
}
|
|
2382
1550
|
}
|
|
2383
1551
|
|
|
2384
|
-
const originalSrc = valueObject.originalSource
|
|
1552
|
+
const originalSrc = (typeof valueObject.originalSource === 'string') ? valueObject.originalSource : ''
|
|
2385
1553
|
const hasFilters = originalSrc.match(this.filterMatch)
|
|
2386
1554
|
let foundFilters = []
|
|
2387
1555
|
if (hasFilters) {
|
|
@@ -2877,10 +2045,12 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2877
2045
|
// console.log('getValueFromSrc caller', caller)
|
|
2878
2046
|
const propertyString = valueObject.value
|
|
2879
2047
|
const pathValue = valueObject.path
|
|
2048
|
+
// Cache joined path to avoid repeated array.join('.') calls
|
|
2049
|
+
const pathJoined = pathValue && pathValue.length ? pathValue.join('.') : null
|
|
2880
2050
|
|
|
2881
2051
|
// Track every call to getValueFromSource for metadata
|
|
2882
|
-
if (
|
|
2883
|
-
const pathKey =
|
|
2052
|
+
if (this._trackCalls && pathJoined) {
|
|
2053
|
+
const pathKey = pathJoined
|
|
2884
2054
|
if (!this.resolutionTracking[pathKey]) {
|
|
2885
2055
|
this.resolutionTracking[pathKey] = {
|
|
2886
2056
|
path: pathKey,
|
|
@@ -2892,7 +2062,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2892
2062
|
|
|
2893
2063
|
// this.resolutionTracking[pathKey].resolutionHistory = this.resolutionTracking[pathKey].resolutionHistory || []
|
|
2894
2064
|
|
|
2895
|
-
// const isDuplicate = this.resolutionTracking[pathKey].resolutionHistory.some(entry =>
|
|
2065
|
+
// const isDuplicate = this.resolutionTracking[pathKey].resolutionHistory.some(entry =>
|
|
2896
2066
|
// entry.variableString === variableString
|
|
2897
2067
|
// )
|
|
2898
2068
|
|
|
@@ -2917,7 +2087,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2917
2087
|
// console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
|
|
2918
2088
|
|
|
2919
2089
|
// Cycle detection: track dependencies and check for cycles
|
|
2920
|
-
const fromPath =
|
|
2090
|
+
const fromPath = pathJoined
|
|
2921
2091
|
// Extract target path from variableString (e.g., 'self:b' → 'b', 'b.c' → 'b.c')
|
|
2922
2092
|
let toPath = variableString
|
|
2923
2093
|
if (variableString.startsWith('self:')) {
|
|
@@ -3049,8 +2219,8 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
3049
2219
|
valueObject,
|
|
3050
2220
|
).then((val) => {
|
|
3051
2221
|
// Update the last call with the resolved value
|
|
3052
|
-
if (
|
|
3053
|
-
const pathKey =
|
|
2222
|
+
if (this._trackCalls && pathJoined) {
|
|
2223
|
+
const pathKey = pathJoined
|
|
3054
2224
|
if (this.resolutionTracking[pathKey] && this.resolutionTracking[pathKey].calls.length) {
|
|
3055
2225
|
// Find the most recent call for this variableString
|
|
3056
2226
|
for (let i = this.resolutionTracking[pathKey].calls.length - 1; i >= 0; i--) {
|
|
@@ -3131,11 +2301,18 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
3131
2301
|
}
|
|
3132
2302
|
|
|
3133
2303
|
if (valueCount.length === 1 && noNestedVars) {
|
|
3134
|
-
|
|
2304
|
+
let lineInfo = ''
|
|
2305
|
+
if (this.originalString && this.configFilePath && valueObject.path) {
|
|
2306
|
+
const ext = path.extname(this.configFilePath)
|
|
2307
|
+
if (ext === '.yml' || ext === '.yaml' || ext === '.json') {
|
|
2308
|
+
const rawLines = this.originalString.split('\n')
|
|
2309
|
+
const lineNum = findLineByPath(arrayToJsonPath(valueObject.path), rawLines, ext)
|
|
2310
|
+
if (lineNum) lineInfo = ` at line ${lineNum},`
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
const configFilePathMsg = (this.configFilePath) ? `\nIn file ${this.configFilePath}${lineInfo} ` : ''
|
|
3135
2314
|
const fromLine = (propertyString !== valueObject.originalSource) ? `\n From "${valueObject.originalSource}"\n` : ''
|
|
3136
2315
|
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
2316
|
throw new Error(`Unable to resolve config variable "${propertyString}".\n${configFilePathMsg}at location ${valueObject.path ? `"${arrayToJsonPath(valueObject.path)}"` : 'n/a'}${fromLine}
|
|
3140
2317
|
\nFix this reference, your inputs and/or provide a valid fallback value.
|
|
3141
2318
|
\nExample of setting a fallback value: \${${variableString}, "fallbackValue"\}\n`)
|
|
@@ -3351,7 +2528,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
3351
2528
|
}
|
|
3352
2529
|
|
|
3353
2530
|
// Variable NOT FOUND. Warn user
|
|
3354
|
-
const key =
|
|
2531
|
+
const key = pathJoined || 'na'
|
|
3355
2532
|
const errorMessage = [
|
|
3356
2533
|
`Invalid variable reference syntax`,
|
|
3357
2534
|
`Key: "${key}"`,
|