configorama 0.6.8 → 0.6.10
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/cli.js +57 -28
- package/package.json +4 -2
- package/src/index.js +39 -2
- package/src/main.js +611 -264
- package/src/resolvers/valueFromCron.js +2 -0
- package/src/resolvers/valueFromEnv.js +2 -0
- package/src/resolvers/valueFromEnv.test.js +78 -0
- package/src/resolvers/valueFromEval.js +1 -0
- package/src/resolvers/valueFromGit.js +24 -9
- package/src/resolvers/valueFromNumber.js +1 -0
- package/src/resolvers/valueFromOptions.js +2 -0
- package/src/resolvers/valueFromString.js +1 -0
- package/src/sync.js +13 -4
- package/src/utils/cleanVariable.js +3 -3
- package/src/utils/configWizard.js +567 -0
- package/src/utils/encoders/index.js +15 -0
- package/src/utils/encoders/js-fixes.js +22 -0
- package/src/utils/{unknownValues.js → encoders/unknown-values.js} +10 -1
- package/src/utils/enrichMetadata.js +439 -82
- package/src/utils/find-nested-variables.js +41 -38
- package/src/utils/find-nested-variables.test.js +119 -35
- package/src/utils/getFullFilePath.js +38 -0
- package/src/utils/getVariableType.js +55 -0
- package/src/utils/logs.js +1 -1
- package/src/utils/parse.js +6 -4
- package/src/utils/resolveAlias.js +3 -2
- package/src/utils/splitByComma.js +2 -1
- package/src/utils/splitCsv.js +6 -6
- package/src/utils/resolveAliasOld.js +0 -65
- package/src/utils/x.js +0 -173
package/src/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const os = require('os')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const fs = require('fs')
|
|
4
|
+
const enrichMetadata = require('./utils/enrichMetadata')
|
|
4
5
|
/* // disable logs to find broken tests
|
|
5
6
|
console.log = () => {}
|
|
6
7
|
// process.exit(1)
|
|
@@ -14,6 +15,7 @@ const traverse = require('traverse')
|
|
|
14
15
|
const dotProp = require('dot-prop')
|
|
15
16
|
const chalk = require('./utils/chalk')
|
|
16
17
|
const { resolveAlias } = require('./utils/resolveAlias')
|
|
18
|
+
const { resolveFilePathFromMatch } = require('./utils/getFullFilePath')
|
|
17
19
|
|
|
18
20
|
/* Default Value resolvers */
|
|
19
21
|
const getValueFromString = require('./resolvers/valueFromString')
|
|
@@ -27,6 +29,7 @@ const createGitResolver = require('./resolvers/valueFromGit')
|
|
|
27
29
|
const YAML = require('./parsers/yaml')
|
|
28
30
|
const TOML = require('./parsers/toml')
|
|
29
31
|
const INI = require('./parsers/ini')
|
|
32
|
+
const JSON5 = require('./parsers/json5')
|
|
30
33
|
/* functions */
|
|
31
34
|
const md5Function = require('./functions/md5')
|
|
32
35
|
|
|
@@ -50,13 +53,16 @@ const { splitCsv } = require('./utils/splitCsv')
|
|
|
50
53
|
const { replaceAll } = require('./utils/replaceAll')
|
|
51
54
|
const { getTextAfterOccurrence, findNestedVariable } = require('./utils/textUtils')
|
|
52
55
|
const { getFallbackString, verifyVariable } = require('./utils/variableUtils')
|
|
53
|
-
const { encodeUnknown, decodeUnknown } = require('./utils/
|
|
56
|
+
const { encodeUnknown, decodeUnknown } = require('./utils/encoders/unknown-values')
|
|
57
|
+
const { decodeEncodedValue } = require('./utils/encoders')
|
|
58
|
+
const { encodeJsSyntax, decodeJsSyntax, hasParenthesesPlaceholder } = require('./utils/encoders/js-fixes')
|
|
54
59
|
const { mergeByKeys } = require('./utils/mergeByKeys')
|
|
55
60
|
const { arrayToJsonPath } = require('./utils/arrayToJsonPath')
|
|
56
61
|
const { findNestedVariables } = require('./utils/find-nested-variables')
|
|
57
62
|
const { makeBox, makeStackedBoxes } = require('@davidwells/box-logger')
|
|
58
63
|
const { logHeader } = require('./utils/logs')
|
|
59
64
|
const { createEditorLink } = require('./utils/createEditorLink')
|
|
65
|
+
const { runConfigWizard } = require('./utils/configWizard')
|
|
60
66
|
/**
|
|
61
67
|
* Maintainer's notes:
|
|
62
68
|
*
|
|
@@ -90,6 +96,7 @@ const logLines = '────────────────────
|
|
|
90
96
|
|
|
91
97
|
let DEBUG = process.argv.includes('--debug') ? true : false
|
|
92
98
|
let VERBOSE = process.argv.includes('--verbose') ? true : false
|
|
99
|
+
let SETUP_MODE = process.argv.includes('--setup') ? true : false
|
|
93
100
|
// DEBUG = true
|
|
94
101
|
let DEBUG_TYPE = false
|
|
95
102
|
const ENABLE_FUNCTIONS = true
|
|
@@ -123,6 +130,7 @@ function preProcess(configObject, variableSyntax) {
|
|
|
123
130
|
if (typeof str !== 'string') return str
|
|
124
131
|
|
|
125
132
|
let result = str
|
|
133
|
+
// result = result.replace(/\$\{self:/g, '${')
|
|
126
134
|
let changed = true
|
|
127
135
|
|
|
128
136
|
// Keep iterating until no more changes (to handle nested variables)
|
|
@@ -248,11 +256,18 @@ class Configorama {
|
|
|
248
256
|
allowUnknownVars: false,
|
|
249
257
|
// Allow undefined to be an end result.
|
|
250
258
|
allowUndefinedValues: false,
|
|
259
|
+
// Allow unknown file refs to pass through without throwing errors
|
|
260
|
+
allowUnknownFileRefs: false,
|
|
261
|
+
// Return metadata
|
|
262
|
+
returnMetadata: false,
|
|
263
|
+
// Return preResolvedVariableDetails
|
|
264
|
+
returnPreResolvedVariableDetails: false,
|
|
251
265
|
}, options)
|
|
252
266
|
|
|
253
267
|
this.filterCache = {}
|
|
254
268
|
|
|
255
269
|
this.foundVariables = []
|
|
270
|
+
this.fileRefsFound = []
|
|
256
271
|
|
|
257
272
|
// Track variable resolutions for metadata (keyed by path)
|
|
258
273
|
this.resolutionTracking = {}
|
|
@@ -343,6 +358,8 @@ class Configorama {
|
|
|
343
358
|
{
|
|
344
359
|
type: 'self',
|
|
345
360
|
prefix: 'self',
|
|
361
|
+
syntax: '${self:pathToKeyInConfig}',
|
|
362
|
+
description: `Resolves values from the current config object. Supports sub-properties via :key lookup.`,
|
|
346
363
|
match: selfRefSyntax,
|
|
347
364
|
resolver: (varString, o, x, pathValue) => {
|
|
348
365
|
return this.getValueFromSelf(varString, o, x, pathValue)
|
|
@@ -357,6 +374,8 @@ class Configorama {
|
|
|
357
374
|
{
|
|
358
375
|
type: 'file',
|
|
359
376
|
prefix: 'file',
|
|
377
|
+
syntax: '${file(pathToFile.json)}',
|
|
378
|
+
description: `Resolves values from files. Supports sub-properties via :key lookup.`,
|
|
360
379
|
match: fileRefSyntax,
|
|
361
380
|
resolver: (varString, o, x, pathValue) => {
|
|
362
381
|
return this.getValueFromFile(varString, { context: pathValue })
|
|
@@ -387,6 +406,7 @@ class Configorama {
|
|
|
387
406
|
/* Resolve deep references */
|
|
388
407
|
{
|
|
389
408
|
type: 'deep',
|
|
409
|
+
internal: true,
|
|
390
410
|
match: deepRefSyntax,
|
|
391
411
|
resolver: (varString, o, x, pathValue) => {
|
|
392
412
|
// console.log('>>>>>getValueFromDeep', varString)
|
|
@@ -399,23 +419,23 @@ class Configorama {
|
|
|
399
419
|
|
|
400
420
|
/* Nicer self: references. Match key in object */
|
|
401
421
|
const fallThroughSelfMatcher = {
|
|
402
|
-
type: '
|
|
422
|
+
type: 'dot.prop',
|
|
403
423
|
match: (varString, fullObject, valueObject) => {
|
|
404
424
|
/*
|
|
405
|
-
console.log('
|
|
406
|
-
console.log('
|
|
425
|
+
console.log('fallThroughSelfMatcher varString', varString)
|
|
426
|
+
console.log('fallThroughSelfMatcher valueObject', valueObject)
|
|
407
427
|
console.log('fullObject', fullObject)
|
|
408
428
|
/** */
|
|
409
429
|
/* its file ref so we need to shift lookup for self in nested files */
|
|
410
430
|
if (valueObject.isFileRef) {
|
|
411
431
|
const exists = dotProp.get(fullObject, varString)
|
|
412
|
-
// console.log('
|
|
432
|
+
// console.log('fallThroughSelfMatcher exists', exists)
|
|
413
433
|
if (!exists) {
|
|
414
434
|
// @ Todo make recursive
|
|
415
435
|
const deepProperties = [valueObject.path[0]].concat(varString)
|
|
416
436
|
const dotPropPath = deepProperties.join('.')
|
|
417
437
|
const deeperExists = dotProp.get(fullObject, dotPropPath)
|
|
418
|
-
// console.log('
|
|
438
|
+
// console.log('fallThroughSelfMatcher deeper', deeperExists)
|
|
419
439
|
return deeperExists
|
|
420
440
|
}
|
|
421
441
|
}
|
|
@@ -426,10 +446,10 @@ class Configorama {
|
|
|
426
446
|
},
|
|
427
447
|
resolver: (varString, options, config, pathValue) => {
|
|
428
448
|
/*
|
|
429
|
-
console.log('
|
|
430
|
-
console.log('
|
|
431
|
-
console.log('
|
|
432
|
-
console.log('
|
|
449
|
+
console.log('fallThroughSelfMatcher resolver', varString)
|
|
450
|
+
console.log('fallThroughSelfMatcher options', options)
|
|
451
|
+
console.log('fallThroughSelfMatcher config', config)
|
|
452
|
+
console.log('fallThroughSelfMatcher pathValue', pathValue)
|
|
433
453
|
/** */
|
|
434
454
|
return this.getValueFromSelf(varString, options, config, pathValue)
|
|
435
455
|
},
|
|
@@ -437,6 +457,19 @@ class Configorama {
|
|
|
437
457
|
|
|
438
458
|
/* Apply user defined variable sources */
|
|
439
459
|
if (options.variableSources) {
|
|
460
|
+
|
|
461
|
+
// ensure each variable source has a type
|
|
462
|
+
options.variableSources.forEach((v) => {
|
|
463
|
+
if (!v.type) {
|
|
464
|
+
console.log('Variable', v)
|
|
465
|
+
throw new Error('Variable source must have a type')
|
|
466
|
+
}
|
|
467
|
+
if (!v.match || !v.resolver) {
|
|
468
|
+
console.log('Variable', v)
|
|
469
|
+
throw new Error('Variable source must have a match and resolver functions')
|
|
470
|
+
}
|
|
471
|
+
})
|
|
472
|
+
|
|
440
473
|
this.variableTypes = this.variableTypes.concat(options.variableSources)
|
|
441
474
|
}
|
|
442
475
|
|
|
@@ -482,7 +515,7 @@ class Configorama {
|
|
|
482
515
|
toKebabCase: (val) => {
|
|
483
516
|
return kebabCase(val)
|
|
484
517
|
},
|
|
485
|
-
/* Type filters */
|
|
518
|
+
/* Type filters for coercion */
|
|
486
519
|
toNumber: (val, from) => {
|
|
487
520
|
const newVal = Number(val)
|
|
488
521
|
return newVal
|
|
@@ -497,7 +530,37 @@ class Configorama {
|
|
|
497
530
|
return JSON.stringify(val)
|
|
498
531
|
},
|
|
499
532
|
toObject: (val) => {
|
|
500
|
-
return
|
|
533
|
+
return JSON5.parse(val)
|
|
534
|
+
},
|
|
535
|
+
/* Type validation filters */
|
|
536
|
+
Number: (value) => {
|
|
537
|
+
const n = Number(value)
|
|
538
|
+
if (isNaN(n)) throw new Error(`Configorama Error: Expected Number, got "${value}"`)
|
|
539
|
+
return n
|
|
540
|
+
},
|
|
541
|
+
Boolean: (value) => {
|
|
542
|
+
if (typeof value === 'boolean') return value
|
|
543
|
+
const v = String(value).toLowerCase()
|
|
544
|
+
if (['true', '1', 'yes', 'on'].includes(v)) return true
|
|
545
|
+
if (['false', '0', 'no', 'off'].includes(v)) return false
|
|
546
|
+
throw new Error(`Configorama Error: Expected Boolean, got "${value}"`)
|
|
547
|
+
},
|
|
548
|
+
String: (value) => {
|
|
549
|
+
if (value === undefined || value === null || value === 'null') return ''
|
|
550
|
+
return String(value)
|
|
551
|
+
},
|
|
552
|
+
Json: (value) => {
|
|
553
|
+
try {
|
|
554
|
+
return typeof value === 'string' ? JSON.parse(value) : value
|
|
555
|
+
} catch (e) {
|
|
556
|
+
throw new Error(`Configorama Error: Invalid JSON in variable`)
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
/* Help filter - identity function that preserves value but provides metadata for wizard */
|
|
560
|
+
help: (value, helpText) => {
|
|
561
|
+
// Identity function - returns value unchanged
|
|
562
|
+
// The helpText argument is extracted during metadata collection for the wizard
|
|
563
|
+
return value
|
|
501
564
|
},
|
|
502
565
|
}
|
|
503
566
|
|
|
@@ -507,7 +570,9 @@ class Configorama {
|
|
|
507
570
|
}
|
|
508
571
|
|
|
509
572
|
// (\|\s*(toUpperCase|toLowerCase|toCamelCase|toKebabCase|capitalize)\s*)+$
|
|
510
|
-
|
|
573
|
+
// Updated to support function-style filters like help('text') with nested parens
|
|
574
|
+
// Use a more permissive pattern that matches anything between parens including nested parens
|
|
575
|
+
this.filterMatch = new RegExp(`(\\|\\s*(${Object.keys(this.filters).join('|')})(?:\\s*\\([^)]*(?:\\([^)]*\\))?[^)]*\\))?\\s*)+}?$`)
|
|
511
576
|
// console.log('this.filterMatch', this.filterMatch)
|
|
512
577
|
|
|
513
578
|
this.functions = {
|
|
@@ -603,7 +668,7 @@ class Configorama {
|
|
|
603
668
|
this.opts
|
|
604
669
|
)
|
|
605
670
|
this.configFileContents = ''
|
|
606
|
-
if (VERBOSE || showFoundVariables) {
|
|
671
|
+
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails) {
|
|
607
672
|
this.configFileContents = fs.readFileSync(this.configFilePath, 'utf8')
|
|
608
673
|
}
|
|
609
674
|
/*
|
|
@@ -630,13 +695,37 @@ class Configorama {
|
|
|
630
695
|
const variableSyntax = this.variableSyntax
|
|
631
696
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
632
697
|
|
|
633
|
-
if (VERBOSE || showFoundVariables) {
|
|
698
|
+
if (VERBOSE || showFoundVariables || this.opts.returnPreResolvedVariableDetails) {
|
|
634
699
|
// Use collectVariableMetadata to get variable info (DRY - don't duplicate logic)
|
|
635
700
|
const metadata = this.collectVariableMetadata()
|
|
636
|
-
|
|
637
|
-
|
|
701
|
+
|
|
702
|
+
const enrich = enrichMetadata(
|
|
703
|
+
metadata,
|
|
704
|
+
this.resolutionTracking,
|
|
705
|
+
this.variableSyntax,
|
|
706
|
+
this.fileRefsFound,
|
|
707
|
+
this.originalConfig,
|
|
708
|
+
this.configFilePath,
|
|
709
|
+
Object.keys(this.filters)
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
if (showFoundVariables) {
|
|
713
|
+
/*
|
|
714
|
+
deepLog('metadata', metadata)
|
|
715
|
+
fs.writeFileSync(`metadata-${path.basename(this.configFilePath)}.json`, JSON.stringify(metadata, null, 2))
|
|
716
|
+
deepLog('enrich', enrich)
|
|
717
|
+
// process.exit(1)
|
|
718
|
+
/** */
|
|
719
|
+
}
|
|
720
|
+
|
|
638
721
|
const variableData = metadata.variables
|
|
722
|
+
const uniqueVariables = metadata.uniqueVariables
|
|
639
723
|
const varKeys = Object.keys(variableData)
|
|
724
|
+
const uniqueVarKeys = Object.keys(uniqueVariables)
|
|
725
|
+
|
|
726
|
+
// if (this.opts.returnPreResolvedVariableDetails) {
|
|
727
|
+
// return metadata
|
|
728
|
+
// }
|
|
640
729
|
|
|
641
730
|
if (!varKeys.length) {
|
|
642
731
|
logHeader('No Variables Found in Config')
|
|
@@ -648,9 +737,9 @@ class Configorama {
|
|
|
648
737
|
|
|
649
738
|
const varTypes = Object.keys(this.variableTypes)
|
|
650
739
|
if (varTypes.length) {
|
|
651
|
-
const exclude = ['
|
|
740
|
+
const exclude = ['dot.prop', 'deep']
|
|
652
741
|
console.log('\nAllowed variable types:')
|
|
653
|
-
varTypes.filter((v) => v.type !== '
|
|
742
|
+
varTypes.filter((v) => v.type !== 'dot.prop').forEach((v) => {
|
|
654
743
|
const vData = this.variableTypes[v]
|
|
655
744
|
if (exclude.includes(vData.type)) {
|
|
656
745
|
return
|
|
@@ -673,164 +762,231 @@ class Configorama {
|
|
|
673
762
|
const longestKey = varKeys.reduce((acc, k) => {
|
|
674
763
|
return Math.max(acc, k.length)
|
|
675
764
|
}, 0)
|
|
676
|
-
// Count all references including nested ones within other variables
|
|
677
|
-
const countAllReferences = (targetVariable) => {
|
|
678
|
-
// Start with direct references
|
|
679
|
-
let count = variableData[targetVariable].length
|
|
680
|
-
|
|
681
|
-
// Check all other variables for nested references to this variable
|
|
682
|
-
varKeys.forEach((otherKey) => {
|
|
683
|
-
if (otherKey === targetVariable) return
|
|
684
|
-
|
|
685
|
-
variableData[otherKey].forEach((instance) => {
|
|
686
|
-
if (instance.resolveDetails) {
|
|
687
|
-
instance.resolveDetails.forEach((detail) => {
|
|
688
|
-
// Check if this resolveDetail references our target variable
|
|
689
|
-
if (detail.fullMatch === targetVariable) {
|
|
690
|
-
count++
|
|
691
|
-
}
|
|
692
|
-
})
|
|
693
|
-
}
|
|
694
|
-
})
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
return count
|
|
698
|
-
}
|
|
699
765
|
|
|
700
|
-
|
|
701
|
-
|
|
766
|
+
// Use uniqueVariables for simpler reference counting
|
|
767
|
+
const referenceData = varKeys.map((k) => {
|
|
768
|
+
// Map from varMatch (e.g., '${env:API_KEY}') to variable name (e.g., 'env:API_KEY')
|
|
769
|
+
// Extract the variable name from the key by removing ${ and }
|
|
770
|
+
const varName = k.replace(/^\$\{/, '').replace(/\}$/, '').split(',')[0].trim()
|
|
771
|
+
const uniqueVar = uniqueVariables[varName]
|
|
772
|
+
const refCount = uniqueVar ? uniqueVar.occurrences.length : variableData[k].length
|
|
702
773
|
const placesWord = refCount > 1 ? 'places' : 'place'
|
|
703
774
|
return `- ${k.padEnd(longestKey).padEnd(longestKey + 10)} referenced ${refCount} ${placesWord}`
|
|
704
|
-
}).join('\n')
|
|
705
|
-
|
|
775
|
+
}).join('\n')
|
|
776
|
+
|
|
777
|
+
console.log(`${referenceData}\n`)
|
|
706
778
|
}
|
|
707
779
|
|
|
708
780
|
logHeader('Variable Details')
|
|
709
781
|
|
|
710
|
-
const lines = this.configFileContents.split('\n')
|
|
711
|
-
|
|
712
|
-
|
|
782
|
+
const lines = this.configFileContents ? this.configFileContents.split('\n') : []
|
|
783
|
+
|
|
713
784
|
const indent = ''
|
|
714
785
|
const boxes = varKeys.map((key, i) => {
|
|
715
786
|
const variableInstances = variableData[key]
|
|
716
787
|
// console.log('variableInstances', variableInstances)
|
|
717
|
-
|
|
718
788
|
const firstInstance = variableInstances[0]
|
|
719
789
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
// console.log('no default value', firstInstance)
|
|
724
|
-
|
|
725
|
-
let dotPropArr = []
|
|
726
|
-
if (firstInstance.defaultValueIsVar && (
|
|
727
|
-
firstInstance.defaultValueIsVar.varType === 'self:' ||
|
|
728
|
-
firstInstance.defaultValueIsVar.varType === 'dot.prop'
|
|
729
|
-
)) {
|
|
730
|
-
dotPropArr = [firstInstance.defaultValueIsVar]
|
|
731
|
-
}
|
|
732
|
-
/* Check if the fallback variable is a self reference */
|
|
733
|
-
const hasDotPropOrSelf = variableInstances.reduce((acc, v) => {
|
|
734
|
-
const dotProp = v.resolveDetails.find((d) => {
|
|
735
|
-
// console.log('d', d)
|
|
736
|
-
return d.varType === 'dot.prop'
|
|
737
|
-
})
|
|
738
|
-
if (dotProp) {
|
|
739
|
-
acc.push(dotProp)
|
|
740
|
-
}
|
|
741
|
-
if (v.resolveDetails && v.resolveDetails.length === 1 && v.resolveDetails[0].varType === 'self:') {
|
|
742
|
-
// console.log('dot.prop', v.resolveDetails)
|
|
743
|
-
acc.push(v.resolveDetails[0])
|
|
744
|
-
}
|
|
745
|
-
return acc
|
|
746
|
-
}, dotPropArr)
|
|
747
|
-
// console.log('hasDotPropOrSelf', hasDotPropOrSelf)
|
|
748
|
-
|
|
749
|
-
if (!hasDotPropOrSelf.length) {
|
|
750
|
-
const debug = (false) ? JSON.stringify(firstInstance, null, 2) : ''
|
|
751
|
-
requiredText = `[Required Variable] ${debug}`
|
|
752
|
-
} else {
|
|
753
|
-
const fallBackValues = variableInstances.filter((v) => v.resolveDetails.find((d) => d.hasFallback)).map((v) => v.resolveDetails)
|
|
754
|
-
// console.log('fallBackValues', fallBackValues)
|
|
755
|
-
if (fallBackValues.length) {
|
|
756
|
-
// console.log('fallBackValues.resolveDetails', fallBackValues)
|
|
757
|
-
}
|
|
790
|
+
// Get uniqueVariable data for description and other metadata
|
|
791
|
+
const varName = key.replace(/^\$\{/, '').replace(/\}$/, '').split(',')[0].trim()
|
|
792
|
+
const uniqueVar = uniqueVariables[varName]
|
|
758
793
|
|
|
759
|
-
|
|
760
|
-
defaultValueSrc = cleanPath
|
|
761
|
-
// Find the dot prop value in the original config
|
|
762
|
-
const dotPropValue = dotProp.get(this.originalConfig, cleanPath)
|
|
763
|
-
// console.log('dotPropValue', dotPropValue)
|
|
764
|
-
if (typeof dotPropValue !== 'undefined') {
|
|
765
|
-
requiredText = ''
|
|
766
|
-
const niceString = typeof dotPropValue === 'object' ? JSON.stringify(dotPropValue) : dotPropValue
|
|
767
|
-
// truncate niceString to 100 characters
|
|
768
|
-
const truncatedString = niceString.length > 100 ? niceString.substring(0, 90) + '...' : niceString
|
|
769
|
-
firstInstance.defaultValue = truncatedString
|
|
770
|
-
} else {
|
|
771
|
-
deepLog('Missing default var', firstInstance)
|
|
772
|
-
throw new Error(`Variable misconfiguration at ${firstInstance.variable}\n\n"${hasDotPropOrSelf[0].variable}" resolves to undefined value.\n`)
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
//this.originalConfig[key] = undefined
|
|
776
|
-
}
|
|
794
|
+
// Build display message from enriched metadata
|
|
777
795
|
const spacing = ' '
|
|
778
796
|
const titleText = `Variable:${spacing}`
|
|
779
|
-
const
|
|
780
|
-
let varMsg = `${reqText}`
|
|
781
|
-
const VALUE_HEX = '#899499' // '#708090'
|
|
797
|
+
const VALUE_HEX = '#899499'
|
|
782
798
|
const keyChalk = chalk.whiteBright
|
|
783
799
|
const valueChalk = chalk.hex(VALUE_HEX)
|
|
784
800
|
|
|
801
|
+
let varMsg = ''
|
|
802
|
+
let requiredMessage = ''
|
|
803
|
+
|
|
804
|
+
// Show required status from metadata
|
|
805
|
+
if (firstInstance.isRequired) {
|
|
806
|
+
requiredMessage = `${chalk.red.bold('[Required]')}`
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Show type filter if present (Boolean, String, Number, etc.)
|
|
810
|
+
if (uniqueVar && uniqueVar.occurrences.length > 0) {
|
|
811
|
+
const typeFilters = ['Boolean', 'String', 'Number', 'Array', 'Object']
|
|
812
|
+
const foundTypes = new Set()
|
|
813
|
+
|
|
814
|
+
uniqueVar.occurrences.forEach(occ => {
|
|
815
|
+
if (occ.filters && Array.isArray(occ.filters)) {
|
|
816
|
+
occ.filters.forEach(filter => {
|
|
817
|
+
if (typeFilters.includes(filter)) {
|
|
818
|
+
foundTypes.add(filter)
|
|
819
|
+
}
|
|
820
|
+
})
|
|
821
|
+
}
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
if (foundTypes.size > 0) {
|
|
825
|
+
const typeText = `${indent}${keyChalk('Type:'.padEnd(titleText.length, ' '))}`
|
|
826
|
+
varMsg += `${typeText} ${valueChalk(Array.from(foundTypes).join(', '))}\n`
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Show description from uniqueVariables if available
|
|
831
|
+
if (uniqueVar && uniqueVar.occurrences.length > 0) {
|
|
832
|
+
// Collect unique descriptions from all occurrences
|
|
833
|
+
const descriptions = uniqueVar.occurrences
|
|
834
|
+
.map(occ => occ.description)
|
|
835
|
+
.filter((desc, index, self) => desc && self.indexOf(desc) === index)
|
|
836
|
+
|
|
837
|
+
if (descriptions.length > 0) {
|
|
838
|
+
const descText = `${indent}${keyChalk('Description:'.padEnd(titleText.length, ' '))}`
|
|
839
|
+
const combinedDesc = descriptions.join('. ')
|
|
840
|
+
varMsg += `${descText} ${valueChalk(combinedDesc)}\n`
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
// Show default value from metadata
|
|
785
847
|
if (typeof firstInstance.defaultValue !== 'undefined') {
|
|
786
|
-
// console.log('firstInstance.defaultValue', firstInstance.defaultValue)
|
|
787
848
|
const defaultValueRender = firstInstance.defaultValue === '' ? '""' : firstInstance.defaultValue
|
|
788
|
-
const defaultValueText = `${indent}${keyChalk(
|
|
789
|
-
|
|
790
|
-
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}`
|
|
849
|
+
const defaultValueText = `${indent}${keyChalk('Default value:'.padEnd(titleText.length, ' '))}`
|
|
850
|
+
varMsg += `${defaultValueText} ${valueChalk(defaultValueRender)}\n`
|
|
791
851
|
}
|
|
792
852
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
varMsg += `${
|
|
853
|
+
// Show default value source path from metadata
|
|
854
|
+
if (firstInstance.defaultValueSrc) {
|
|
855
|
+
varMsg += `${indent}${keyChalk('Default value path:'.padEnd(titleText.length, ' '))} `
|
|
856
|
+
varMsg += `${valueChalk(firstInstance.defaultValueSrc)}\n`
|
|
796
857
|
}
|
|
797
858
|
|
|
859
|
+
// Show resolve order from metadata
|
|
798
860
|
if (firstInstance.resolveOrder.length > 1) {
|
|
799
|
-
varMsg +=
|
|
861
|
+
varMsg += `${indent}${keyChalk('Resolve Order:'.padEnd(titleText.length, ' '))}`
|
|
800
862
|
const resolveOrder = firstInstance.resolveOrder.join(', ')
|
|
801
|
-
varMsg += ` ${valueChalk(resolveOrder)}`
|
|
863
|
+
varMsg += ` ${valueChalk(resolveOrder)}\n`
|
|
802
864
|
}
|
|
803
865
|
|
|
866
|
+
// Show path(s) from metadata
|
|
804
867
|
let locationRender = valueChalk(variableInstances[0].path)
|
|
805
|
-
|
|
806
|
-
let
|
|
868
|
+
let locationLabel = `${indent}${keyChalk('Config Path:'.padEnd(titleText.length, ' '))}`
|
|
869
|
+
let typeText = ''
|
|
807
870
|
if (variableInstances.length > 1) {
|
|
808
|
-
|
|
809
|
-
const
|
|
810
|
-
|
|
871
|
+
const pathIndent = ' '.repeat(titleText.length + 1)
|
|
872
|
+
const pathItems = variableInstances.map((v, idx) => {
|
|
873
|
+
// Show type filter per path if different
|
|
874
|
+
if (uniqueVar && uniqueVar.occurrences.length > 1) {
|
|
875
|
+
const occurrence = uniqueVar.occurrences.find(occ => occ.path === v.path)
|
|
876
|
+
const typeFilters = ['Boolean', 'String', 'Number', 'Array', 'Object']
|
|
877
|
+
const pathType = occurrence && occurrence.filters
|
|
878
|
+
? occurrence.filters.find(f => typeFilters.includes(f))
|
|
879
|
+
: null
|
|
880
|
+
|
|
881
|
+
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
882
|
+
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
883
|
+
return `${prefix}${valueChalk(`- ${v.path}`)}${typeText}`
|
|
884
|
+
}
|
|
885
|
+
const prefix = idx === 0 ? '' : `${indent}${pathIndent}`
|
|
886
|
+
return `${prefix}${valueChalk(`- ${v.path}`)}${typeText}`
|
|
887
|
+
})
|
|
888
|
+
locationRender = pathItems.join('\n')
|
|
889
|
+
locationLabel = `${indent}${keyChalk('Config Paths:'.padEnd(titleText.length, ' '))}`
|
|
890
|
+
} else {
|
|
891
|
+
// look for type filter in the first instance
|
|
892
|
+
const typeFilters = ['Boolean', 'String', 'Number', 'Array', 'Object']
|
|
893
|
+
const pathType = firstInstance.filters
|
|
894
|
+
? firstInstance.filters.find(f => typeFilters.includes(f))
|
|
895
|
+
: null
|
|
896
|
+
|
|
897
|
+
typeText = pathType ? ` ${chalk.dim(`Type: ${pathType}`)}` : ''
|
|
811
898
|
}
|
|
899
|
+
varMsg += `${locationLabel} ${locationRender}`
|
|
900
|
+
|
|
901
|
+
// Find line number in config file based on format (YAML, TOML, JSON, INI)
|
|
902
|
+
const configKey = firstInstance.key
|
|
903
|
+
const line = lines.findIndex((line) => {
|
|
904
|
+
const fileType = this.configFileType
|
|
905
|
+
const escapedKey = configKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
906
|
+
// YAML: key: or key :
|
|
907
|
+
if (fileType === '.yml' || fileType === '.yaml') {
|
|
908
|
+
return new RegExp(`^\\s*${escapedKey}\\s*:`).test(line)
|
|
909
|
+
}
|
|
910
|
+
// TOML: key = or key=
|
|
911
|
+
if (fileType === '.toml') {
|
|
912
|
+
return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
|
|
913
|
+
}
|
|
914
|
+
// JSON: "key": or "key" :
|
|
915
|
+
if (fileType === '.json' || fileType === '.json5') {
|
|
916
|
+
return new RegExp(`"${escapedKey}"\\s*:`).test(line)
|
|
917
|
+
}
|
|
918
|
+
// INI: key = or key=
|
|
919
|
+
if (fileType === '.ini') {
|
|
920
|
+
return new RegExp(`^\\s*${escapedKey}\\s*=`).test(line)
|
|
921
|
+
}
|
|
922
|
+
// JS/TS/ESM: key: or "key": or 'key': or `key`: or [`key`]:
|
|
923
|
+
if (['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(fileType)) {
|
|
924
|
+
return new RegExp(`(?:${escapedKey}|"${escapedKey}"|'${escapedKey}'|\`${escapedKey}\`|\\[\`${escapedKey}\`\\])\\s*:`).test(line)
|
|
925
|
+
}
|
|
926
|
+
// Default fallback: try YAML-style
|
|
927
|
+
return line.includes(`${configKey}:`)
|
|
928
|
+
})
|
|
929
|
+
const lineNumber = line !== -1 ? line + 1 : 0
|
|
812
930
|
|
|
813
|
-
varMsg += `\n${locationLabel} ${locationRender}`
|
|
814
931
|
|
|
815
|
-
// find the match in our lines
|
|
816
|
-
const line = lines.findIndex((line) => line.includes(key))
|
|
817
|
-
const lineNumber = line + 1
|
|
818
|
-
|
|
819
|
-
// console.log(` ${chalk.bold(key)}`)
|
|
820
932
|
return {
|
|
821
|
-
|
|
933
|
+
content: {
|
|
934
|
+
left: varMsg,
|
|
935
|
+
backgroundColor: 'red',
|
|
936
|
+
width: '100%',
|
|
937
|
+
},
|
|
822
938
|
title: {
|
|
823
|
-
left:
|
|
824
|
-
right: lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, `Line: ${lineNumber}`, 'gray') : '',
|
|
939
|
+
left: `▷ ${lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, key) : key}`,
|
|
940
|
+
right: lineNumber ? createEditorLink(this.configFilePath, lineNumber, 1, `${requiredMessage} ${lineNumber ? `Line: ${lineNumber.toString().padEnd(2, ' ')}` : ''}`, 'gray') : '',
|
|
941
|
+
center: typeText,
|
|
942
|
+
paddingBottom: 1,
|
|
943
|
+
paddingTop: (i === 0) ? 1 : 0,
|
|
944
|
+
truncate: true,
|
|
825
945
|
},
|
|
946
|
+
width: '100%',
|
|
826
947
|
}
|
|
827
948
|
})
|
|
828
949
|
|
|
829
950
|
console.log(makeStackedBoxes(boxes, {
|
|
951
|
+
borderText: 'Variable Details. Click on titles to open in editor.',
|
|
830
952
|
borderColor: 'gray',
|
|
831
|
-
minWidth:
|
|
953
|
+
minWidth: '96%',
|
|
832
954
|
borderStyle: 'bold',
|
|
955
|
+
disableTitleSeparator: true,
|
|
833
956
|
}))
|
|
957
|
+
// process.exit(1)
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
// WALK through CLI prompt if --setup flag is set
|
|
962
|
+
if (SETUP_MODE) {
|
|
963
|
+
logHeader('Setup Mode')
|
|
964
|
+
// deepLog('enrich', enrich)
|
|
965
|
+
const userInputs = await runConfigWizard(enrich, this.originalConfig, this.configFilePath)
|
|
966
|
+
|
|
967
|
+
console.log('\n')
|
|
968
|
+
logHeader('User Inputs Summary')
|
|
969
|
+
console.log(JSON.stringify(userInputs, null, 2))
|
|
970
|
+
|
|
971
|
+
// TODO set values
|
|
972
|
+
|
|
973
|
+
// Apply user inputs to options and environment
|
|
974
|
+
if (userInputs.options) {
|
|
975
|
+
Object.assign(this.opts, userInputs.options)
|
|
976
|
+
}
|
|
977
|
+
if (userInputs.env) {
|
|
978
|
+
Object.assign(process.env, userInputs.env)
|
|
979
|
+
}
|
|
980
|
+
// Note: self references are in the config, so no need to apply them
|
|
981
|
+
|
|
982
|
+
console.log()
|
|
983
|
+
logHeader('Resolving Configuration')
|
|
984
|
+
console.log()
|
|
985
|
+
|
|
986
|
+
// process.exit(1)
|
|
987
|
+
|
|
988
|
+
// Continue with normal resolution flow using the new values
|
|
989
|
+
// Don't exit - let it fall through to resolve the config
|
|
834
990
|
}
|
|
835
991
|
|
|
836
992
|
/* Exit early if list or info flag is set */
|
|
@@ -926,9 +1082,8 @@ class Configorama {
|
|
|
926
1082
|
}
|
|
927
1083
|
|
|
928
1084
|
/* fix for file(JS-ref.js, raw) to keep parens and inline code */
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
rawValue = rawValue.replace(OPEN_PAREN_PLACEHOLDER_PATTERN, '(')
|
|
1085
|
+
if (hasParenthesesPlaceholder(rawValue)) {
|
|
1086
|
+
rawValue = decodeJsSyntax(rawValue)
|
|
932
1087
|
this.update(rawValue)
|
|
933
1088
|
}
|
|
934
1089
|
|
|
@@ -972,6 +1127,8 @@ class Configorama {
|
|
|
972
1127
|
collectVariableMetadata() {
|
|
973
1128
|
const variableSyntax = this.variableSyntax
|
|
974
1129
|
const variablesKnownTypes = this.variablesKnownTypes
|
|
1130
|
+
const variableTypes = this.variableTypes
|
|
1131
|
+
const filterMatch = this.filterMatch
|
|
975
1132
|
const foundVariables = []
|
|
976
1133
|
const variableData = {}
|
|
977
1134
|
const fileRefs = []
|
|
@@ -986,29 +1143,117 @@ class Configorama {
|
|
|
986
1143
|
return
|
|
987
1144
|
}
|
|
988
1145
|
|
|
989
|
-
const nested = findNestedVariables(
|
|
1146
|
+
const nested = findNestedVariables(
|
|
1147
|
+
rawValue,
|
|
1148
|
+
variableSyntax,
|
|
1149
|
+
variablesKnownTypes,
|
|
1150
|
+
configValuePath,
|
|
1151
|
+
variableTypes
|
|
1152
|
+
)
|
|
990
1153
|
|
|
991
1154
|
const lastItem = nested[nested.length - 1]
|
|
992
1155
|
const lastKeyPath = this.path[this.path.length - 1]
|
|
993
1156
|
const itemKey = (lastKeyPath.match(/[\d+]$/)) ? `${this.path[this.path.length - 2]}[${lastKeyPath}]` : lastKeyPath
|
|
994
|
-
|
|
1157
|
+
|
|
1158
|
+
// Extract filters from varMatch
|
|
1159
|
+
const originalSrc = lastItem.varMatch || ''
|
|
1160
|
+
const hasFilters = filterMatch && originalSrc.match(filterMatch)
|
|
1161
|
+
let foundFilters = []
|
|
1162
|
+
let keyWithoutFilters = originalSrc
|
|
1163
|
+
|
|
1164
|
+
if (hasFilters) {
|
|
1165
|
+
// Extract filter names from the match (e.g., "| String}" -> ["String"])
|
|
1166
|
+
const filterPart = hasFilters[0].replace(/}?$/, '') // Remove trailing }
|
|
1167
|
+
foundFilters = filterPart
|
|
1168
|
+
.split('|')
|
|
1169
|
+
.map((filter) => filter.trim())
|
|
1170
|
+
.filter(Boolean)
|
|
1171
|
+
|
|
1172
|
+
// Remove filters from the key (replace "| String}" with "}")
|
|
1173
|
+
// Also clean up any trailing whitespace before the closing brace
|
|
1174
|
+
keyWithoutFilters = originalSrc.replace(filterMatch, '}').replace(/\s+}$/, '}')
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const key = keyWithoutFilters
|
|
1178
|
+
|
|
1179
|
+
// Strip filters from resolveDetails
|
|
1180
|
+
const cleanedResolveDetails = nested.map(detail => {
|
|
1181
|
+
const cleaned = { ...detail }
|
|
1182
|
+
if (cleaned.varMatch && filterMatch) {
|
|
1183
|
+
const match = cleaned.varMatch.match(filterMatch)
|
|
1184
|
+
if (match) {
|
|
1185
|
+
cleaned.varMatch = cleaned.varMatch.replace(filterMatch, '').replace(/\s+$/, '') + '}'
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (cleaned.variable && filterMatch) {
|
|
1189
|
+
const match = cleaned.variable.match(filterMatch)
|
|
1190
|
+
if (match) {
|
|
1191
|
+
cleaned.variable = cleaned.variable.replace(filterMatch, '').replace(/\s+$/, '')
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
if (cleaned.varString && filterMatch) {
|
|
1195
|
+
const match = cleaned.varString.match(filterMatch)
|
|
1196
|
+
if (match) {
|
|
1197
|
+
cleaned.varString = cleaned.varString.replace(filterMatch, '').trim()
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// Also clean fallbackValues if present
|
|
1201
|
+
if (cleaned.fallbackValues && Array.isArray(cleaned.fallbackValues)) {
|
|
1202
|
+
cleaned.fallbackValues = cleaned.fallbackValues.map(fb => {
|
|
1203
|
+
const cleanedFb = { ...fb }
|
|
1204
|
+
if (cleanedFb.varMatch && filterMatch) {
|
|
1205
|
+
const match = cleanedFb.varMatch.match(filterMatch)
|
|
1206
|
+
if (match) {
|
|
1207
|
+
cleanedFb.varMatch = cleanedFb.varMatch.replace(filterMatch, '').trim()
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (cleanedFb.variable && filterMatch) {
|
|
1211
|
+
const match = cleanedFb.variable.match(filterMatch)
|
|
1212
|
+
if (match) {
|
|
1213
|
+
cleanedFb.variable = cleanedFb.variable.replace(filterMatch, '').trim()
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (cleanedFb.stringValue && filterMatch) {
|
|
1217
|
+
const match = cleanedFb.stringValue.match(filterMatch)
|
|
1218
|
+
if (match) {
|
|
1219
|
+
cleanedFb.stringValue = cleanedFb.stringValue.replace(filterMatch, '').trim()
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return cleanedFb
|
|
1223
|
+
})
|
|
1224
|
+
}
|
|
1225
|
+
return cleaned
|
|
1226
|
+
})
|
|
1227
|
+
|
|
995
1228
|
const varData = {
|
|
996
1229
|
path: configValuePath,
|
|
997
1230
|
key: itemKey,
|
|
998
|
-
|
|
999
|
-
variable:
|
|
1231
|
+
originalStringValue: rawValue,
|
|
1232
|
+
variable: keyWithoutFilters,
|
|
1233
|
+
variableWithFilters: originalSrc,
|
|
1000
1234
|
isRequired: false,
|
|
1001
1235
|
defaultValue: undefined,
|
|
1002
1236
|
matchIndex: matchCount++,
|
|
1003
1237
|
resolveOrder: [],
|
|
1004
|
-
resolveDetails:
|
|
1238
|
+
resolveDetails: cleanedResolveDetails,
|
|
1239
|
+
...(foundFilters.length > 0 && { filters: foundFilters }),
|
|
1005
1240
|
}
|
|
1006
1241
|
let defaultValueIsVar = false
|
|
1007
1242
|
|
|
1008
1243
|
function calculateResolveOrder(item) {
|
|
1244
|
+
// Helper to strip filters from variable strings
|
|
1245
|
+
const stripFilters = (str) => {
|
|
1246
|
+
if (!str || !filterMatch) return str
|
|
1247
|
+
const match = str.match(filterMatch)
|
|
1248
|
+
if (match) {
|
|
1249
|
+
return str.replace(filterMatch, '').trim()
|
|
1250
|
+
}
|
|
1251
|
+
return str
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1009
1254
|
if (item && item.fallbackValues) {
|
|
1010
1255
|
let hasResolvedFallback
|
|
1011
|
-
const order = ([item.valueBeforeFallback]).concat(item.fallbackValues.map((f, i) => {
|
|
1256
|
+
const order = ([stripFilters(item.valueBeforeFallback)]).concat(item.fallbackValues.map((f, i) => {
|
|
1012
1257
|
if (f.fallbackValues) {
|
|
1013
1258
|
const [nestedOrder, nestedResolvedFallback] = calculateResolveOrder(f)
|
|
1014
1259
|
if (!hasResolvedFallback && nestedResolvedFallback) {
|
|
@@ -1018,21 +1263,22 @@ class Configorama {
|
|
|
1018
1263
|
}
|
|
1019
1264
|
|
|
1020
1265
|
if (!hasResolvedFallback && f.isResolvedFallback) {
|
|
1021
|
-
hasResolvedFallback = f.stringValue
|
|
1266
|
+
hasResolvedFallback = stripFilters(f.stringValue)
|
|
1022
1267
|
}
|
|
1023
1268
|
if (f.isResolvedFallback) {
|
|
1024
|
-
hasResolvedFallback = f.stringValue
|
|
1269
|
+
hasResolvedFallback = stripFilters(f.stringValue)
|
|
1025
1270
|
}
|
|
1026
1271
|
|
|
1027
1272
|
if (!hasResolvedFallback && f.isVariable) {
|
|
1028
1273
|
defaultValueIsVar = f
|
|
1029
1274
|
}
|
|
1030
|
-
|
|
1275
|
+
const valueStr = stripFilters(f.stringValue || f.variable)
|
|
1276
|
+
return `${valueStr}${f.isResolvedFallback ? ' (default)' : ''}`
|
|
1031
1277
|
})).flat()
|
|
1032
1278
|
|
|
1033
1279
|
return [order, hasResolvedFallback]
|
|
1034
1280
|
}
|
|
1035
|
-
return [[item.variable], undefined]
|
|
1281
|
+
return [[stripFilters(item.variable)], undefined]
|
|
1036
1282
|
}
|
|
1037
1283
|
|
|
1038
1284
|
const [resolveOrder, hasResolvedFallback] = calculateResolveOrder(lastItem)
|
|
@@ -1056,10 +1302,9 @@ class Configorama {
|
|
|
1056
1302
|
|
|
1057
1303
|
// Extract file references
|
|
1058
1304
|
nested.forEach((detail) => {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
const fileMatch = detail.varType.match(/^(?:file|text)\((.*?)\)/)
|
|
1305
|
+
// console.log('detail', detail)
|
|
1306
|
+
if (detail.variableType && (detail.variableType === 'file' || detail.variableType === 'text')) {
|
|
1307
|
+
const fileMatch = detail.variable.match(/^(?:file|text)\((.*?)\)/)
|
|
1063
1308
|
if (fileMatch && fileMatch[1]) {
|
|
1064
1309
|
let fileContent = fileMatch[1].trim()
|
|
1065
1310
|
|
|
@@ -1125,19 +1370,19 @@ class Configorama {
|
|
|
1125
1370
|
// Check for self-references that resolve to config values
|
|
1126
1371
|
let dotPropArr = []
|
|
1127
1372
|
if (firstInstance.defaultValueIsVar && (
|
|
1128
|
-
firstInstance.defaultValueIsVar.
|
|
1129
|
-
firstInstance.defaultValueIsVar.
|
|
1373
|
+
firstInstance.defaultValueIsVar.variableType === 'self:' ||
|
|
1374
|
+
firstInstance.defaultValueIsVar.variableType === 'dot.prop'
|
|
1130
1375
|
)) {
|
|
1131
1376
|
dotPropArr = [firstInstance.defaultValueIsVar]
|
|
1132
1377
|
}
|
|
1133
1378
|
|
|
1134
1379
|
const hasDotPropOrSelf = instances.reduce((acc, v) => {
|
|
1135
|
-
|
|
1136
|
-
if (
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1380
|
+
// Only check the outermost variable (last in resolveDetails)
|
|
1381
|
+
if (v.resolveDetails && v.resolveDetails.length > 0) {
|
|
1382
|
+
const outermostDetail = v.resolveDetails[v.resolveDetails.length - 1]
|
|
1383
|
+
if (outermostDetail.variableType === 'dot.prop' || outermostDetail.variableType === 'self') {
|
|
1384
|
+
acc.push(outermostDetail)
|
|
1385
|
+
}
|
|
1141
1386
|
}
|
|
1142
1387
|
return acc
|
|
1143
1388
|
}, dotPropArr)
|
|
@@ -1150,10 +1395,20 @@ class Configorama {
|
|
|
1150
1395
|
const dotPropValue = dotProp.get(this.originalConfig, cleanPath)
|
|
1151
1396
|
if (typeof dotPropValue === 'undefined') {
|
|
1152
1397
|
isTrulyRequired = true
|
|
1398
|
+
} else {
|
|
1399
|
+
// Enrich with default value from self-reference
|
|
1400
|
+
firstInstance.defaultValueSrc = cleanPath
|
|
1401
|
+
const niceString = typeof dotPropValue === 'object' ? JSON.stringify(dotPropValue) : dotPropValue
|
|
1402
|
+
const truncatedString = niceString.length > 100 ? niceString.substring(0, 90) + '...' : niceString
|
|
1403
|
+
firstInstance.defaultValue = truncatedString
|
|
1404
|
+
firstInstance.isRequired = false
|
|
1153
1405
|
}
|
|
1154
1406
|
}
|
|
1155
1407
|
}
|
|
1156
1408
|
|
|
1409
|
+
// Update isRequired based on computed isTrulyRequired
|
|
1410
|
+
firstInstance.isRequired = isTrulyRequired
|
|
1411
|
+
|
|
1157
1412
|
if (isTrulyRequired) {
|
|
1158
1413
|
requiredCount++
|
|
1159
1414
|
} else {
|
|
@@ -1163,13 +1418,23 @@ class Configorama {
|
|
|
1163
1418
|
|
|
1164
1419
|
return {
|
|
1165
1420
|
variables: variableData,
|
|
1421
|
+
uniqueVariables: {},
|
|
1422
|
+
fileDependencies: {
|
|
1423
|
+
globPatterns: fileGlobPatterns,
|
|
1424
|
+
// all: fileRefs,
|
|
1425
|
+
dynamicPaths: fileRefs.filter(ref => ref.indexOf('*') !== -1 || ref.match(variableSyntax)),
|
|
1426
|
+
// resolve files are those that are paths with no * and no inner variables
|
|
1427
|
+
resolvedPaths: fileRefs.filter(ref => ref.indexOf('*') === -1 && !ref.match(variableSyntax)),
|
|
1428
|
+
// Set in enrichMetadata
|
|
1429
|
+
byConfigPath: undefined,
|
|
1430
|
+
// Set in enrichMetadata
|
|
1431
|
+
references: undefined,
|
|
1432
|
+
},
|
|
1166
1433
|
summary: {
|
|
1167
1434
|
totalVariables: varKeys.length,
|
|
1168
1435
|
requiredVariables: requiredCount,
|
|
1169
1436
|
variablesWithDefaults: withDefaultsCount
|
|
1170
1437
|
},
|
|
1171
|
-
fileRefs: fileRefs,
|
|
1172
|
-
fileGlobPatterns: fileGlobPatterns,
|
|
1173
1438
|
}
|
|
1174
1439
|
}
|
|
1175
1440
|
runFunction(variableString) {
|
|
@@ -1193,6 +1458,7 @@ class Configorama {
|
|
|
1193
1458
|
let argsToPass
|
|
1194
1459
|
if (rawArgs && rawArgs.match(/^{([^}]+)}$/)) {
|
|
1195
1460
|
// console.log('OBJECT', hasFunc[2])
|
|
1461
|
+
// TODO use JSON5
|
|
1196
1462
|
argsToPass = [JSON.parse(rawArgs)]
|
|
1197
1463
|
} else {
|
|
1198
1464
|
// TODO fix how commas + spaces are ned
|
|
@@ -1459,48 +1725,79 @@ class Configorama {
|
|
|
1459
1725
|
if (matches.length === 1) {
|
|
1460
1726
|
valueObject.currentVarDetails = matches[0]
|
|
1461
1727
|
valueObject.currentVarDetails.result = results[0]
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// Initialize resolution history if needed
|
|
1731
|
+
if (!valueObject.resolutionHistory) {
|
|
1732
|
+
valueObject.resolutionHistory = []
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
let result = valueObject.value
|
|
1736
|
+
for (let i = 0; i < matches.length; i += 1) {
|
|
1737
|
+
this.warnIfNotFound(matches[i].variable, results[i])
|
|
1462
1738
|
|
|
1463
1739
|
// Extract metadata from result if present
|
|
1464
|
-
let actualResult = results[
|
|
1740
|
+
let actualResult = results[i]
|
|
1465
1741
|
let resolverType = undefined
|
|
1466
|
-
if (results[
|
|
1467
|
-
if (results[
|
|
1468
|
-
actualResult = results[
|
|
1469
|
-
resolverType = results[
|
|
1470
|
-
} else if (results[
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
resolverType = results[0].__resolverType
|
|
1742
|
+
if (results[i] && typeof results[i] === 'object') {
|
|
1743
|
+
if (results[i].__internal_metadata) {
|
|
1744
|
+
actualResult = results[i].value
|
|
1745
|
+
resolverType = results[i].__resolverType
|
|
1746
|
+
} else if (results[i].__internal_only_flag) {
|
|
1747
|
+
actualResult = results[i]
|
|
1748
|
+
resolverType = results[i].__resolverType
|
|
1474
1749
|
}
|
|
1475
1750
|
}
|
|
1476
|
-
// valueObject.currentVarDetails.varType = results[0].__resolverType
|
|
1477
1751
|
|
|
1478
|
-
// Track resolution history
|
|
1479
|
-
if (!valueObject.resolutionHistory) {
|
|
1480
|
-
valueObject.resolutionHistory = []
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
1752
|
// Extract clean result to avoid circular references
|
|
1484
|
-
// For __internal_only_flag objects (like deep resolver results), extract the value
|
|
1485
|
-
// For real data objects (like file contents), keep them as-is
|
|
1486
1753
|
let cleanResult = actualResult
|
|
1487
1754
|
if (actualResult && typeof actualResult === 'object' && actualResult.__internal_only_flag) {
|
|
1488
1755
|
cleanResult = actualResult.value
|
|
1489
1756
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1757
|
+
|
|
1758
|
+
let valueBeforeResolution = result
|
|
1759
|
+
|
|
1760
|
+
if (typeof valueBeforeResolution === 'object' && valueBeforeResolution.__internal_only_flag) {
|
|
1761
|
+
valueBeforeResolution = valueBeforeResolution.value
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
const finalResult = decodeEncodedValue(cleanResult)
|
|
1765
|
+
|
|
1766
|
+
// Track this resolution step in history
|
|
1767
|
+
const historyEntry = {}
|
|
1768
|
+
|
|
1769
|
+
historyEntry.match = matches[i].match
|
|
1770
|
+
historyEntry.variable = matches[i].variable
|
|
1771
|
+
if (historyEntry.resultType === 'string' && historyEntry.result.match(/^>passthrough\[/)) {
|
|
1772
|
+
historyEntry.variableType = 'encodedUnknown'
|
|
1497
1773
|
}
|
|
1498
1774
|
if (resolverType) {
|
|
1499
|
-
historyEntry.
|
|
1775
|
+
historyEntry.variableType = resolverType
|
|
1776
|
+
}
|
|
1777
|
+
historyEntry.result = finalResult
|
|
1778
|
+
|
|
1779
|
+
const isDeepResult = typeof finalResult === 'string' && finalResult.match(/^\$\{deep:\d+\}$/)
|
|
1780
|
+
|
|
1781
|
+
if (isDeepResult) {
|
|
1782
|
+
historyEntry.resultAfterDeep = 'TBD'
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
historyEntry.resultType = typeof finalResult
|
|
1786
|
+
historyEntry.valueBeforeResolution = valueBeforeResolution
|
|
1787
|
+
historyEntry.from = 'renderMatches'
|
|
1788
|
+
if (isDeepResult) {
|
|
1789
|
+
historyEntry.resultIsDeep = true
|
|
1500
1790
|
}
|
|
1501
1791
|
|
|
1792
|
+
if (finalResult !== cleanResult) {
|
|
1793
|
+
historyEntry.resultEncoded = cleanResult
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
|
|
1798
|
+
|
|
1502
1799
|
// Check if variable has fallback values (comma-separated)
|
|
1503
|
-
const variableParts = splitByComma(matches[
|
|
1800
|
+
const variableParts = splitByComma(matches[i].variable)
|
|
1504
1801
|
if (variableParts.length > 1) {
|
|
1505
1802
|
historyEntry.hasFallback = true
|
|
1506
1803
|
historyEntry.valueBeforeFallback = variableParts[0]
|
|
@@ -1529,10 +1826,10 @@ class Configorama {
|
|
|
1529
1826
|
fallbackData.isResolvedFallback = true
|
|
1530
1827
|
}
|
|
1531
1828
|
} else {
|
|
1532
|
-
// Extract
|
|
1829
|
+
// Extract variableType from variable references
|
|
1533
1830
|
const varTypeMatch = trimmedFallback.match(this.variablesKnownTypes)
|
|
1534
1831
|
if (varTypeMatch && varTypeMatch[1]) {
|
|
1535
|
-
fallbackData.
|
|
1832
|
+
fallbackData.variableType = varTypeMatch[1]
|
|
1536
1833
|
}
|
|
1537
1834
|
}
|
|
1538
1835
|
|
|
@@ -1541,33 +1838,16 @@ class Configorama {
|
|
|
1541
1838
|
}
|
|
1542
1839
|
|
|
1543
1840
|
// Only add to history if not a duplicate (same match + variable)
|
|
1544
|
-
const isDuplicate = valueObject.resolutionHistory.some(entry =>
|
|
1545
|
-
entry.match === historyEntry.match &&
|
|
1841
|
+
const isDuplicate = valueObject.resolutionHistory.some(entry =>
|
|
1842
|
+
entry.match === historyEntry.match &&
|
|
1546
1843
|
entry.variable === historyEntry.variable
|
|
1547
1844
|
)
|
|
1548
|
-
|
|
1845
|
+
|
|
1549
1846
|
if (!isDuplicate) {
|
|
1550
1847
|
valueObject.resolutionHistory.push(historyEntry)
|
|
1551
1848
|
}
|
|
1552
1849
|
|
|
1553
|
-
//
|
|
1554
|
-
if (valueObject.path && valueObject.path.length) {
|
|
1555
|
-
const pathKey = valueObject.path.join('.')
|
|
1556
|
-
if (!this.resolutionTracking[pathKey]) {
|
|
1557
|
-
this.resolutionTracking[pathKey] = {
|
|
1558
|
-
path: pathKey,
|
|
1559
|
-
originalPropertyString: valueObject.originalSource,
|
|
1560
|
-
calls: []
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
this.resolutionTracking[pathKey].resolutionHistory = valueObject.resolutionHistory
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
let result = valueObject.value
|
|
1568
|
-
for (let i = 0; i < matches.length; i += 1) {
|
|
1569
|
-
this.warnIfNotFound(matches[i].variable, results[i])
|
|
1570
|
-
// console.log('Render MATCHES', results[i])
|
|
1850
|
+
// Process the match
|
|
1571
1851
|
let valueToPop = results[i]
|
|
1572
1852
|
// TODO refactor this. __internal_only_flag needed to stop clash with sync/async file resolution
|
|
1573
1853
|
if (results[i] && typeof results[i] === 'object' && (results[i].__internal_only_flag || results[i].__internal_metadata)) {
|
|
@@ -1581,6 +1861,21 @@ class Configorama {
|
|
|
1581
1861
|
console.log(this.deep)
|
|
1582
1862
|
/** */
|
|
1583
1863
|
}
|
|
1864
|
+
|
|
1865
|
+
// Save resolution history to tracking map for persistence across iterations
|
|
1866
|
+
if (valueObject.path && valueObject.path.length) {
|
|
1867
|
+
const pathKey = valueObject.path.join('.')
|
|
1868
|
+
if (!this.resolutionTracking[pathKey]) {
|
|
1869
|
+
this.resolutionTracking[pathKey] = {
|
|
1870
|
+
path: pathKey,
|
|
1871
|
+
originalPropertyString: valueObject.originalSource,
|
|
1872
|
+
resolvedPropertyValue: undefined,
|
|
1873
|
+
calls: []
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
this.resolutionTracking[pathKey].resolutionHistory = valueObject.resolutionHistory
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1584
1879
|
return result
|
|
1585
1880
|
}
|
|
1586
1881
|
/**
|
|
@@ -1729,7 +2024,7 @@ class Configorama {
|
|
|
1729
2024
|
if (currentDetails &&
|
|
1730
2025
|
currentDetails.resultType === 'number' &&
|
|
1731
2026
|
parentDetails && parentDetails.resultType === 'string' &&
|
|
1732
|
-
parentDetails.result.match(/^\d+$/) && parentDetails.
|
|
2027
|
+
parentDetails.result.match(/^\d+$/) && parentDetails.variableType === 'env'
|
|
1733
2028
|
) {
|
|
1734
2029
|
if (Number(parentDetails.result) === currentDetails.result) {
|
|
1735
2030
|
property = String(valueToPopulate)
|
|
@@ -1985,7 +2280,23 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
1985
2280
|
})
|
|
1986
2281
|
}
|
|
1987
2282
|
property = foundFilters.reduce((acc, filter) => {
|
|
1988
|
-
|
|
2283
|
+
// Check if filter has function-style arguments
|
|
2284
|
+
const funcMatch = filter.match(/^(\w+)\((.*)\)$/)
|
|
2285
|
+
let filterName = filter
|
|
2286
|
+
let filterArgs = []
|
|
2287
|
+
|
|
2288
|
+
if (funcMatch) {
|
|
2289
|
+
filterName = funcMatch[1]
|
|
2290
|
+
const rawArgs = funcMatch[2]
|
|
2291
|
+
if (rawArgs) {
|
|
2292
|
+
const splitter = splitCsv(rawArgs, ', ')
|
|
2293
|
+
filterArgs = formatFunctionArgs(splitter)
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
const newVal = filterArgs.length > 0
|
|
2298
|
+
? this.filters[filterName](acc, ...filterArgs, 'from populateVariable')
|
|
2299
|
+
: this.filters[filterName](acc, 'from populateVariable')
|
|
1989
2300
|
// console.log('PROPERTY', newVal)
|
|
1990
2301
|
return newVal
|
|
1991
2302
|
}, property)
|
|
@@ -2095,10 +2406,27 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2095
2406
|
this.resolutionTracking[pathKey] = {
|
|
2096
2407
|
path: pathKey,
|
|
2097
2408
|
originalPropertyString: propertyString,
|
|
2409
|
+
resolvedPropertyValue: undefined,
|
|
2098
2410
|
calls: []
|
|
2099
2411
|
}
|
|
2100
2412
|
}
|
|
2101
2413
|
|
|
2414
|
+
// this.resolutionTracking[pathKey].resolutionHistory = this.resolutionTracking[pathKey].resolutionHistory || []
|
|
2415
|
+
|
|
2416
|
+
// const isDuplicate = this.resolutionTracking[pathKey].resolutionHistory.some(entry =>
|
|
2417
|
+
// entry.variableString === variableString
|
|
2418
|
+
// )
|
|
2419
|
+
|
|
2420
|
+
// if (!isDuplicate) {
|
|
2421
|
+
// this.resolutionTracking[pathKey].resolutionHistory.push({
|
|
2422
|
+
// variableString: variableString,
|
|
2423
|
+
// propertyString: propertyString,
|
|
2424
|
+
// caller: caller,
|
|
2425
|
+
// lol: 'what'
|
|
2426
|
+
// })
|
|
2427
|
+
// }
|
|
2428
|
+
|
|
2429
|
+
|
|
2102
2430
|
this.resolutionTracking[pathKey].calls.push({
|
|
2103
2431
|
variableString: variableString,
|
|
2104
2432
|
propertyString: propertyString,
|
|
@@ -2205,7 +2533,6 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2205
2533
|
this.options,
|
|
2206
2534
|
this.config,
|
|
2207
2535
|
valueObject,
|
|
2208
|
-
|
|
2209
2536
|
).then((val) => {
|
|
2210
2537
|
// Update the last call with the resolved value
|
|
2211
2538
|
if (pathValue && pathValue.length) {
|
|
@@ -2214,7 +2541,8 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2214
2541
|
// Find the most recent call for this variableString
|
|
2215
2542
|
for (let i = this.resolutionTracking[pathKey].calls.length - 1; i >= 0; i--) {
|
|
2216
2543
|
if (this.resolutionTracking[pathKey].calls[i].variableString === variableString) {
|
|
2217
|
-
|
|
2544
|
+
const v = (typeof val === 'object' && val.__internal_only_flag) ? val.value : val
|
|
2545
|
+
this.resolutionTracking[pathKey].calls[i].resolvedValue = v
|
|
2218
2546
|
this.resolutionTracking[pathKey].calls[i].resolverType = resolverType
|
|
2219
2547
|
break
|
|
2220
2548
|
}
|
|
@@ -2255,17 +2583,22 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2255
2583
|
// console.log('valueCount', valueCount)
|
|
2256
2584
|
// TODO throw on empty values?
|
|
2257
2585
|
// No fallback value found AND this is undefined, throw error
|
|
2258
|
-
const nestedVars = findNestedVariables(propertyString, this.variableSyntax, this.variablesKnownTypes)
|
|
2586
|
+
const nestedVars = findNestedVariables(propertyString, this.variableSyntax, this.variablesKnownTypes, undefined, this.variableTypes)
|
|
2259
2587
|
// console.log('nestedVars', nestedVars)
|
|
2260
2588
|
const noNestedVars = nestedVars.length < 2
|
|
2589
|
+
|
|
2590
|
+
if (this.opts.allowUnknownFileRefs && variableString.match(fileRefSyntax)) {
|
|
2591
|
+
// Encode the unknown file variable to pass through resolution
|
|
2592
|
+
return Promise.resolve(encodeUnknown(propertyString))
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2261
2595
|
if (valueCount.length === 1 && noNestedVars) {
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2596
|
+
const configFilePathMsg = (this.configFilePath) ? `\nIn file ${this.configFilePath} ` : ''
|
|
2597
|
+
const fromLine = (propertyString !== valueObject.originalSource) ? `\n From "${valueObject.originalSource}"\n` : ''
|
|
2598
|
+
|
|
2599
|
+
|
|
2265
2600
|
|
|
2266
|
-
|
|
2267
|
-
From "${valueObject.originalSource}"
|
|
2268
|
-
At location ${valueObject.path ? `"${arrayToJsonPath(valueObject.path)}"` : 'na'}${configFilePath}
|
|
2601
|
+
throw new Error(`Unable to resolve config variable "${propertyString}".\n${configFilePathMsg}at location ${valueObject.path ? `"${arrayToJsonPath(valueObject.path)}"` : 'n/a'}${fromLine}
|
|
2269
2602
|
\nFix this reference, your inputs and/or provide a valid fallback value.
|
|
2270
2603
|
\nExample of setting a fallback value: \${${variableString}, "fallbackValue"\}\n`)
|
|
2271
2604
|
}
|
|
@@ -2300,13 +2633,28 @@ Unable to resolve configuration variable
|
|
|
2300
2633
|
}
|
|
2301
2634
|
|
|
2302
2635
|
const newUse = newHasFilter.reduce((acc, currentFilter, i) => {
|
|
2303
|
-
if (
|
|
2304
|
-
|
|
2636
|
+
// Check if filter has function-style arguments: filterName(arg1, arg2)
|
|
2637
|
+
const funcMatch = currentFilter.match(/^(\w+)\((.*)\)$/)
|
|
2638
|
+
let filterName = currentFilter
|
|
2639
|
+
let filterArgs = null
|
|
2640
|
+
|
|
2641
|
+
if (funcMatch) {
|
|
2642
|
+
filterName = funcMatch[1]
|
|
2643
|
+
const rawArgs = funcMatch[2]
|
|
2644
|
+
// Parse arguments using the same logic as functions
|
|
2645
|
+
if (rawArgs) {
|
|
2646
|
+
const splitter = splitCsv(rawArgs, ', ')
|
|
2647
|
+
filterArgs = formatFunctionArgs(splitter)
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
if (!this.filters[filterName]) {
|
|
2652
|
+
throw new Error(`Filter "${filterName}" not found`)
|
|
2305
2653
|
}
|
|
2306
2654
|
return acc.concat({
|
|
2307
|
-
filter: this.filters[
|
|
2308
|
-
filterName:
|
|
2309
|
-
|
|
2655
|
+
filter: this.filters[filterName],
|
|
2656
|
+
filterName: filterName,
|
|
2657
|
+
args: filterArgs
|
|
2310
2658
|
})
|
|
2311
2659
|
}, [])
|
|
2312
2660
|
// console.log('pathValue', pathValue)
|
|
@@ -2568,7 +2916,7 @@ Unable to resolve configuration variable
|
|
|
2568
2916
|
// console.log('matchedFileString', matchedFileString)
|
|
2569
2917
|
|
|
2570
2918
|
// Get function input params if any supplied https://regex101.com/r/qlNFVm/1
|
|
2571
|
-
|
|
2919
|
+
// var funcParamsRegex = /(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*/g
|
|
2572
2920
|
var funcParamsRegex = /(\w+)\s*\(((?:[^()]+)*)?\s*\)/g
|
|
2573
2921
|
// tighter (?<![.\w-])\b(\w+)\s*\(((?:[^()]+)*)?\s*\)\s*
|
|
2574
2922
|
var hasParams = funcParamsRegex.exec(matchedFileString)
|
|
@@ -2592,45 +2940,37 @@ Unable to resolve configuration variable
|
|
|
2592
2940
|
}
|
|
2593
2941
|
// console.log('argsToPass', argsToPass)
|
|
2594
2942
|
|
|
2595
|
-
const
|
|
2596
|
-
|
|
2597
|
-
)
|
|
2598
|
-
|
|
2599
|
-
// Resolve alias if the path contains alias syntax
|
|
2600
|
-
const resolvedPath = resolveAlias(relativePath, this.configPath)
|
|
2601
|
-
// console.log('resolvedPath', resolvedPath)
|
|
2602
|
-
|
|
2603
|
-
let fullFilePath = path.isAbsolute(resolvedPath) ? resolvedPath : path.join(this.configPath, resolvedPath)
|
|
2943
|
+
const fileDetails = resolveFilePathFromMatch(matchedFileString, syntax, this.configPath)
|
|
2944
|
+
// console.log('fileDetails', fileDetails)
|
|
2604
2945
|
|
|
2605
|
-
|
|
2946
|
+
const { fullFilePath, resolvedPath, relativePath } = fileDetails
|
|
2606
2947
|
|
|
2607
|
-
|
|
2608
|
-
// Get real path to handle potential symlinks (but don't fatal error)
|
|
2609
|
-
fullFilePath = fs.realpathSync(fullFilePath)
|
|
2948
|
+
const exists = fs.existsSync(fullFilePath)
|
|
2610
2949
|
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
}
|
|
2950
|
+
this.fileRefsFound.push({
|
|
2951
|
+
// location: options.context.path.join('.'),
|
|
2952
|
+
filePath: fullFilePath,
|
|
2953
|
+
relativePath,
|
|
2954
|
+
resolvedVariableString: options.context.value,
|
|
2955
|
+
originalVariableString: options.context.originalSource,
|
|
2956
|
+
containsVariables: options.context.value !== options.context.originalSource,
|
|
2957
|
+
exists,
|
|
2958
|
+
})
|
|
2620
2959
|
|
|
2621
2960
|
let fileExtension = resolvedPath.split('.')
|
|
2622
2961
|
|
|
2623
2962
|
fileExtension = fileExtension[fileExtension.length - 1]
|
|
2624
2963
|
|
|
2625
2964
|
// Validate file exists
|
|
2626
|
-
if (!
|
|
2965
|
+
if (!exists) {
|
|
2627
2966
|
const originalVar = options.context && options.context.originalSource
|
|
2628
2967
|
|
|
2629
2968
|
const findNestedResult = findNestedVariables(
|
|
2630
|
-
originalVar,
|
|
2631
|
-
this.variableSyntax,
|
|
2632
|
-
this.variablesKnownTypes,
|
|
2633
|
-
options.context.path
|
|
2969
|
+
originalVar,
|
|
2970
|
+
this.variableSyntax,
|
|
2971
|
+
this.variablesKnownTypes,
|
|
2972
|
+
options.context.path,
|
|
2973
|
+
this.variableTypes
|
|
2634
2974
|
)
|
|
2635
2975
|
// console.log('findNestedResult', findNestedResult)
|
|
2636
2976
|
let hasFallback = false
|
|
@@ -2644,9 +2984,10 @@ Unable to resolve configuration variable
|
|
|
2644
2984
|
// console.log('NO FILE FOUND', fullFilePath)
|
|
2645
2985
|
// console.log('variableString', variableString)
|
|
2646
2986
|
|
|
2647
|
-
if (!hasFallback) {
|
|
2987
|
+
if (!hasFallback && !this.opts.allowUnknownFileRefs) {
|
|
2648
2988
|
const errorMsg = makeBox({
|
|
2649
2989
|
title: `File Not Found in ${originalVar}`,
|
|
2990
|
+
minWidth: '100%',
|
|
2650
2991
|
text: `Variable ${variableString} cannot resolve due to missing file.
|
|
2651
2992
|
|
|
2652
2993
|
File not found ${fullFilePath}
|
|
@@ -2662,13 +3003,19 @@ ${JSON.stringify(options.context, null, 2)}`,
|
|
|
2662
3003
|
return Promise.resolve(undefined)
|
|
2663
3004
|
}
|
|
2664
3005
|
|
|
3006
|
+
|
|
3007
|
+
|
|
2665
3008
|
let valueToPopulate
|
|
2666
3009
|
|
|
2667
3010
|
const variableFileContents = fs.readFileSync(fullFilePath, 'utf-8')
|
|
2668
3011
|
|
|
2669
3012
|
/* handle case for referencing raw JS files to inline them */
|
|
2670
|
-
if (argsToPass.length
|
|
2671
|
-
|
|
3013
|
+
if (argsToPass.length
|
|
3014
|
+
&& (argsToPass && argsToPass[0] && argsToPass[0].toLowerCase() === 'raw')
|
|
3015
|
+
|| opts.asRawText
|
|
3016
|
+
) {
|
|
3017
|
+
// Encode foo() to foo__PH_PAREN_OPEN__) to avoid function collisions
|
|
3018
|
+
valueToPopulate = encodeJsSyntax(variableFileContents)
|
|
2672
3019
|
return Promise.resolve(valueToPopulate)
|
|
2673
3020
|
}
|
|
2674
3021
|
|
|
@@ -3015,25 +3362,25 @@ Please use ":" to reference sub properties. ${deepProperties}`
|
|
|
3015
3362
|
}
|
|
3016
3363
|
|
|
3017
3364
|
warnIfNotFound(variableString, valueToPopulate) {
|
|
3018
|
-
let
|
|
3365
|
+
let variableTypeText
|
|
3019
3366
|
if (variableString.match(envRefSyntax)) {
|
|
3020
|
-
|
|
3367
|
+
variableTypeText = 'environment variable'
|
|
3021
3368
|
} else if (variableString.match(optRefSyntax)) {
|
|
3022
|
-
|
|
3369
|
+
variableTypeText = 'option'
|
|
3023
3370
|
} else if (variableString.match(selfRefSyntax)) {
|
|
3024
|
-
|
|
3371
|
+
variableTypeText = 'config attribute'
|
|
3025
3372
|
} else if (variableString.match(fileRefSyntax)) {
|
|
3026
|
-
|
|
3373
|
+
variableTypeText = 'file'
|
|
3027
3374
|
} else if (variableString.match(deepRefSyntax)) {
|
|
3028
|
-
|
|
3375
|
+
variableTypeText = 'deep'
|
|
3029
3376
|
} else if (variableString.match(textRefSyntax)) {
|
|
3030
|
-
|
|
3377
|
+
variableTypeText = 'text'
|
|
3031
3378
|
}
|
|
3032
3379
|
if (!isValidValue(valueToPopulate)) {
|
|
3033
3380
|
// console.log("MISSING", variableString)
|
|
3034
3381
|
// console.log(this.deep)
|
|
3035
3382
|
// console.log(valueToPopulate)
|
|
3036
|
-
const notFoundMsg = `No ${
|
|
3383
|
+
const notFoundMsg = `No ${variableTypeText} found to satisfy the '\${${variableString}}' variable. Attempting fallback value`
|
|
3037
3384
|
if (DEBUG) {
|
|
3038
3385
|
console.log(notFoundMsg)
|
|
3039
3386
|
}
|