configorama 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -870,14 +870,49 @@ const config = await configorama(configFile, {
|
|
|
870
870
|
allowUnresolvedVariables: ['param', 'file'], // only these pass through
|
|
871
871
|
options: { stage: 'prod' }
|
|
872
872
|
})
|
|
873
|
-
//
|
|
874
|
-
//
|
|
873
|
+
// Input: { paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
|
|
874
|
+
// Output: { paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
|
|
875
|
+
|
|
876
|
+
// Allow only SPECIFIC types to be unresolved
|
|
877
|
+
const config = await configorama(configFile, {
|
|
878
|
+
allowUnresolvedVariables: ['param', 'file'], // only these pass through
|
|
879
|
+
options: { stage: 'prod' }
|
|
880
|
+
})
|
|
881
|
+
// Input: { key: '${env:MISSING_VAR}', paramKey: '${param:x}', fileKey: '${file(missing.yml)}' }
|
|
882
|
+
// Unresolved ${param:x} and ${file(missing.yml)} pass through but
|
|
883
|
+
// Output error thrown because ${env:MISSING_VAR} throws an error
|
|
875
884
|
```
|
|
876
885
|
|
|
877
|
-
This is useful for multi-stage resolution (e.g., Serverless Dashboard resolves params after local resolution).
|
|
886
|
+
This is useful for multi-stage resolution (e.g., Downstream Serverless Dashboard resolves params after local resolution).
|
|
887
|
+
|
|
888
|
+
> **Note:** This option does NOT apply to `self:` or dotProp variables (e.g., `${foo.bar.baz}`). These are local references that configorama fully owns—if they can't be resolved, it's a config error, not something to defer to another system.
|
|
878
889
|
|
|
879
890
|
## FAQ
|
|
880
891
|
|
|
892
|
+
**Q: What happens with circular variable dependencies?**
|
|
893
|
+
|
|
894
|
+
Configorama detects circular dependencies and throws a helpful error instead of hanging forever.
|
|
895
|
+
|
|
896
|
+
```yml
|
|
897
|
+
# Direct cycle - throws error
|
|
898
|
+
a: ${self:b}
|
|
899
|
+
b: ${self:a}
|
|
900
|
+
# Error: Circular variable dependency detected: b → a → b
|
|
901
|
+
|
|
902
|
+
# Indirect cycle - also detected
|
|
903
|
+
a: ${self:b}
|
|
904
|
+
b: ${self:c}
|
|
905
|
+
c: ${self:a}
|
|
906
|
+
# Error: Circular variable dependency detected: c → a → b → c
|
|
907
|
+
|
|
908
|
+
# Works with shorthand syntax too
|
|
909
|
+
foo:
|
|
910
|
+
bar: ${baz.qux}
|
|
911
|
+
baz:
|
|
912
|
+
qux: ${foo.bar}
|
|
913
|
+
# Error: Circular variable dependency detected: baz.qux → foo.bar → baz.qux
|
|
914
|
+
```
|
|
915
|
+
|
|
881
916
|
**Q: Why should I use this?**
|
|
882
917
|
|
|
883
918
|
Never rendering a stale configuration file again!
|
package/package.json
CHANGED
package/src/main.js
CHANGED
|
@@ -121,6 +121,7 @@ class Configorama {
|
|
|
121
121
|
allowUndefinedValues: false,
|
|
122
122
|
// Allow known variable types that can't be resolved to pass through
|
|
123
123
|
// Can be: false | true | ['param', 'file', 'env', ...]
|
|
124
|
+
// Note: Does not apply to self: or dotprop refs - those always error
|
|
124
125
|
allowUnresolvedVariables: false,
|
|
125
126
|
// Return metadata
|
|
126
127
|
returnMetadata: false,
|
|
@@ -2767,6 +2768,25 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
2767
2768
|
|
|
2768
2769
|
// console.log('getValueFromSrc propertyString', propertyString)
|
|
2769
2770
|
// console.log(`tracker contains ${variableString}`, this.tracker.contains(variableString))
|
|
2771
|
+
|
|
2772
|
+
// Cycle detection: track dependencies and check for cycles
|
|
2773
|
+
const fromPath = valueObject.path ? valueObject.path.join('.') : null
|
|
2774
|
+
// Extract target path from variableString (e.g., 'self:b' → 'b', 'b.c' → 'b.c')
|
|
2775
|
+
let toPath = variableString
|
|
2776
|
+
if (variableString.startsWith('self:')) {
|
|
2777
|
+
toPath = variableString.slice(5)
|
|
2778
|
+
}
|
|
2779
|
+
// For cycle detection, only track self-references
|
|
2780
|
+
if (fromPath && (variableString.startsWith('self:') || !variableString.includes(':'))) {
|
|
2781
|
+
if (this.tracker.wouldCreateCycle(fromPath, toPath)) {
|
|
2782
|
+
const cyclePath = this.tracker.getCyclePath(fromPath, toPath)
|
|
2783
|
+
return Promise.reject(new Error(
|
|
2784
|
+
`Circular variable dependency detected: ${cyclePath.join(' → ')}`
|
|
2785
|
+
))
|
|
2786
|
+
}
|
|
2787
|
+
this.tracker.addDependency(fromPath, toPath)
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2770
2790
|
if (this.tracker.contains(variableString)) {
|
|
2771
2791
|
// console.log('try to get', variableString)
|
|
2772
2792
|
return this.tracker.get(variableString, propertyString)
|
|
@@ -3253,6 +3273,7 @@ Missing Value ${missingValue} - ${matchedString}
|
|
|
3253
3273
|
// console.log('self fixed deepProperties', deepProperties)
|
|
3254
3274
|
}
|
|
3255
3275
|
}
|
|
3276
|
+
|
|
3256
3277
|
return this.getDeeperValue(deepProperties, valueToPopulate).then((res) => {
|
|
3257
3278
|
/*
|
|
3258
3279
|
console.log('self getDeeperValue variableString', variableString)
|
|
@@ -8,6 +8,8 @@ class PromiseTracker {
|
|
|
8
8
|
reset() {
|
|
9
9
|
this.promiseList = []
|
|
10
10
|
this.promiseMap = {}
|
|
11
|
+
// Track which variables depend on which (for cycle detection)
|
|
12
|
+
this.dependencyGraph = {}
|
|
11
13
|
this.startTime = Date.now()
|
|
12
14
|
this.cursor = 0
|
|
13
15
|
}
|
|
@@ -81,6 +83,58 @@ class PromiseTracker {
|
|
|
81
83
|
promise.waitList += ` ${specifier}`
|
|
82
84
|
return promise
|
|
83
85
|
}
|
|
86
|
+
// Add a dependency edge: "from" depends on "to"
|
|
87
|
+
addDependency(from, to) {
|
|
88
|
+
if (!this.dependencyGraph[from]) {
|
|
89
|
+
this.dependencyGraph[from] = new Set()
|
|
90
|
+
}
|
|
91
|
+
this.dependencyGraph[from].add(to)
|
|
92
|
+
}
|
|
93
|
+
// Check if adding dependency from → to would create a cycle
|
|
94
|
+
wouldCreateCycle(from, to) {
|
|
95
|
+
// Check if "to" can reach "from" (meaning from → to would close a cycle)
|
|
96
|
+
const visited = new Set()
|
|
97
|
+
const stack = [to]
|
|
98
|
+
while (stack.length > 0) {
|
|
99
|
+
const current = stack.pop()
|
|
100
|
+
if (current === from) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
if (visited.has(current)) {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
visited.add(current)
|
|
107
|
+
const deps = this.dependencyGraph[current]
|
|
108
|
+
if (deps) {
|
|
109
|
+
for (const dep of deps) {
|
|
110
|
+
stack.push(dep)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
// Get the cycle path for error reporting
|
|
117
|
+
getCyclePath(from, to) {
|
|
118
|
+
const path = [from, to]
|
|
119
|
+
const visited = new Set([from, to])
|
|
120
|
+
let current = to
|
|
121
|
+
while (current !== from) {
|
|
122
|
+
const deps = this.dependencyGraph[current]
|
|
123
|
+
if (!deps) break
|
|
124
|
+
let found = false
|
|
125
|
+
for (const dep of deps) {
|
|
126
|
+
if (dep === from || !visited.has(dep)) {
|
|
127
|
+
path.push(dep)
|
|
128
|
+
visited.add(dep)
|
|
129
|
+
current = dep
|
|
130
|
+
found = true
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!found) break
|
|
135
|
+
}
|
|
136
|
+
return path
|
|
137
|
+
}
|
|
84
138
|
getPending() {
|
|
85
139
|
return this.promiseList.filter(p => (p.state === 'pending'))
|
|
86
140
|
}
|
package/types/src/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.js"],"names":[],"mappings":";AA0GA;IACE,0CAybC;IAjbC,cAcW;IAuBX,gBAAqB;IAErB,sBAAwB;IACxB,qBAAuB;IAGvB,uBAA4B;IAc5B,uBAAoC;IAIpC,kBAAqC;IACrC,kBAAqC;IAErC,yBAA+F;IAC/F,yBAAuD;IACvD,kCAAyE;IAKvE,YAA0B;IAE1B,oBAA6C;IAE7C,gBAAoD;IAOpD,uBAAkC;IAElC,uBAA8B;IAE9B,uBAAkC;IASpC,wBAAmC;IAGnC,mBA8GC;IAoED,4BAA8C;IAO9C,aA2EC;IAUD,oBAEC;IAGD,eAkDC;IAOD,YAAc;IACd,cAAgB;IAChB,kBAAkB;IAGpB;;;;OAIG;IACH,0BAHW,MAAM,GACJ,OAAO,CAQnB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAOvB;IAED;;;;OAIG;IACH,gCAHW,MAAM,GACJ,OAAO,CAoBnB;IAKD;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,GAAG,CAAC,CAqsBxB;IAlsBC,aAA4B;IAc1B,2BAA4B;IAQ5B,uBAAgD;IA8qBpD;;;OAGG;IACH,2BAFa,MAAM,CA8alB;IAvBC;;;;;;;;;;;;;;;MAoBC;IAIH;;;;OAIG;IACH,uCAFa,OAAO,CAAC,GAAG,CAAC,CAIxB;IACD,+CAsBC;IAKD;;;;;;;;;;;;;;;;;;;OAmBG;IACH;;;;;;;;;;;OAWG;IACH,mFAHa;;;;cAZC,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAWD,CA+D9B;IACD;;;OAGG;IACH;;;;;OAKG;IACH,oCAHa,OAAO,CAAC;;;;cAnFP,QAAQ;;;;eACR,IAAI,GAAC,MAAM,SAAO;OAkFgB,CAAC,EAAE,CA6BlD;IACD;;;;;OAKG;IACH,iDAFa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAID;;;;;OAKG;IACH;;;;OAIG;IACH,2BAFa,eAAc;;;;;;;;;;OAAa,CAavC;IACD;;;;;OAKG;IACH,yBAHW;;;;;;;;;;OAAa,gCACX,cAAS,CAOrB;IACD;;;;;;OAMG;IACH,6DAFa,GAAC,CA+Kb;IAKD;;;;;;;OAOG;IACH,yDAHa,OAAO,CAAC,GAAG,CAAC,CAiCxB;IACD;;;;OAIG;IAOH;;;;;;OAMG;IACH,wFA2BC;IACD;;;;;;;;;;;OAWG;IACH,8BARG;QAAyB,KAAK,EAAtB,GAAG;QACoB,IAAI,GAA3B,MAAM,EAAE;QACa,cAAc,GAAnC,MAAM;QACc,iBAAiB;KAC7C,6CAEU;QAAC,KAAK,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,QAAQ;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC,CAmY9J;IAID;;;;;;;;OAQG;IACH,qEAHa,OAAO,CAAC,GAAG,CAAC,CAoExB;IAKD;;;;;;;OAOG;IACH,0FAFa,OAAO,CAAC,GAAG,CAAC,CA6gBxB;IACD,+EA+BC;IACD,yDAeC;IACD,oEA6BC;IAKD,8CAQC;IACD,kDAyBC;IACD;;;;;;;;;;;;;OAaG;IACH,wEAoDC;IAKD,4BAOC;IACD,sCAoDC;CACF"}
|
|
@@ -3,6 +3,7 @@ declare class PromiseTracker {
|
|
|
3
3
|
reset(): void;
|
|
4
4
|
promiseList: any[];
|
|
5
5
|
promiseMap: {};
|
|
6
|
+
dependencyGraph: {};
|
|
6
7
|
startTime: number;
|
|
7
8
|
cursor: number;
|
|
8
9
|
start(): void;
|
|
@@ -12,6 +13,9 @@ declare class PromiseTracker {
|
|
|
12
13
|
add(variable: any, promise: any, specifier: any, hasFilter: any, promiseKey: any): any;
|
|
13
14
|
contains(variable: any): boolean;
|
|
14
15
|
get(variable: any, specifier: any): any;
|
|
16
|
+
addDependency(from: any, to: any): void;
|
|
17
|
+
wouldCreateCycle(from: any, to: any): boolean;
|
|
18
|
+
getCyclePath(from: any, to: any): any[];
|
|
15
19
|
getPending(): any[];
|
|
16
20
|
getSettled(): any[];
|
|
17
21
|
getAll(): any[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromiseTracker.d.ts","sourceRoot":"","sources":["../../../src/utils/PromiseTracker.js"],"names":[],"mappings":";AAGA;IAIE,
|
|
1
|
+
{"version":3,"file":"PromiseTracker.d.ts","sourceRoot":"","sources":["../../../src/utils/PromiseTracker.js"],"names":[],"mappings":";AAGA;IAIE,cAOC;IANC,mBAAqB;IACrB,eAAoB;IAEpB,oBAAyB;IACzB,kBAA2B;IAC3B,eAAe;IAEjB,cAGC;IADC,yBAAyD;IAE3D,eAqBC;IACD,aAGC;IACD,uFA+BC;IACD,iCAEC;IACD,wCAIC;IAED,wCAKC;IAED,8CAqBC;IAED,wCAoBC;IACD,oBAEC;IACD,oBAEC;IACD,gBAEC;CACF"}
|