typed-settings 0.1.1 → 0.2.1
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 +18 -9
- package/bin/typed-settings.mjs +5 -0
- package/dist/adapters/joi.cjs +1 -26
- package/dist/adapters/joi.d.cts +2 -1
- package/dist/adapters/joi.d.ts +2 -1
- package/dist/adapters/joi.js +1 -1
- package/dist/adapters/superstruct.cjs +1 -26
- package/dist/adapters/superstruct.js +1 -1
- package/dist/adapters/yup.cjs +1 -25
- package/dist/adapters/yup.js +1 -1
- package/dist/adapters/zod.cjs +3 -36
- package/dist/adapters/zod.d.cts +2 -0
- package/dist/adapters/zod.d.ts +2 -0
- package/dist/adapters/zod.js +1 -33
- package/dist/check-XVFZEVNV.js +1 -0
- package/dist/chunk-4EAMKDNK.js +3 -0
- package/dist/chunk-5D2AQWDG.js +1 -0
- package/dist/chunk-C6CG2D4B.js +2 -0
- package/dist/chunk-FQYD5ZKE.js +1 -0
- package/dist/chunk-NQDE3WLR.js +3 -0
- package/dist/chunk-RURPDXIJ.js +1 -0
- package/dist/cli/main.cjs +36 -2220
- package/dist/cli/main.js +32 -806
- package/dist/errors-JkkOM0u-.d.cts +25 -0
- package/dist/errors-JkkOM0u-.d.ts +25 -0
- package/dist/framework/next.cjs +5 -1153
- package/dist/framework/next.d.cts +5 -3
- package/dist/framework/next.d.ts +5 -3
- package/dist/framework/next.js +1 -15
- package/dist/framework/vite.cjs +5 -1151
- package/dist/framework/vite.d.cts +10 -3
- package/dist/framework/vite.d.ts +10 -3
- package/dist/framework/vite.js +1 -14
- package/dist/index.cjs +5 -1176
- package/dist/index.d.cts +46 -10
- package/dist/index.d.ts +46 -10
- package/dist/index.js +1 -6
- package/dist/node.cjs +6 -1236
- package/dist/node.d.cts +23 -9
- package/dist/node.d.ts +23 -9
- package/dist/node.js +1 -7
- package/dist/{types-CClP1lxq.d.ts → types-CcsYDAdO.d.cts} +27 -3
- package/dist/{types-CClP1lxq.d.cts → types-CcsYDAdO.d.ts} +27 -3
- package/dist/vault/aws.cjs +4 -217
- package/dist/vault/aws.d.cts +3 -1
- package/dist/vault/aws.d.ts +3 -1
- package/dist/vault/aws.js +2 -167
- package/dist/vault/hashicorp.cjs +4 -183
- package/dist/vault/hashicorp.d.cts +3 -1
- package/dist/vault/hashicorp.d.ts +3 -1
- package/dist/vault/hashicorp.js +2 -77
- package/dist/vault/http.cjs +4 -185
- package/dist/vault/http.d.cts +3 -1
- package/dist/vault/http.d.ts +3 -1
- package/dist/vault/http.js +2 -79
- package/package.json +10 -3
- package/dist/chunk-33M5GIKQ.js +0 -13
- package/dist/chunk-A5TCQJ7L.js +0 -65
- package/dist/chunk-EE6LXNJV.js +0 -34
- package/dist/chunk-F6DX3HXZ.js +0 -101
- package/dist/chunk-OVFMKC2O.js +0 -15
- package/dist/chunk-RCO6K3WQ.js +0 -984
- package/dist/chunk-RQC6OMVV.js +0 -86
- package/dist/chunk-UFUHHR2J.js +0 -38
- package/dist/chunk-VTIYOSG2.js +0 -233
- package/dist/errors-itGaU4Q8.d.cts +0 -17
- package/dist/errors-itGaU4Q8.d.ts +0 -17
- package/dist/formats/env.cjs +0 -103
- package/dist/formats/env.d.cts +0 -8
- package/dist/formats/env.d.ts +0 -8
- package/dist/formats/env.js +0 -1
- package/dist/formats/json.cjs +0 -15
- package/dist/formats/json.d.cts +0 -7
- package/dist/formats/json.d.ts +0 -7
- package/dist/formats/json.js +0 -1
- package/dist/formats/toml.cjs +0 -17
- package/dist/formats/toml.d.cts +0 -7
- package/dist/formats/toml.d.ts +0 -7
- package/dist/formats/toml.js +0 -1
- package/dist/formats/yaml.cjs +0 -40
- package/dist/formats/yaml.d.cts +0 -8
- package/dist/formats/yaml.d.ts +0 -8
- package/dist/formats/yaml.js +0 -1
package/dist/chunk-RCO6K3WQ.js
DELETED
|
@@ -1,984 +0,0 @@
|
|
|
1
|
-
import { parseJsonText } from './chunk-33M5GIKQ.js';
|
|
2
|
-
import { parseTomlText } from './chunk-OVFMKC2O.js';
|
|
3
|
-
import { parseYamlText } from './chunk-UFUHHR2J.js';
|
|
4
|
-
import { ConfigError, redactValue, byteLengthUtf8 } from './chunk-EE6LXNJV.js';
|
|
5
|
-
import { parseEnvText } from './chunk-F6DX3HXZ.js';
|
|
6
|
-
|
|
7
|
-
// src/expand.ts
|
|
8
|
-
var NAME_RE = "[A-Za-z_][A-Za-z0-9_]*";
|
|
9
|
-
function expandValue(input, lookup, opts) {
|
|
10
|
-
let out = "";
|
|
11
|
-
let i = 0;
|
|
12
|
-
while (i < input.length) {
|
|
13
|
-
const c = input[i];
|
|
14
|
-
if (c === "$" && input[i + 1] === "$") {
|
|
15
|
-
out += "$";
|
|
16
|
-
i += 2;
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
if (c === "\\" && input[i + 1] === "$") {
|
|
20
|
-
out += "$";
|
|
21
|
-
i += 2;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (c !== "$") {
|
|
25
|
-
out += c;
|
|
26
|
-
i++;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
const rest = input.slice(i);
|
|
30
|
-
if (rest.startsWith("${")) {
|
|
31
|
-
const parsed = parseBraced(input, i);
|
|
32
|
-
if (!parsed) {
|
|
33
|
-
out += "$";
|
|
34
|
-
i++;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
const { name: name2, op, def, end } = parsed;
|
|
38
|
-
if (op && op !== ":-" && op !== "-") {
|
|
39
|
-
throw new ConfigError([
|
|
40
|
-
{ path: name2, from: opts?.from ?? "env", message: `E_BAD_OP: only :- and - supported, got ${op}` }
|
|
41
|
-
], "E_BAD_OP");
|
|
42
|
-
}
|
|
43
|
-
const val2 = lookup(name2);
|
|
44
|
-
const missing = val2 === void 0 || op === ":-" && val2 === "";
|
|
45
|
-
if (!missing) {
|
|
46
|
-
out += val2;
|
|
47
|
-
} else if (def !== void 0) {
|
|
48
|
-
out += expandValue(def, lookup, opts);
|
|
49
|
-
} else if (opts?.allowUnresolved) {
|
|
50
|
-
out += input.slice(i, end);
|
|
51
|
-
} else {
|
|
52
|
-
throw new ConfigError([
|
|
53
|
-
{ path: name2, from: opts?.from ?? "env", message: `E_UNRESOLVED: ${name2} missing (expand)` }
|
|
54
|
-
], "E_UNRESOLVED");
|
|
55
|
-
}
|
|
56
|
-
i = end;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const m = new RegExp(`^\\$(?<name>${NAME_RE})`).exec(rest);
|
|
60
|
-
if (!m || !m.groups) {
|
|
61
|
-
out += "$";
|
|
62
|
-
i++;
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const name = m.groups["name"];
|
|
66
|
-
const val = lookup(name);
|
|
67
|
-
if (val === void 0) {
|
|
68
|
-
if (opts?.allowUnresolved) {
|
|
69
|
-
out += "$" + name;
|
|
70
|
-
} else {
|
|
71
|
-
throw new ConfigError([
|
|
72
|
-
{ path: name, from: opts?.from ?? "env", message: `E_UNRESOLVED: ${name} missing (expand)` }
|
|
73
|
-
], "E_UNRESOLVED");
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
out += val;
|
|
77
|
-
}
|
|
78
|
-
i += 1 + name.length;
|
|
79
|
-
}
|
|
80
|
-
return out;
|
|
81
|
-
}
|
|
82
|
-
function parseBraced(input, start) {
|
|
83
|
-
let i = start + 2;
|
|
84
|
-
const nameMatch = new RegExp(`^(?<name>${NAME_RE})`).exec(input.slice(i));
|
|
85
|
-
if (!nameMatch?.groups) return null;
|
|
86
|
-
const name = nameMatch.groups["name"];
|
|
87
|
-
i += name.length;
|
|
88
|
-
if (input[i] === "}") return { name, end: i + 1 };
|
|
89
|
-
if (input[i] === ":") {
|
|
90
|
-
const two = input.slice(i, i + 2);
|
|
91
|
-
if (two !== ":-") return { name, op: two[0] + (input[i + 1] ?? ""), def: "", end: input.length };
|
|
92
|
-
i += 2;
|
|
93
|
-
const { value, end } = readUntilCloseBrace(input, i);
|
|
94
|
-
return { name, op: ":-", def: value, end };
|
|
95
|
-
}
|
|
96
|
-
if (input[i] === "-") {
|
|
97
|
-
i += 1;
|
|
98
|
-
const { value, end } = readUntilCloseBrace(input, i);
|
|
99
|
-
return { name, op: "-", def: value, end };
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
function readUntilCloseBrace(input, start) {
|
|
104
|
-
let depth = 0;
|
|
105
|
-
let i = start;
|
|
106
|
-
let val = "";
|
|
107
|
-
while (i < input.length) {
|
|
108
|
-
if (input[i] === "\\" && input[i + 1] === "}") {
|
|
109
|
-
val += "}";
|
|
110
|
-
i += 2;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (input[i] === "{") depth++;
|
|
114
|
-
if (input[i] === "}") {
|
|
115
|
-
if (depth === 0) return { value: val, end: i + 1 };
|
|
116
|
-
depth--;
|
|
117
|
-
}
|
|
118
|
-
val += input[i];
|
|
119
|
-
i++;
|
|
120
|
-
}
|
|
121
|
-
throw new Error("ParseError: unterminated ${}");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// src/coerce.ts
|
|
125
|
-
function tryParseEnvValue(input) {
|
|
126
|
-
return tryParse(input, true);
|
|
127
|
-
}
|
|
128
|
-
function tryParse(input, allowSplit) {
|
|
129
|
-
if (typeof input !== "string") return input;
|
|
130
|
-
const s = input;
|
|
131
|
-
if (s === "") return "";
|
|
132
|
-
const low = s.toLowerCase();
|
|
133
|
-
if (low === "true" || low === "false") return low === "true";
|
|
134
|
-
if (low === "null") return null;
|
|
135
|
-
if (low === "undefined") return void 0;
|
|
136
|
-
if (/^-?\d+$/.test(s)) {
|
|
137
|
-
const n = Number(s);
|
|
138
|
-
if (Number.isSafeInteger(n)) return n;
|
|
139
|
-
return s;
|
|
140
|
-
}
|
|
141
|
-
if (/^-?(\d+\.\d*|\.\d+|\d+)(e[+-]?\d+)?$/i.test(s) && s !== "-" && s !== ".") {
|
|
142
|
-
const n = Number(s);
|
|
143
|
-
if (Number.isFinite(n)) return n;
|
|
144
|
-
return s;
|
|
145
|
-
}
|
|
146
|
-
if (["yes", "y", "on"].includes(low)) return true;
|
|
147
|
-
if (["no", "n", "off"].includes(low)) return false;
|
|
148
|
-
const t = s.trim();
|
|
149
|
-
if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
|
|
150
|
-
try {
|
|
151
|
-
return JSON.parse(t);
|
|
152
|
-
} catch {
|
|
153
|
-
return input;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (allowSplit && s.includes(",")) {
|
|
157
|
-
const parts = splitCsv(s);
|
|
158
|
-
if (parts !== null) return parts.map((p) => tryParse(p, false));
|
|
159
|
-
}
|
|
160
|
-
return input;
|
|
161
|
-
}
|
|
162
|
-
function splitCsv(s) {
|
|
163
|
-
const out = [];
|
|
164
|
-
let cur = "";
|
|
165
|
-
let quote = null;
|
|
166
|
-
for (let i = 0; i < s.length; i++) {
|
|
167
|
-
const c = s[i];
|
|
168
|
-
if (quote) {
|
|
169
|
-
if (c === "\\" && i + 1 < s.length) {
|
|
170
|
-
cur += c + s[i + 1];
|
|
171
|
-
i++;
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
if (c === quote) {
|
|
175
|
-
quote = null;
|
|
176
|
-
cur += c;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
cur += c;
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (c === '"' || c === "'") {
|
|
183
|
-
quote = c;
|
|
184
|
-
cur += c;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
if (c === "\\" && s[i + 1] === ",") {
|
|
188
|
-
cur += ",";
|
|
189
|
-
i++;
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (c === ",") {
|
|
193
|
-
out.push(cur.trim());
|
|
194
|
-
cur = "";
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
cur += c;
|
|
198
|
-
}
|
|
199
|
-
if (quote) return null;
|
|
200
|
-
out.push(cur.trim());
|
|
201
|
-
return out.map((p) => {
|
|
202
|
-
if (p.length >= 2 && p.startsWith("'") && p.endsWith("'")) return p.slice(1, -1).replace(/\\,/g, ",");
|
|
203
|
-
if (p.length >= 2 && p.startsWith('"') && p.endsWith('"')) return p.slice(1, -1);
|
|
204
|
-
return p.replace(/\\,/g, ",");
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
function coerceDeep(value) {
|
|
208
|
-
if (typeof value === "string") return tryParseEnvValue(value);
|
|
209
|
-
if (Array.isArray(value)) return value.map(coerceDeep);
|
|
210
|
-
if (value && typeof value === "object") {
|
|
211
|
-
const o = {};
|
|
212
|
-
for (const [k, v] of Object.entries(value)) o[k] = coerceDeep(v);
|
|
213
|
-
return o;
|
|
214
|
-
}
|
|
215
|
-
return value;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/merge.ts
|
|
219
|
-
var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
220
|
-
var INDEX_RE = /^(0|[1-9][0-9]*)$/;
|
|
221
|
-
function stripPrefix(key, prefix) {
|
|
222
|
-
return key;
|
|
223
|
-
}
|
|
224
|
-
function expandKeys(flat, opts) {
|
|
225
|
-
const out = {};
|
|
226
|
-
const topKinds = /* @__PURE__ */ new Map();
|
|
227
|
-
const kindOf = (top) => {
|
|
228
|
-
let k = topKinds.get(top);
|
|
229
|
-
if (!k) {
|
|
230
|
-
k = { indexed: false, named: false, scalar: false };
|
|
231
|
-
topKinds.set(top, k);
|
|
232
|
-
}
|
|
233
|
-
return k;
|
|
234
|
-
};
|
|
235
|
-
const lookupEnvMap = (rawKey) => {
|
|
236
|
-
if (!opts?.envMap) return void 0;
|
|
237
|
-
for (const cand of [rawKey, rawKey.toLowerCase(), rawKey.toUpperCase()]) {
|
|
238
|
-
const hit = opts.envMap[cand];
|
|
239
|
-
if (hit !== void 0) return hit;
|
|
240
|
-
}
|
|
241
|
-
return void 0;
|
|
242
|
-
};
|
|
243
|
-
for (const [rawKey, value] of Object.entries(flat)) {
|
|
244
|
-
if (!/^[A-Za-z0-9_]+$/.test(rawKey)) continue;
|
|
245
|
-
const escaped = lookupEnvMap(rawKey);
|
|
246
|
-
const effective = escaped ?? rawKey;
|
|
247
|
-
if (escaped !== void 0) {
|
|
248
|
-
const k = effective.toLowerCase();
|
|
249
|
-
if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
|
|
250
|
-
out[k] = value;
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
const stripped = stripPrefix(rawKey);
|
|
254
|
-
if (stripped === null) continue;
|
|
255
|
-
const useKey = stripped;
|
|
256
|
-
if (useKey.includes("__")) {
|
|
257
|
-
if (FORBIDDEN.has(useKey.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
|
|
258
|
-
const parts = useKey.split("__");
|
|
259
|
-
for (const p of parts) {
|
|
260
|
-
if (FORBIDDEN.has(p.toLowerCase())) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO: forbidden key" }], "E_PROTO");
|
|
261
|
-
if (!p) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_EMPTY_SEGMENT" }], "E_EMPTY_SEGMENT");
|
|
262
|
-
}
|
|
263
|
-
const lowered = parts.map((p) => p.toLowerCase());
|
|
264
|
-
const top = lowered[0];
|
|
265
|
-
const kind = kindOf(top);
|
|
266
|
-
const childIsIndex = lowered.length > 1 && INDEX_RE.test(lowered[1]);
|
|
267
|
-
if (kind.scalar && childIsIndex) {
|
|
268
|
-
throw new ConfigError(
|
|
269
|
-
[{ path: rawKey, from: "keys", message: `E_ARRAY_MIX: '${top}' has both scalar/list and indexed (__0) values in the same layer` }],
|
|
270
|
-
"E_ARRAY_MIX"
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
if (childIsIndex) {
|
|
274
|
-
if (kind.named) {
|
|
275
|
-
throw new ConfigError(
|
|
276
|
-
[{ path: rawKey, from: "keys", message: `E_ARRAY_MIX: '${top}' mixes indexed (__0) and named children in the same layer` }],
|
|
277
|
-
"E_ARRAY_MIX"
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
kind.indexed = true;
|
|
281
|
-
} else if (lowered.length > 1) {
|
|
282
|
-
if (kind.indexed) {
|
|
283
|
-
throw new ConfigError(
|
|
284
|
-
[{ path: rawKey, from: "keys", message: `E_ARRAY_MIX: '${top}' mixes named and indexed (__0) children in the same layer` }],
|
|
285
|
-
"E_ARRAY_MIX"
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
kind.named = true;
|
|
289
|
-
}
|
|
290
|
-
setPath(out, lowered, value);
|
|
291
|
-
} else {
|
|
292
|
-
const k = useKey.toLowerCase();
|
|
293
|
-
if (FORBIDDEN.has(k)) throw new ConfigError([{ path: rawKey, from: "keys", message: "E_PROTO" }], "E_PROTO");
|
|
294
|
-
const kind = kindOf(k);
|
|
295
|
-
if (kind.indexed) {
|
|
296
|
-
throw new ConfigError(
|
|
297
|
-
[{ path: rawKey, from: "keys", message: `E_ARRAY_MIX: '${k}' has both indexed (__0) and scalar/list values in the same layer` }],
|
|
298
|
-
"E_ARRAY_MIX"
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
kind.scalar = true;
|
|
302
|
-
out[k] = value;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return out;
|
|
306
|
-
}
|
|
307
|
-
function normalizeIndexedObjects(value, path = "<root>") {
|
|
308
|
-
if (Array.isArray(value)) return value.map((x, i) => normalizeIndexedObjects(x, `${path}[${i}]`));
|
|
309
|
-
if (!isPlainObject(value)) return value;
|
|
310
|
-
const entries = Object.entries(value);
|
|
311
|
-
const normalized = {};
|
|
312
|
-
for (const [k, v] of entries) {
|
|
313
|
-
normalized[k] = normalizeIndexedObjects(v, path === "<root>" ? k : `${path}.${k}`);
|
|
314
|
-
}
|
|
315
|
-
if (entries.length > 0 && entries.every(([k]) => INDEX_RE.test(k))) {
|
|
316
|
-
const idx = entries.map(([k]) => Number(k)).sort((a, b) => a - b);
|
|
317
|
-
if (idx.length > 1024) {
|
|
318
|
-
throw new ConfigError([{ path, from: "keys", message: "E_ARRAY_CAP: >1024 elements" }], "E_ARRAY_CAP");
|
|
319
|
-
}
|
|
320
|
-
for (let i = 0; i < idx.length; i++) {
|
|
321
|
-
if (idx[i] !== i) {
|
|
322
|
-
throw new ConfigError(
|
|
323
|
-
[{ path, from: "keys", message: `E_SPARSE_ARRAY: indexes [${idx.join(",")}] are not dense 0..${idx.length - 1}` }],
|
|
324
|
-
"E_SPARSE_ARRAY"
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return idx.map((_, i) => normalized[String(i)]);
|
|
329
|
-
}
|
|
330
|
-
return normalized;
|
|
331
|
-
}
|
|
332
|
-
function setPath(root, parts, value) {
|
|
333
|
-
let cur = root;
|
|
334
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
335
|
-
const p = parts[i];
|
|
336
|
-
const existing = cur[p];
|
|
337
|
-
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
338
|
-
cur = existing;
|
|
339
|
-
} else {
|
|
340
|
-
const n = {};
|
|
341
|
-
cur[p] = n;
|
|
342
|
-
cur = n;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
cur[parts[parts.length - 1]] = value;
|
|
346
|
-
}
|
|
347
|
-
function isPlainObject(v) {
|
|
348
|
-
if (!v || typeof v !== "object") return false;
|
|
349
|
-
const proto = Object.getPrototypeOf(v);
|
|
350
|
-
return proto === Object.prototype || proto === null;
|
|
351
|
-
}
|
|
352
|
-
function deepMerge(base, override, arrayStrategy = "replace") {
|
|
353
|
-
if (override === void 0) return base;
|
|
354
|
-
if (base === void 0) return override;
|
|
355
|
-
if (override === null) return null;
|
|
356
|
-
if (Array.isArray(base) && Array.isArray(override)) {
|
|
357
|
-
if (arrayStrategy === "replace") return override;
|
|
358
|
-
if (arrayStrategy === "concat") return [...base, ...override];
|
|
359
|
-
const len = Math.max(base.length, override.length);
|
|
360
|
-
if (len > 1024) throw new ConfigError([{ path: "<array>", from: "merge", message: "E_ARRAY_CAP: >1024" }], "E_ARRAY_CAP");
|
|
361
|
-
const out = [];
|
|
362
|
-
for (let i = 0; i < len; i++) {
|
|
363
|
-
if (i in override) {
|
|
364
|
-
out[i] = i in base ? deepMerge(base[i], override[i], arrayStrategy) : override[i];
|
|
365
|
-
} else if (i in base) {
|
|
366
|
-
out[i] = base[i];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
for (let i = 0; i < len; i++) {
|
|
370
|
-
if (!(i in out)) {
|
|
371
|
-
throw new ConfigError(
|
|
372
|
-
[{ path: `<array>[${i}]`, from: "merge", message: "E_SPARSE_ARRAY: hole at index " + i }],
|
|
373
|
-
"E_SPARSE_ARRAY"
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return out;
|
|
378
|
-
}
|
|
379
|
-
if (isPlainObject(base) && isPlainObject(override)) {
|
|
380
|
-
const out = { ...base };
|
|
381
|
-
for (const [k, v] of Object.entries(override)) {
|
|
382
|
-
if (FORBIDDEN.has(k)) throw new ConfigError([{ path: k, from: "merge", message: "E_PROTO" }], "E_PROTO");
|
|
383
|
-
out[k] = k in out ? deepMerge(out[k], v, arrayStrategy) : v;
|
|
384
|
-
}
|
|
385
|
-
return out;
|
|
386
|
-
}
|
|
387
|
-
return override;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// src/adapter.ts
|
|
391
|
-
function normalizePath(path) {
|
|
392
|
-
if (!path || path.length === 0) return "<root>";
|
|
393
|
-
const parts = [];
|
|
394
|
-
for (const seg of path) {
|
|
395
|
-
if (seg !== null && typeof seg === "object" && "key" in seg) {
|
|
396
|
-
parts.push(String(seg.key));
|
|
397
|
-
} else {
|
|
398
|
-
parts.push(String(seg));
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return parts.join(".");
|
|
402
|
-
}
|
|
403
|
-
function validateStandard(schema, input) {
|
|
404
|
-
let r;
|
|
405
|
-
try {
|
|
406
|
-
r = schema["~standard"].validate(input);
|
|
407
|
-
} catch (e) {
|
|
408
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
409
|
-
return { issues: [{ path: "<root>", message: msg }], async: false };
|
|
410
|
-
}
|
|
411
|
-
if (typeof r?.then === "function") {
|
|
412
|
-
const p = r.then(
|
|
413
|
-
(resolved) => normalizeResult(resolved),
|
|
414
|
-
(e) => ({ ok: false, issues: [{ path: "<root>", message: e instanceof Error ? e.message : String(e) }] })
|
|
415
|
-
);
|
|
416
|
-
return { promise: p };
|
|
417
|
-
}
|
|
418
|
-
const n = normalizeResult(r);
|
|
419
|
-
if (!n.ok) return { issues: n.issues, async: false };
|
|
420
|
-
return { value: n.value };
|
|
421
|
-
}
|
|
422
|
-
function normalizeResult(r) {
|
|
423
|
-
if (!r.issues || r.issues.length === 0) {
|
|
424
|
-
return { ok: true, value: r.value };
|
|
425
|
-
}
|
|
426
|
-
return {
|
|
427
|
-
ok: false,
|
|
428
|
-
issues: r.issues.map((i) => ({ path: normalizePath(i.path), message: i.message }))
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// src/index.ts
|
|
433
|
-
function getEnvSnapshot(injected) {
|
|
434
|
-
if (injected) return injected;
|
|
435
|
-
const g = globalThis;
|
|
436
|
-
if (g.process?.env) return { ...g.process.env };
|
|
437
|
-
return {};
|
|
438
|
-
}
|
|
439
|
-
function isSecretKey(key) {
|
|
440
|
-
return /key|secret|token|password|private/i.test(key);
|
|
441
|
-
}
|
|
442
|
-
function withOverrides(map, fn) {
|
|
443
|
-
const g = globalThis;
|
|
444
|
-
const prev = g.process?.env ? { ...g.process.env } : void 0;
|
|
445
|
-
const restore = () => {
|
|
446
|
-
if (g.process?.env && prev) {
|
|
447
|
-
for (const k of Object.keys(g.process.env)) delete g.process.env[k];
|
|
448
|
-
Object.assign(g.process.env, prev);
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
if (g.process?.env) {
|
|
452
|
-
for (const [k, v] of Object.entries(map)) {
|
|
453
|
-
if (v === void 0) delete g.process.env[k];
|
|
454
|
-
else g.process.env[k] = v;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
let out;
|
|
458
|
-
try {
|
|
459
|
-
out = fn();
|
|
460
|
-
} catch (e) {
|
|
461
|
-
restore();
|
|
462
|
-
throw e;
|
|
463
|
-
}
|
|
464
|
-
if (isThenable(out)) return out.finally(restore);
|
|
465
|
-
restore();
|
|
466
|
-
return out;
|
|
467
|
-
}
|
|
468
|
-
function pickPublic(cfg, keys) {
|
|
469
|
-
const out = {};
|
|
470
|
-
for (const k of keys) if (k in cfg) out[k] = cfg[k];
|
|
471
|
-
return out;
|
|
472
|
-
}
|
|
473
|
-
function viteSettings(opts, metaEnv) {
|
|
474
|
-
return settings({ ...opts, sources: [{ map: metaEnv }], env: {} });
|
|
475
|
-
}
|
|
476
|
-
function readFileSyncSafe(path, maxBytes = 2 * 1024 * 1024) {
|
|
477
|
-
const getFs = () => {
|
|
478
|
-
const injected = globalThis.__typedSettingsFs;
|
|
479
|
-
if (injected) return injected;
|
|
480
|
-
const g = globalThis;
|
|
481
|
-
if (g.process) {
|
|
482
|
-
const fn = new Function('return typeof require !== "undefined" ? require("node:fs") : null')();
|
|
483
|
-
if (!fn) {
|
|
484
|
-
throw new ConfigError(
|
|
485
|
-
[{ path, from: path, message: "E_NO_FS: file sources need `typed-settings/node` under ESM (or pass { text }/{ map })" }],
|
|
486
|
-
"E_NO_FS"
|
|
487
|
-
);
|
|
488
|
-
}
|
|
489
|
-
return fn;
|
|
490
|
-
}
|
|
491
|
-
return null;
|
|
492
|
-
};
|
|
493
|
-
try {
|
|
494
|
-
const fs = getFs();
|
|
495
|
-
if (!fs) return void 0;
|
|
496
|
-
const text = fs.readFileSync(path, "utf8");
|
|
497
|
-
if (byteLengthUtf8(text) > maxBytes) {
|
|
498
|
-
throw new ConfigError(
|
|
499
|
-
[{ path, from: path, message: `E_FILE_TOO_LARGE: >${maxBytes} bytes` }],
|
|
500
|
-
"E_FILE_TOO_LARGE"
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
return text;
|
|
504
|
-
} catch (e) {
|
|
505
|
-
if (e instanceof ConfigError) throw e;
|
|
506
|
-
const err = e;
|
|
507
|
-
if (err?.code === "ENOENT") return void 0;
|
|
508
|
-
throw e;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
function parseStructuredFile(path, text) {
|
|
512
|
-
const lower = path.toLowerCase();
|
|
513
|
-
try {
|
|
514
|
-
if (lower.endsWith(".json")) return parseJsonText(text, path);
|
|
515
|
-
if (lower.endsWith(".yaml") || lower.endsWith(".yml")) return parseYamlText(text, path);
|
|
516
|
-
return parseTomlText(text, path);
|
|
517
|
-
} catch (e) {
|
|
518
|
-
const err = e;
|
|
519
|
-
throw new ConfigError([{ path, from: path, message: err.message }], "E_PARSE");
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function stripFlatKey(key, effPrefix) {
|
|
523
|
-
if (!effPrefix) return key;
|
|
524
|
-
if (key.length <= effPrefix.length) return null;
|
|
525
|
-
if (key.slice(0, effPrefix.length).toLowerCase() !== effPrefix.toLowerCase()) return null;
|
|
526
|
-
return key.slice(effPrefix.length);
|
|
527
|
-
}
|
|
528
|
-
function assertAcyclic(value, label) {
|
|
529
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
530
|
-
const visit = (v, path) => {
|
|
531
|
-
if (!v || typeof v !== "object") return;
|
|
532
|
-
if (visiting.has(v)) {
|
|
533
|
-
throw new ConfigError(
|
|
534
|
-
[{ path, from: label, message: `E_CYCLE: cyclic value at ${path} (from ${label})` }],
|
|
535
|
-
"E_CYCLE"
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
visiting.add(v);
|
|
539
|
-
try {
|
|
540
|
-
for (const [k, child] of Object.entries(v)) visit(child, path ? `${path}.${k}` : k);
|
|
541
|
-
} finally {
|
|
542
|
-
visiting.delete(v);
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
visit(value, "");
|
|
546
|
-
}
|
|
547
|
-
function flatToLayer(m, label, srcPrefix, globalPrefix, opts, skipExpand) {
|
|
548
|
-
assertAcyclic(m, label);
|
|
549
|
-
const effPrefix = srcPrefix ?? globalPrefix;
|
|
550
|
-
const stripped = {};
|
|
551
|
-
for (const [k, v] of Object.entries(m)) {
|
|
552
|
-
const s = stripFlatKey(k, effPrefix);
|
|
553
|
-
if (s === null) continue;
|
|
554
|
-
stripped[s] = v;
|
|
555
|
-
}
|
|
556
|
-
const node = normalizeIndexedObjects(
|
|
557
|
-
expandKeys(stripped, { envMap: opts.envMap })
|
|
558
|
-
);
|
|
559
|
-
return { node, label, skipExpand };
|
|
560
|
-
}
|
|
561
|
-
function structuredToLayer(value, label, prefix) {
|
|
562
|
-
assertAcyclic(value, label);
|
|
563
|
-
if (!isPlainObject(value)) return { node: value ?? {}, label, skipExpand: false };
|
|
564
|
-
const out = {};
|
|
565
|
-
for (const [k, v] of Object.entries(value)) {
|
|
566
|
-
if (["__proto__", "constructor", "prototype"].includes(k)) {
|
|
567
|
-
throw new ConfigError([{ path: k, from: label, message: "E_PROTO: forbidden key" }], "E_PROTO");
|
|
568
|
-
}
|
|
569
|
-
if (prefix) {
|
|
570
|
-
const s = stripFlatKey(k, prefix);
|
|
571
|
-
if (s === null) continue;
|
|
572
|
-
out[s] = v;
|
|
573
|
-
} else {
|
|
574
|
-
out[k] = v;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return { node: out, label, skipExpand: false };
|
|
578
|
-
}
|
|
579
|
-
function recordLayerFrom(fromMap, layer) {
|
|
580
|
-
const node = layer.node;
|
|
581
|
-
if (node && typeof node === "object" && !Array.isArray(node)) {
|
|
582
|
-
for (const k of Object.keys(node)) fromMap.set(k.toLowerCase(), layer.label);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
function isThenable(v) {
|
|
586
|
-
return !!v && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
|
|
587
|
-
}
|
|
588
|
-
function asProvider(src) {
|
|
589
|
-
if (!("provider" in src || "load" in src)) return null;
|
|
590
|
-
const prov = "provider" in src ? src.provider : src;
|
|
591
|
-
if (!prov || typeof prov.load !== "function") return null;
|
|
592
|
-
return { load: prov.load, name: typeof prov.name === "string" ? prov.name : "<provider>" };
|
|
593
|
-
}
|
|
594
|
-
function timeoutError(name) {
|
|
595
|
-
return new ConfigError([{ path: name, from: name, message: "E_TIMEOUT: provider load timed out" }], "E_TIMEOUT");
|
|
596
|
-
}
|
|
597
|
-
function collectSources(opts, envSnapshot) {
|
|
598
|
-
const sources = opts.sources ?? [".env", "env"];
|
|
599
|
-
const prefix = opts.prefix;
|
|
600
|
-
const layers = [];
|
|
601
|
-
const fromMap = /* @__PURE__ */ new Map();
|
|
602
|
-
const maxBytes = opts.maxBytes ?? 2 * 1024 * 1024;
|
|
603
|
-
const push = (layer) => {
|
|
604
|
-
layers.push(layer);
|
|
605
|
-
recordLayerFrom(fromMap, layer);
|
|
606
|
-
};
|
|
607
|
-
const parseEnv = (text, label) => {
|
|
608
|
-
try {
|
|
609
|
-
return parseEnvText(text);
|
|
610
|
-
} catch (e) {
|
|
611
|
-
const err = e;
|
|
612
|
-
throw new ConfigError([{ path: label, from: label, message: err.message }], "E_PARSE");
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
for (const src of sources) {
|
|
616
|
-
if (src === "env") {
|
|
617
|
-
push(flatToLayer({ ...envSnapshot }, "env", void 0, prefix, opts, false));
|
|
618
|
-
} else if (src === ".env" || src === ".env.local") {
|
|
619
|
-
const text = readFileSyncSafe(src, maxBytes);
|
|
620
|
-
if (text === void 0) continue;
|
|
621
|
-
push(flatToLayer(parseEnv(text, src), src, void 0, prefix, opts, false));
|
|
622
|
-
} else if (typeof src === "string") {
|
|
623
|
-
const text = readFileSyncSafe(src, maxBytes);
|
|
624
|
-
if (text === void 0) continue;
|
|
625
|
-
const lower = src.toLowerCase();
|
|
626
|
-
if (lower.endsWith(".json") || lower.endsWith(".yaml") || lower.endsWith(".yml") || lower.endsWith(".toml")) {
|
|
627
|
-
push(structuredToLayer(parseStructuredFile(src, text), src, prefix));
|
|
628
|
-
} else {
|
|
629
|
-
push(flatToLayer(parseEnv(text, src), src, void 0, prefix, opts, false));
|
|
630
|
-
}
|
|
631
|
-
} else if (src && typeof src === "object" && "text" in src) {
|
|
632
|
-
push(flatToLayer(parseEnv(src.text, "text"), "text", void 0, prefix, opts, false));
|
|
633
|
-
} else if (src && typeof src === "object" && "map" in src) {
|
|
634
|
-
const m = src;
|
|
635
|
-
push(flatToLayer(m.map ?? {}, "overrides", m.prefix, prefix, opts, false));
|
|
636
|
-
} else if (src && typeof src === "object" && "file" in src) {
|
|
637
|
-
const p = src.file;
|
|
638
|
-
const text = readFileSyncSafe(p, maxBytes);
|
|
639
|
-
if (text === void 0) continue;
|
|
640
|
-
const lower = p.toLowerCase();
|
|
641
|
-
if (lower.endsWith(".json") || lower.endsWith(".yaml") || lower.endsWith(".yml") || lower.endsWith(".toml")) {
|
|
642
|
-
push(structuredToLayer(parseStructuredFile(p, text), p, prefix));
|
|
643
|
-
} else {
|
|
644
|
-
push(flatToLayer(parseEnv(text, p), p, void 0, prefix, opts, false));
|
|
645
|
-
}
|
|
646
|
-
} else if (src && typeof src === "object" && "dir" in src) {
|
|
647
|
-
continue;
|
|
648
|
-
} else if (src && typeof src === "object") {
|
|
649
|
-
const prov = asProvider(src);
|
|
650
|
-
if (prov) {
|
|
651
|
-
const srcPrefix = "prefix" in src ? src.prefix : void 0;
|
|
652
|
-
const loaded = prov.load();
|
|
653
|
-
if (isThenable(loaded)) {
|
|
654
|
-
throw new ConfigError(
|
|
655
|
-
[{ path: "<provider>", from: prov.name, message: "USE_ASYNC: provider is async, use settingsAsync()" }],
|
|
656
|
-
"USE_ASYNC"
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
push(flatToLayer(loaded ?? {}, prov.name, srcPrefix, prefix, opts, !opts.expandSecrets));
|
|
660
|
-
} else {
|
|
661
|
-
push(flatToLayer(src, "overrides", void 0, prefix, opts, false));
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return { layers, fromMap };
|
|
666
|
-
}
|
|
667
|
-
async function collectSourcesAsync(opts, envSnapshot) {
|
|
668
|
-
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
669
|
-
const needsAsync = (opts.sources ?? []).some((s) => s && typeof s === "object" && asProvider(s) !== null);
|
|
670
|
-
if (!needsAsync) return collectSources(opts, envSnapshot);
|
|
671
|
-
const sources = opts.sources;
|
|
672
|
-
const prefix = opts.prefix;
|
|
673
|
-
const layers = [];
|
|
674
|
-
const fromMap = /* @__PURE__ */ new Map();
|
|
675
|
-
const maxBytes = opts.maxBytes ?? 2 * 1024 * 1024;
|
|
676
|
-
const push = (layer) => {
|
|
677
|
-
layers.push(layer);
|
|
678
|
-
recordLayerFrom(fromMap, layer);
|
|
679
|
-
};
|
|
680
|
-
const parseEnv = (text, label) => {
|
|
681
|
-
try {
|
|
682
|
-
return parseEnvText(text);
|
|
683
|
-
} catch (e) {
|
|
684
|
-
const err = e;
|
|
685
|
-
throw new ConfigError([{ path: label, from: label, message: err.message }], "E_PARSE");
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
const withTimeout = async (name, fn) => {
|
|
689
|
-
let t;
|
|
690
|
-
const timeout = new Promise((_, rej) => {
|
|
691
|
-
t = setTimeout(() => rej(timeoutError(name)), timeoutMs);
|
|
692
|
-
});
|
|
693
|
-
const winner = await Promise.race([Promise.resolve().then(fn), timeout]).catch((e) => {
|
|
694
|
-
clearTimeout(t);
|
|
695
|
-
if (e instanceof ConfigError) throw e;
|
|
696
|
-
const err = e;
|
|
697
|
-
throw new ConfigError(
|
|
698
|
-
[{ path: name, from: name, message: `E_PROVIDER: ${err?.message ?? String(e)}` }],
|
|
699
|
-
"E_PROVIDER"
|
|
700
|
-
);
|
|
701
|
-
});
|
|
702
|
-
clearTimeout(t);
|
|
703
|
-
return winner;
|
|
704
|
-
};
|
|
705
|
-
for (const src of sources) {
|
|
706
|
-
if (src === "env") {
|
|
707
|
-
push(flatToLayer({ ...envSnapshot }, "env", void 0, prefix, opts, false));
|
|
708
|
-
} else if (src === ".env" || src === ".env.local") {
|
|
709
|
-
const text = readFileSyncSafe(src, maxBytes);
|
|
710
|
-
if (text === void 0) continue;
|
|
711
|
-
push(flatToLayer(parseEnv(text, src), src, void 0, prefix, opts, false));
|
|
712
|
-
} else if (typeof src === "string") {
|
|
713
|
-
const text = readFileSyncSafe(src, maxBytes);
|
|
714
|
-
if (text === void 0) continue;
|
|
715
|
-
const lower = src.toLowerCase();
|
|
716
|
-
if (lower.endsWith(".json") || lower.endsWith(".yaml") || lower.endsWith(".yml") || lower.endsWith(".toml")) {
|
|
717
|
-
push(structuredToLayer(parseStructuredFile(src, text), src, prefix));
|
|
718
|
-
} else {
|
|
719
|
-
push(flatToLayer(parseEnv(text, src), src, void 0, prefix, opts, false));
|
|
720
|
-
}
|
|
721
|
-
} else if (src && typeof src === "object" && "text" in src) {
|
|
722
|
-
push(flatToLayer(parseEnv(src.text, "text"), "text", void 0, prefix, opts, false));
|
|
723
|
-
} else if (src && typeof src === "object" && "map" in src) {
|
|
724
|
-
const m = src;
|
|
725
|
-
push(flatToLayer(m.map ?? {}, "overrides", m.prefix, prefix, opts, false));
|
|
726
|
-
} else if (src && typeof src === "object" && "file" in src) {
|
|
727
|
-
const p = src.file;
|
|
728
|
-
const text = readFileSyncSafe(p, maxBytes);
|
|
729
|
-
if (text === void 0) continue;
|
|
730
|
-
const lower = p.toLowerCase();
|
|
731
|
-
if (lower.endsWith(".json") || lower.endsWith(".yaml") || lower.endsWith(".yml") || lower.endsWith(".toml")) {
|
|
732
|
-
push(structuredToLayer(parseStructuredFile(p, text), p, prefix));
|
|
733
|
-
} else {
|
|
734
|
-
push(flatToLayer(parseEnv(text, p), p, void 0, prefix, opts, false));
|
|
735
|
-
}
|
|
736
|
-
} else if (src && typeof src === "object" && "dir" in src) {
|
|
737
|
-
continue;
|
|
738
|
-
} else if (src && typeof src === "object") {
|
|
739
|
-
const prov = asProvider(src);
|
|
740
|
-
if (prov) {
|
|
741
|
-
const srcPrefix = "prefix" in src ? src.prefix : void 0;
|
|
742
|
-
const loaded = await withTimeout(prov.name, () => prov.load());
|
|
743
|
-
push(flatToLayer(loaded ?? {}, prov.name, srcPrefix, prefix, opts, !opts.expandSecrets));
|
|
744
|
-
} else {
|
|
745
|
-
push(flatToLayer(src, "overrides", void 0, prefix, opts, false));
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
return { layers, fromMap };
|
|
750
|
-
}
|
|
751
|
-
function buildConfigObject(opts, envSnapshot) {
|
|
752
|
-
const { layers, fromMap } = collectSources(opts, envSnapshot);
|
|
753
|
-
const arrayStrategy = opts.arrayStrategy ?? "replace";
|
|
754
|
-
let merged = {};
|
|
755
|
-
for (const layer of layers) {
|
|
756
|
-
merged = deepMerge(merged, layer.node, arrayStrategy);
|
|
757
|
-
}
|
|
758
|
-
const skipTops = new Set(
|
|
759
|
-
layers.filter((l) => l.skipExpand).flatMap((l) => Object.keys(l.node)).map((k) => k.toLowerCase())
|
|
760
|
-
);
|
|
761
|
-
if (opts.expand !== false) {
|
|
762
|
-
merged = expandDeepStrings(merged, makeLookup(merged, envSnapshot), {
|
|
763
|
-
allowUnresolved: opts.allowUnresolved,
|
|
764
|
-
fromMap,
|
|
765
|
-
skipTops
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
if (opts.coerce !== false) {
|
|
769
|
-
merged = coerceDeep(merged);
|
|
770
|
-
}
|
|
771
|
-
const fromFor = (path) => {
|
|
772
|
-
const top = path.split(".")[0].toLowerCase();
|
|
773
|
-
if (top === "<root>") return "schema";
|
|
774
|
-
return fromMap.get(top) ?? "defaults";
|
|
775
|
-
};
|
|
776
|
-
return { input: merged, fromFor };
|
|
777
|
-
}
|
|
778
|
-
function makeLookup(merged, envSnapshot) {
|
|
779
|
-
const rec = merged && typeof merged === "object" && !Array.isArray(merged) ? merged : {};
|
|
780
|
-
const ciKeys = new Map(Object.keys(rec).map((k) => [k.toLowerCase(), k]));
|
|
781
|
-
return (name) => {
|
|
782
|
-
const hit = ciKeys.get(name.toLowerCase());
|
|
783
|
-
if (hit !== void 0) {
|
|
784
|
-
const direct = rec[hit];
|
|
785
|
-
if (typeof direct === "string") return direct;
|
|
786
|
-
if (direct !== void 0 && direct !== null) return String(direct);
|
|
787
|
-
}
|
|
788
|
-
return envSnapshot[name] ?? envSnapshot[name.toUpperCase()] ?? envSnapshot[name.toLowerCase()];
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
function expandDeepStrings(obj, lookup, ctx, path = []) {
|
|
792
|
-
if (typeof obj === "string") {
|
|
793
|
-
const top = (path[0] ?? "<root>").toLowerCase();
|
|
794
|
-
return expandLeaf(obj, lookup, { ...ctx, from: ctx.fromMap.get(top) ?? "config" }, path.map((p) => p.toLowerCase()));
|
|
795
|
-
}
|
|
796
|
-
if (Array.isArray(obj)) return obj.map((x, i) => expandDeepStrings(x, lookup, ctx, [...path, String(i)]));
|
|
797
|
-
if (obj && typeof obj === "object") {
|
|
798
|
-
if (path.length === 0) {
|
|
799
|
-
const o2 = {};
|
|
800
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
801
|
-
o2[k] = ctx.skipTops.has(k.toLowerCase()) ? v : expandDeepStrings(v, lookup, ctx, [k]);
|
|
802
|
-
}
|
|
803
|
-
return o2;
|
|
804
|
-
}
|
|
805
|
-
const o = {};
|
|
806
|
-
for (const [k, v] of Object.entries(obj)) o[k] = expandDeepStrings(v, lookup, ctx, [...path, k]);
|
|
807
|
-
return o;
|
|
808
|
-
}
|
|
809
|
-
return obj;
|
|
810
|
-
}
|
|
811
|
-
function expandLeaf(value, lookup, ctx, stack) {
|
|
812
|
-
return expandValue(
|
|
813
|
-
value,
|
|
814
|
-
(name) => {
|
|
815
|
-
const lower = name.toLowerCase();
|
|
816
|
-
if (stack.includes(lower)) {
|
|
817
|
-
throw new ConfigError(
|
|
818
|
-
[{ path: name, from: ctx.from, message: `E_CIRCULAR: $${[...stack, name].join("->$")}` }],
|
|
819
|
-
"E_CIRCULAR"
|
|
820
|
-
);
|
|
821
|
-
}
|
|
822
|
-
const raw = lookup(name);
|
|
823
|
-
if (raw === void 0) return void 0;
|
|
824
|
-
if (raw.includes("$")) return expandLeaf(raw, lookup, ctx, [...stack, lower]);
|
|
825
|
-
return raw;
|
|
826
|
-
},
|
|
827
|
-
{ allowUnresolved: ctx.allowUnresolved, from: ctx.from }
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
function collectLeafPaths(v, prefix = "") {
|
|
831
|
-
if (Array.isArray(v)) {
|
|
832
|
-
const out = [];
|
|
833
|
-
v.forEach((x, i) => out.push(...collectLeafPaths(x, prefix ? `${prefix}.${i}` : `${i}`)));
|
|
834
|
-
return out;
|
|
835
|
-
}
|
|
836
|
-
if (isPlainObject(v)) {
|
|
837
|
-
const out = [];
|
|
838
|
-
for (const [k, val] of Object.entries(v)) {
|
|
839
|
-
const p = prefix ? `${prefix}.${k.toLowerCase()}` : k.toLowerCase();
|
|
840
|
-
if (isPlainObject(val) || Array.isArray(val)) out.push(...collectLeafPaths(val, p));
|
|
841
|
-
else out.push(p);
|
|
842
|
-
}
|
|
843
|
-
return out;
|
|
844
|
-
}
|
|
845
|
-
return prefix ? [prefix.toLowerCase()] : [];
|
|
846
|
-
}
|
|
847
|
-
function applyUnknownKeys(input, value, mode) {
|
|
848
|
-
if (mode === "strip" || mode === void 0) return value;
|
|
849
|
-
if (mode === "preserve") {
|
|
850
|
-
if (isPlainObject(input) && isPlainObject(value)) return deepMerge(input, value, "replace");
|
|
851
|
-
return value;
|
|
852
|
-
}
|
|
853
|
-
const inputPaths = new Set(collectLeafPaths(input));
|
|
854
|
-
const valuePaths = new Set(collectLeafPaths(value));
|
|
855
|
-
const unknown = [...inputPaths].filter((p) => !valuePaths.has(p));
|
|
856
|
-
if (unknown.length > 0) {
|
|
857
|
-
throw new ConfigError(
|
|
858
|
-
unknown.map((p) => ({ path: p, from: "schema", message: "E_UNKNOWN_KEY: unknown key (unknownKeys=reject)" })),
|
|
859
|
-
"E_UNKNOWN_KEY"
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
return value;
|
|
863
|
-
}
|
|
864
|
-
function freezeDeep(v) {
|
|
865
|
-
if (Array.isArray(v)) {
|
|
866
|
-
for (const x of v) freezeDeep(x);
|
|
867
|
-
return Object.freeze(v);
|
|
868
|
-
}
|
|
869
|
-
if (v && typeof v === "object") {
|
|
870
|
-
for (const k of Object.keys(v)) freezeDeep(v[k]);
|
|
871
|
-
return Object.freeze(v);
|
|
872
|
-
}
|
|
873
|
-
return v;
|
|
874
|
-
}
|
|
875
|
-
function settings(opts) {
|
|
876
|
-
const envSnapshot = getEnvSnapshot(opts.env);
|
|
877
|
-
const { input, fromFor } = buildConfigObject(opts, envSnapshot);
|
|
878
|
-
const r = validateStandard(opts.schema, input);
|
|
879
|
-
if ("promise" in r) {
|
|
880
|
-
throw new ConfigError(
|
|
881
|
-
[{ path: "<root>", from: "schema", message: "USE_ASYNC: schema is async, use settingsAsync()" }],
|
|
882
|
-
"USE_ASYNC"
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
|
-
if ("issues" in r) {
|
|
886
|
-
throw new ConfigError(
|
|
887
|
-
r.issues.map((i) => ({ path: i.path, from: fromFor(i.path), message: maskIfSecret(i.path, i.message, input) }))
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
|
-
const value = r.value;
|
|
891
|
-
const final = applyUnknownKeys(input, value, opts.unknownKeys ?? "strip");
|
|
892
|
-
return opts.freeze === false ? final : freezeDeep(final);
|
|
893
|
-
}
|
|
894
|
-
async function settingsAsync(opts) {
|
|
895
|
-
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
896
|
-
const envSnapshot = getEnvSnapshot(opts.env);
|
|
897
|
-
const { input, fromFor } = await buildConfigObjectAsync(opts, envSnapshot);
|
|
898
|
-
const r = validateStandard(opts.schema, input);
|
|
899
|
-
if ("promise" in r) {
|
|
900
|
-
const raced = await Promise.race([
|
|
901
|
-
r.promise,
|
|
902
|
-
new Promise((_, rej) => setTimeout(() => rej(new ConfigError([{ path: "<root>", from: "schema", message: "E_TIMEOUT" }], "E_TIMEOUT")), timeoutMs))
|
|
903
|
-
]);
|
|
904
|
-
if (!raced.ok) {
|
|
905
|
-
throw new ConfigError(raced.issues.map((i) => ({ path: i.path, from: fromFor(i.path), message: maskIfSecret(i.path, i.message, input) })));
|
|
906
|
-
}
|
|
907
|
-
const value2 = raced.value;
|
|
908
|
-
const final2 = applyUnknownKeys(input, value2, opts.unknownKeys ?? "strip");
|
|
909
|
-
return opts.freeze === false ? final2 : freezeDeep(final2);
|
|
910
|
-
}
|
|
911
|
-
if ("issues" in r) {
|
|
912
|
-
throw new ConfigError(r.issues.map((i) => ({ path: i.path, from: fromFor(i.path), message: maskIfSecret(i.path, i.message, input) })));
|
|
913
|
-
}
|
|
914
|
-
const value = r.value;
|
|
915
|
-
const final = applyUnknownKeys(input, value, opts.unknownKeys ?? "strip");
|
|
916
|
-
return opts.freeze === false ? final : freezeDeep(final);
|
|
917
|
-
}
|
|
918
|
-
async function buildConfigObjectAsync(opts, envSnapshot) {
|
|
919
|
-
const { layers, fromMap } = await collectSourcesAsync(opts, envSnapshot);
|
|
920
|
-
const arrayStrategy = opts.arrayStrategy ?? "replace";
|
|
921
|
-
let merged = {};
|
|
922
|
-
for (const layer of layers) {
|
|
923
|
-
merged = deepMerge(merged, layer.node, arrayStrategy);
|
|
924
|
-
}
|
|
925
|
-
const skipTops = new Set(
|
|
926
|
-
layers.filter((l) => l.skipExpand).flatMap((l) => Object.keys(l.node)).map((k) => k.toLowerCase())
|
|
927
|
-
);
|
|
928
|
-
if (opts.expand !== false) {
|
|
929
|
-
merged = expandDeepStrings(merged, makeLookup(merged, envSnapshot), {
|
|
930
|
-
allowUnresolved: opts.allowUnresolved,
|
|
931
|
-
fromMap,
|
|
932
|
-
skipTops
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
if (opts.coerce !== false) merged = coerceDeep(merged);
|
|
936
|
-
const fromFor = (path) => {
|
|
937
|
-
const top = path.split(".")[0].toLowerCase();
|
|
938
|
-
if (top === "<root>") return "schema";
|
|
939
|
-
return fromMap.get(top) ?? "defaults";
|
|
940
|
-
};
|
|
941
|
-
return { input: merged, fromFor };
|
|
942
|
-
}
|
|
943
|
-
function getLeafByPath(input, path) {
|
|
944
|
-
const parts = path.split(".");
|
|
945
|
-
let cur = input;
|
|
946
|
-
for (const part of parts) {
|
|
947
|
-
if (cur === null || typeof cur !== "object") return void 0;
|
|
948
|
-
if (Array.isArray(cur)) {
|
|
949
|
-
const m = /\[(\d+)\]/.exec(part);
|
|
950
|
-
const idx = m ? Number(m[1]) : Number(part);
|
|
951
|
-
if (!Number.isInteger(idx)) return void 0;
|
|
952
|
-
cur = cur[idx];
|
|
953
|
-
continue;
|
|
954
|
-
}
|
|
955
|
-
const rec = cur;
|
|
956
|
-
const found = Object.keys(rec).find((k) => k.toLowerCase() === part.toLowerCase());
|
|
957
|
-
if (!found) return void 0;
|
|
958
|
-
cur = rec[found];
|
|
959
|
-
}
|
|
960
|
-
return cur;
|
|
961
|
-
}
|
|
962
|
-
function base64Of(s) {
|
|
963
|
-
try {
|
|
964
|
-
const g = globalThis;
|
|
965
|
-
if (g.btoa) return g.btoa(s);
|
|
966
|
-
if (g.Buffer) return g.Buffer.from(s, "utf8").toString("base64");
|
|
967
|
-
} catch {
|
|
968
|
-
}
|
|
969
|
-
return null;
|
|
970
|
-
}
|
|
971
|
-
function maskIfSecret(path, message, input) {
|
|
972
|
-
if (!isSecretKey(path)) return message;
|
|
973
|
-
let out = message;
|
|
974
|
-
const leaf = getLeafByPath(input, path);
|
|
975
|
-
if (typeof leaf === "string" && leaf.length > 0) {
|
|
976
|
-
const redacted = redactValue(path, leaf);
|
|
977
|
-
out = out.split(leaf).join(redacted);
|
|
978
|
-
const b64 = base64Of(leaf);
|
|
979
|
-
if (b64 && b64.length > 8) out = out.split(b64).join(redacted);
|
|
980
|
-
}
|
|
981
|
-
return `${out} (redacted)`;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
export { pickPublic, settings, settingsAsync, viteSettings, withOverrides };
|