@meltstudio/config-loader 3.7.0 → 4.0.3
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 +38 -3
- package/dist/index.d.mts +409 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1579 -0
- package/package.json +16 -2
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1579 @@
|
|
|
1
|
+
// src/settings.ts
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var ConfigLoadError = class extends Error {
|
|
6
|
+
/** All validation errors that caused the load to fail. */
|
|
7
|
+
errors;
|
|
8
|
+
/** Non-fatal warnings collected during loading. */
|
|
9
|
+
warnings;
|
|
10
|
+
constructor(errors, warnings) {
|
|
11
|
+
const count = errors.length;
|
|
12
|
+
const summary = `Configuration loading failed with ${count} error${count === 1 ? "" : "s"}`;
|
|
13
|
+
const details = errors.slice(0, 10).map((e, i) => ` ${i + 1}. ${e.message}`).join("\n");
|
|
14
|
+
const message = count > 0 ? `${summary}:
|
|
15
|
+
${details}` : (
|
|
16
|
+
/* istanbul ignore next */
|
|
17
|
+
summary
|
|
18
|
+
);
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "ConfigLoadError";
|
|
21
|
+
this.errors = errors;
|
|
22
|
+
this.warnings = warnings;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var ConfigFileError = class extends ConfigLoadError {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super([{ message, kind: "file_validation" }], []);
|
|
28
|
+
this.name = "ConfigFileError";
|
|
29
|
+
this.message = message;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/nodes/configNode.ts
|
|
34
|
+
var ConfigNode = class {
|
|
35
|
+
value;
|
|
36
|
+
path;
|
|
37
|
+
sourceType;
|
|
38
|
+
file;
|
|
39
|
+
variableName;
|
|
40
|
+
argName;
|
|
41
|
+
line;
|
|
42
|
+
column;
|
|
43
|
+
sensitive;
|
|
44
|
+
constructor(value, path2, sourceType, file, variableName, argName, line = null, column = null, sensitive = false) {
|
|
45
|
+
this.value = value;
|
|
46
|
+
this.path = path2;
|
|
47
|
+
this.sourceType = sourceType;
|
|
48
|
+
this.file = file;
|
|
49
|
+
this.variableName = variableName;
|
|
50
|
+
this.argName = argName;
|
|
51
|
+
this.line = line;
|
|
52
|
+
this.column = column;
|
|
53
|
+
this.sensitive = sensitive;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var configNode_default = ConfigNode;
|
|
57
|
+
|
|
58
|
+
// src/nodes/configNodeArray.ts
|
|
59
|
+
var ConfigNodeArray = class {
|
|
60
|
+
arrayValues;
|
|
61
|
+
constructor(arrayValues) {
|
|
62
|
+
this.arrayValues = arrayValues;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var configNodeArray_default = ConfigNodeArray;
|
|
66
|
+
|
|
67
|
+
// src/types.ts
|
|
68
|
+
var InvalidValue = class {
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/option/arrayOption.ts
|
|
72
|
+
var ArrayValueContainer = class {
|
|
73
|
+
val;
|
|
74
|
+
item;
|
|
75
|
+
constructor(item, val) {
|
|
76
|
+
this.val = val;
|
|
77
|
+
this.item = item;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var arrayOption_default = ArrayValueContainer;
|
|
81
|
+
|
|
82
|
+
// src/fileLoader.ts
|
|
83
|
+
import * as fs from "fs";
|
|
84
|
+
import yaml from "js-yaml";
|
|
85
|
+
import SourceMap from "js-yaml-source-map";
|
|
86
|
+
import * as path from "path";
|
|
87
|
+
import { parse as parseToml } from "smol-toml";
|
|
88
|
+
var fileCache = /* @__PURE__ */ new Map();
|
|
89
|
+
var JsonSourceMap = class {
|
|
90
|
+
locations = /* @__PURE__ */ new Map();
|
|
91
|
+
constructor(content) {
|
|
92
|
+
this.buildMap(content);
|
|
93
|
+
}
|
|
94
|
+
buildMap(content, prefix = []) {
|
|
95
|
+
const lines = content.split("\n");
|
|
96
|
+
for (let i = 0; i < lines.length; i++) {
|
|
97
|
+
const line = lines[i];
|
|
98
|
+
const keyRegex = /^(\s*)"([^"]+)"\s*:/g;
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = keyRegex.exec(line)) !== null) {
|
|
101
|
+
const key = match[2];
|
|
102
|
+
const column = match[1].length + 1;
|
|
103
|
+
this.locations.set(key, {
|
|
104
|
+
line: i + 1,
|
|
105
|
+
column,
|
|
106
|
+
position: 0
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const data = JSON.parse(content);
|
|
112
|
+
this.walkObject(data, prefix, lines);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
walkObject(obj, prefix, lines) {
|
|
117
|
+
for (const key of Object.keys(obj)) {
|
|
118
|
+
const fullPath = [...prefix, key].join(".");
|
|
119
|
+
for (let i = 0; i < lines.length; i++) {
|
|
120
|
+
const line = lines[i];
|
|
121
|
+
const keyPattern = `"${key}"`;
|
|
122
|
+
const idx = line.indexOf(keyPattern);
|
|
123
|
+
if (idx !== -1) {
|
|
124
|
+
const afterKey = line.slice(idx + keyPattern.length).trim();
|
|
125
|
+
if (afterKey.startsWith(":")) {
|
|
126
|
+
this.locations.set(fullPath, {
|
|
127
|
+
line: i + 1,
|
|
128
|
+
column: idx + 1,
|
|
129
|
+
position: 0
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const val = obj[key];
|
|
136
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
137
|
+
this.walkObject(
|
|
138
|
+
val,
|
|
139
|
+
[...prefix, key],
|
|
140
|
+
lines
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
lookup(path2) {
|
|
146
|
+
const key = Array.isArray(path2) ? path2.join(".") : path2;
|
|
147
|
+
return this.locations.get(key);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
function escapeRegex(str) {
|
|
151
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
152
|
+
}
|
|
153
|
+
function walkTomlObject(obj, prefix, lines, locations) {
|
|
154
|
+
for (const key of Object.keys(obj)) {
|
|
155
|
+
const fullPath = [...prefix, key].join(".");
|
|
156
|
+
for (let i = 0; i < lines.length; i++) {
|
|
157
|
+
const line = lines[i];
|
|
158
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=`);
|
|
159
|
+
if (keyPattern.test(line)) {
|
|
160
|
+
const idx = line.indexOf(key);
|
|
161
|
+
locations.set(fullPath, {
|
|
162
|
+
line: i + 1,
|
|
163
|
+
column: idx + 1,
|
|
164
|
+
position: 0
|
|
165
|
+
});
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const val = obj[key];
|
|
170
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
171
|
+
walkTomlObject(
|
|
172
|
+
val,
|
|
173
|
+
[...prefix, key],
|
|
174
|
+
lines,
|
|
175
|
+
locations
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function buildTomlSourceMap(content, data) {
|
|
181
|
+
const locations = /* @__PURE__ */ new Map();
|
|
182
|
+
const lines = content.split("\n");
|
|
183
|
+
walkTomlObject(data, [], lines, locations);
|
|
184
|
+
return {
|
|
185
|
+
lookup(lookupPath) {
|
|
186
|
+
const key = Array.isArray(lookupPath) ? lookupPath.join(".") : lookupPath;
|
|
187
|
+
return locations.get(key);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function loadConfigFile(filePath) {
|
|
192
|
+
const cached = fileCache.get(filePath);
|
|
193
|
+
if (cached) return cached;
|
|
194
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
195
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
196
|
+
try {
|
|
197
|
+
if (ext === ".json") {
|
|
198
|
+
const result2 = {
|
|
199
|
+
data: JSON.parse(content),
|
|
200
|
+
sourceMap: new JsonSourceMap(content)
|
|
201
|
+
};
|
|
202
|
+
fileCache.set(filePath, result2);
|
|
203
|
+
return result2;
|
|
204
|
+
}
|
|
205
|
+
if (ext === ".toml") {
|
|
206
|
+
const data2 = parseToml(content);
|
|
207
|
+
const result2 = {
|
|
208
|
+
data: data2,
|
|
209
|
+
sourceMap: buildTomlSourceMap(content, data2)
|
|
210
|
+
};
|
|
211
|
+
fileCache.set(filePath, result2);
|
|
212
|
+
return result2;
|
|
213
|
+
}
|
|
214
|
+
const sourceMap = new SourceMap();
|
|
215
|
+
const data = yaml.load(content, { listener: sourceMap.listen() });
|
|
216
|
+
const result = { data, sourceMap };
|
|
217
|
+
fileCache.set(filePath, result);
|
|
218
|
+
return result;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
const message = err instanceof Error ? err.message : "Unknown parsing error";
|
|
221
|
+
throw new ConfigFileError(
|
|
222
|
+
`Failed to parse config file '${filePath}': ${message}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function clearFileCache() {
|
|
227
|
+
fileCache.clear();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/utils.ts
|
|
231
|
+
function valueIsInvalid(val) {
|
|
232
|
+
return val instanceof InvalidValue || val === null || val === void 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/option/base.ts
|
|
236
|
+
function valueToString(val) {
|
|
237
|
+
if (typeof val === "object" && val !== null) {
|
|
238
|
+
return JSON.stringify(val);
|
|
239
|
+
}
|
|
240
|
+
return String(val);
|
|
241
|
+
}
|
|
242
|
+
function lookupLocation(sourceMap, path2) {
|
|
243
|
+
if (!sourceMap) return null;
|
|
244
|
+
const loc = sourceMap.lookup(path2.map(String));
|
|
245
|
+
if (!loc) return null;
|
|
246
|
+
return { line: loc.line, column: loc.column };
|
|
247
|
+
}
|
|
248
|
+
function findEnvFileSource(envKey, currentValue, envFileResults) {
|
|
249
|
+
if (!envFileResults) return null;
|
|
250
|
+
for (let i = envFileResults.length - 1; i >= 0; i--) {
|
|
251
|
+
const result = envFileResults[i];
|
|
252
|
+
const entry = result.entries.get(envKey);
|
|
253
|
+
if (entry && entry.value === currentValue) {
|
|
254
|
+
return {
|
|
255
|
+
filePath: result.filePath,
|
|
256
|
+
line: entry.line,
|
|
257
|
+
column: entry.column
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
function checkNumberType(val, pathStr, sourceOfVal, errors) {
|
|
264
|
+
if (typeof val === "string") {
|
|
265
|
+
const parseVal = parseInt(val, 10);
|
|
266
|
+
if (Number.isNaN(parseVal)) {
|
|
267
|
+
errors?.errors.push({
|
|
268
|
+
message: `Cannot convert value '${val}' for '${pathStr}' to number in ${sourceOfVal}.`,
|
|
269
|
+
path: pathStr,
|
|
270
|
+
source: sourceOfVal,
|
|
271
|
+
kind: "type_conversion"
|
|
272
|
+
});
|
|
273
|
+
return new InvalidValue();
|
|
274
|
+
}
|
|
275
|
+
errors?.warnings.push(
|
|
276
|
+
`The option ${pathStr} is stated as a number but is provided as a string`
|
|
277
|
+
);
|
|
278
|
+
return parseVal;
|
|
279
|
+
}
|
|
280
|
+
errors?.errors.push({
|
|
281
|
+
message: `Invalid state. Invalid kind in ${sourceOfVal}`,
|
|
282
|
+
source: sourceOfVal,
|
|
283
|
+
kind: "invalid_state"
|
|
284
|
+
});
|
|
285
|
+
return new InvalidValue();
|
|
286
|
+
}
|
|
287
|
+
function formatFileLocation(file, loc) {
|
|
288
|
+
if (!loc) return file;
|
|
289
|
+
return `${file}:${loc.line}:${loc.column}`;
|
|
290
|
+
}
|
|
291
|
+
var OptionBase = class {
|
|
292
|
+
params;
|
|
293
|
+
constructor(params) {
|
|
294
|
+
this.params = params;
|
|
295
|
+
}
|
|
296
|
+
getValue(sourceFile, env, args, path2, defaultValues, objectFromArray, envFileResults, errors) {
|
|
297
|
+
const resolved = this.resolveValue(
|
|
298
|
+
sourceFile,
|
|
299
|
+
env,
|
|
300
|
+
args,
|
|
301
|
+
path2,
|
|
302
|
+
defaultValues,
|
|
303
|
+
objectFromArray,
|
|
304
|
+
envFileResults,
|
|
305
|
+
errors
|
|
306
|
+
);
|
|
307
|
+
if (resolved && this.params.sensitive) {
|
|
308
|
+
resolved.sensitive = true;
|
|
309
|
+
}
|
|
310
|
+
if (resolved && this.params.oneOf) {
|
|
311
|
+
const passed = this.runOneOfCheck(resolved, path2, errors);
|
|
312
|
+
if (!passed) return resolved;
|
|
313
|
+
}
|
|
314
|
+
if (resolved && this.params.validate) {
|
|
315
|
+
this.runValidation(resolved, path2, errors);
|
|
316
|
+
}
|
|
317
|
+
return resolved;
|
|
318
|
+
}
|
|
319
|
+
resolveValue(sourceFile, env, args, path2, defaultValues, objectFromArray, envFileResults, errors) {
|
|
320
|
+
const ident = path2.join(".");
|
|
321
|
+
if (this.params.cli && args) {
|
|
322
|
+
if (ident in args) {
|
|
323
|
+
return new configNode_default(
|
|
324
|
+
this.checkType(args[ident], path2, `CLI argument --${ident}`, errors),
|
|
325
|
+
ident,
|
|
326
|
+
"args",
|
|
327
|
+
null,
|
|
328
|
+
null,
|
|
329
|
+
ident
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (this.params.env && env) {
|
|
334
|
+
if (this.params.env in env) {
|
|
335
|
+
const val = env[this.params.env];
|
|
336
|
+
if (val !== void 0) {
|
|
337
|
+
const envFileSource = findEnvFileSource(
|
|
338
|
+
this.params.env,
|
|
339
|
+
val,
|
|
340
|
+
envFileResults
|
|
341
|
+
);
|
|
342
|
+
if (envFileSource) {
|
|
343
|
+
return new configNode_default(
|
|
344
|
+
this.checkType(
|
|
345
|
+
val,
|
|
346
|
+
path2,
|
|
347
|
+
`env file ${envFileSource.filePath} (${this.params.env})`,
|
|
348
|
+
errors
|
|
349
|
+
),
|
|
350
|
+
ident,
|
|
351
|
+
"envFile",
|
|
352
|
+
envFileSource.filePath,
|
|
353
|
+
this.params.env,
|
|
354
|
+
null,
|
|
355
|
+
envFileSource.line,
|
|
356
|
+
envFileSource.column
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return new configNode_default(
|
|
360
|
+
this.checkType(
|
|
361
|
+
val,
|
|
362
|
+
path2,
|
|
363
|
+
`environment variable ${this.params.env}`,
|
|
364
|
+
errors
|
|
365
|
+
),
|
|
366
|
+
ident,
|
|
367
|
+
"env",
|
|
368
|
+
null,
|
|
369
|
+
this.params.env,
|
|
370
|
+
null
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (typeof sourceFile === "string") {
|
|
376
|
+
const { data, sourceMap } = loadConfigFile(sourceFile);
|
|
377
|
+
const node = this.resolveFromFileData(
|
|
378
|
+
data || {},
|
|
379
|
+
sourceFile,
|
|
380
|
+
sourceMap,
|
|
381
|
+
path2,
|
|
382
|
+
ident,
|
|
383
|
+
errors
|
|
384
|
+
);
|
|
385
|
+
if (node) return node;
|
|
386
|
+
}
|
|
387
|
+
if (Array.isArray(sourceFile)) {
|
|
388
|
+
for (let index = 0; index < sourceFile.length; index += 1) {
|
|
389
|
+
const file = sourceFile[index];
|
|
390
|
+
const { data, sourceMap } = loadConfigFile(file);
|
|
391
|
+
const node = this.resolveFromFileData(
|
|
392
|
+
data || {},
|
|
393
|
+
file,
|
|
394
|
+
sourceMap,
|
|
395
|
+
path2,
|
|
396
|
+
ident,
|
|
397
|
+
errors
|
|
398
|
+
);
|
|
399
|
+
if (node) return node;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (objectFromArray) {
|
|
403
|
+
const node = this.resolveFromFileData(
|
|
404
|
+
objectFromArray.value,
|
|
405
|
+
objectFromArray.file,
|
|
406
|
+
objectFromArray.sourceMap ?? null,
|
|
407
|
+
path2,
|
|
408
|
+
ident,
|
|
409
|
+
errors
|
|
410
|
+
);
|
|
411
|
+
if (node) return node;
|
|
412
|
+
}
|
|
413
|
+
if (defaultValues) {
|
|
414
|
+
const val = this.findInObject(
|
|
415
|
+
defaultValues,
|
|
416
|
+
path2,
|
|
417
|
+
errors
|
|
418
|
+
);
|
|
419
|
+
if (val instanceof arrayOption_default) {
|
|
420
|
+
return new configNode_default(
|
|
421
|
+
this.checkType(val, path2, "default", errors),
|
|
422
|
+
ident,
|
|
423
|
+
"default",
|
|
424
|
+
null,
|
|
425
|
+
null,
|
|
426
|
+
null
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (!valueIsInvalid(val)) {
|
|
430
|
+
return new configNode_default(
|
|
431
|
+
this.checkType(val, path2, "default", errors),
|
|
432
|
+
ident,
|
|
433
|
+
"default",
|
|
434
|
+
null,
|
|
435
|
+
null,
|
|
436
|
+
null
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (this.params.defaultValue !== void 0) {
|
|
441
|
+
let defaultValue;
|
|
442
|
+
const rawDefault = this.params.defaultValue;
|
|
443
|
+
if (typeof rawDefault === "function") {
|
|
444
|
+
defaultValue = rawDefault();
|
|
445
|
+
} else {
|
|
446
|
+
defaultValue = rawDefault;
|
|
447
|
+
}
|
|
448
|
+
if (this.params.kind === "array" && Array.isArray(defaultValue)) {
|
|
449
|
+
defaultValue = this.buildArrayOption(defaultValue, errors);
|
|
450
|
+
}
|
|
451
|
+
if (!valueIsInvalid(defaultValue)) {
|
|
452
|
+
return new configNode_default(
|
|
453
|
+
this.checkType(defaultValue, path2, "default", errors),
|
|
454
|
+
ident,
|
|
455
|
+
"default",
|
|
456
|
+
null,
|
|
457
|
+
null,
|
|
458
|
+
null
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (this.params.required) {
|
|
463
|
+
const hints = [];
|
|
464
|
+
if (this.params.env)
|
|
465
|
+
hints.push(`environment variable ${this.params.env}`);
|
|
466
|
+
if (this.params.cli) hints.push(`CLI argument --${ident}`);
|
|
467
|
+
hints.push(`config file key: ${ident}`);
|
|
468
|
+
const hint = hints.join(", ");
|
|
469
|
+
errors?.errors.push({
|
|
470
|
+
message: `Required option '${ident}' is missing. Set it via ${hint}.`,
|
|
471
|
+
path: ident,
|
|
472
|
+
kind: "required"
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
runOneOfCheck(node, path2, errors) {
|
|
478
|
+
const allowed = this.params.oneOf;
|
|
479
|
+
if (!allowed) return true;
|
|
480
|
+
const value = node.value;
|
|
481
|
+
if (valueIsInvalid(value)) return true;
|
|
482
|
+
if (!allowed.includes(value)) {
|
|
483
|
+
const ident = path2.join(".");
|
|
484
|
+
const source = node.file ?? node.variableName ?? node.argName ?? node.sourceType;
|
|
485
|
+
const allowedStr = allowed.map((v) => `'${String(v)}'`).join(", ");
|
|
486
|
+
errors?.errors.push({
|
|
487
|
+
message: `Value '${typeof value === "object" ? JSON.stringify(value) : String(value)}' for '${ident}' is not one of: ${allowedStr}.`,
|
|
488
|
+
path: ident,
|
|
489
|
+
source,
|
|
490
|
+
kind: "validation",
|
|
491
|
+
line: node.line ?? void 0,
|
|
492
|
+
column: node.column ?? void 0
|
|
493
|
+
});
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
runValidation(node, path2, errors) {
|
|
499
|
+
const validator = this.params.validate;
|
|
500
|
+
if (!validator) return;
|
|
501
|
+
const value = node.value;
|
|
502
|
+
if (valueIsInvalid(value)) return;
|
|
503
|
+
const result = validator["~standard"].validate(value);
|
|
504
|
+
if (result instanceof Promise) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
"Async validators are not supported. The validate function must return a synchronous result."
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
if ("issues" in result && result.issues) {
|
|
510
|
+
const ident = path2.join(".");
|
|
511
|
+
const source = node.file ?? node.variableName ?? node.argName ?? node.sourceType;
|
|
512
|
+
for (const issue of result.issues) {
|
|
513
|
+
const sourceLabel = source !== node.sourceType ? ` (source: ${source})` : "";
|
|
514
|
+
errors?.errors.push({
|
|
515
|
+
message: `Validation failed for '${ident}'${sourceLabel}: ${issue.message}`,
|
|
516
|
+
path: ident,
|
|
517
|
+
source,
|
|
518
|
+
kind: "validation",
|
|
519
|
+
line: node.line ?? void 0,
|
|
520
|
+
column: node.column ?? void 0
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
resolveFromFileData(data, file, sourceMap, path2, ident, errors) {
|
|
526
|
+
const val = this.findInObject(data, path2, errors);
|
|
527
|
+
const loc = lookupLocation(sourceMap, path2);
|
|
528
|
+
if (val instanceof arrayOption_default || !valueIsInvalid(val)) {
|
|
529
|
+
return new configNode_default(
|
|
530
|
+
this.checkType(val, path2, formatFileLocation(file, loc), errors),
|
|
531
|
+
ident,
|
|
532
|
+
"file",
|
|
533
|
+
file,
|
|
534
|
+
null,
|
|
535
|
+
null,
|
|
536
|
+
loc?.line ?? null,
|
|
537
|
+
loc?.column ?? null
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
checkType(val, path2, sourceOfVal, errors) {
|
|
543
|
+
const ident = path2.join(".");
|
|
544
|
+
if (valueIsInvalid(val)) {
|
|
545
|
+
errors?.errors.push({
|
|
546
|
+
message: `Invalid state. Invalid kind in ${sourceOfVal}`,
|
|
547
|
+
source: sourceOfVal,
|
|
548
|
+
kind: "invalid_state"
|
|
549
|
+
});
|
|
550
|
+
return val;
|
|
551
|
+
}
|
|
552
|
+
if (typeof val === this.params.kind) {
|
|
553
|
+
return val;
|
|
554
|
+
}
|
|
555
|
+
if (this.params.kind === "string") {
|
|
556
|
+
if (typeof val === "number") {
|
|
557
|
+
errors?.warnings.push(
|
|
558
|
+
`The option ${ident} is stated as a string but is provided as a number`
|
|
559
|
+
);
|
|
560
|
+
return val.toString();
|
|
561
|
+
}
|
|
562
|
+
if (typeof val === "boolean") {
|
|
563
|
+
errors?.warnings.push(
|
|
564
|
+
`The option ${ident} is stated as a string but is provided as a boolean`
|
|
565
|
+
);
|
|
566
|
+
return val.toString();
|
|
567
|
+
}
|
|
568
|
+
errors?.errors.push({
|
|
569
|
+
message: `Cannot convert value '${valueToString(
|
|
570
|
+
val
|
|
571
|
+
)}' for '${ident}' to string in ${sourceOfVal}.`,
|
|
572
|
+
path: ident,
|
|
573
|
+
source: sourceOfVal,
|
|
574
|
+
kind: "type_conversion"
|
|
575
|
+
});
|
|
576
|
+
return new InvalidValue();
|
|
577
|
+
}
|
|
578
|
+
if (this.params.kind === "boolean") {
|
|
579
|
+
if (typeof val !== "boolean" && typeof val !== "object") {
|
|
580
|
+
const normalized = typeof val === "string" ? val.toLowerCase() : val;
|
|
581
|
+
if ([1, "1", "true", "yes"].includes(normalized)) {
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
if ([0, "0", "false", "no"].includes(normalized)) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
errors?.errors.push({
|
|
589
|
+
message: `Cannot convert value '${valueToString(
|
|
590
|
+
val
|
|
591
|
+
)}' for '${ident}' to boolean in ${sourceOfVal}.`,
|
|
592
|
+
path: ident,
|
|
593
|
+
source: sourceOfVal,
|
|
594
|
+
kind: "type_conversion"
|
|
595
|
+
});
|
|
596
|
+
return new InvalidValue();
|
|
597
|
+
}
|
|
598
|
+
if (this.params.kind === "number") {
|
|
599
|
+
return checkNumberType(val, ident, sourceOfVal, errors);
|
|
600
|
+
}
|
|
601
|
+
errors?.errors.push({
|
|
602
|
+
message: `Invalid state. Invalid kind in ${sourceOfVal}`,
|
|
603
|
+
source: sourceOfVal,
|
|
604
|
+
kind: "invalid_state"
|
|
605
|
+
});
|
|
606
|
+
throw new Error(
|
|
607
|
+
"Invalid kind. Must be 'string', 'number', 'boolean' or 'array'"
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
findInObject(obj, path2, errors) {
|
|
611
|
+
if (path2.length > 1) {
|
|
612
|
+
const [child, ...rest] = path2;
|
|
613
|
+
const val = obj[child];
|
|
614
|
+
if (typeof val === "string") {
|
|
615
|
+
errors?.errors.push({
|
|
616
|
+
message: `Cannot traverse into '${path2.join(".")}': expected an object but found a string '${val}'`,
|
|
617
|
+
kind: "invalid_path"
|
|
618
|
+
});
|
|
619
|
+
return new InvalidValue();
|
|
620
|
+
}
|
|
621
|
+
if (typeof val === "number") {
|
|
622
|
+
errors?.errors.push({
|
|
623
|
+
message: `Cannot traverse into '${path2.join(".")}': expected an object but found a number '${val}'`,
|
|
624
|
+
kind: "invalid_path"
|
|
625
|
+
});
|
|
626
|
+
return new InvalidValue();
|
|
627
|
+
}
|
|
628
|
+
if (typeof val === "boolean") {
|
|
629
|
+
errors?.errors.push({
|
|
630
|
+
message: `Cannot traverse into '${path2.join(".")}': expected an object but found a boolean '${val.toString()}'`,
|
|
631
|
+
kind: "invalid_path"
|
|
632
|
+
});
|
|
633
|
+
return new InvalidValue();
|
|
634
|
+
}
|
|
635
|
+
if (Array.isArray(val)) {
|
|
636
|
+
errors?.errors.push({
|
|
637
|
+
message: `Cannot traverse into '${path2.join(".")}': expected an object but found an array '${valueToString(val)}'`,
|
|
638
|
+
kind: "invalid_path"
|
|
639
|
+
});
|
|
640
|
+
return new InvalidValue();
|
|
641
|
+
}
|
|
642
|
+
if (val === void 0) {
|
|
643
|
+
return new InvalidValue();
|
|
644
|
+
}
|
|
645
|
+
if (val === null) {
|
|
646
|
+
errors?.errors.push({
|
|
647
|
+
message: `Option '${path2.join(".")}' is null \u2014 expected an object to traverse into`,
|
|
648
|
+
kind: "null_value"
|
|
649
|
+
});
|
|
650
|
+
return new InvalidValue();
|
|
651
|
+
}
|
|
652
|
+
return this.findInObject(val, rest, errors);
|
|
653
|
+
}
|
|
654
|
+
if (path2.length === 1) {
|
|
655
|
+
const val = obj[path2[0]];
|
|
656
|
+
if (val === null) {
|
|
657
|
+
if (this.params.required) {
|
|
658
|
+
errors?.errors.push({
|
|
659
|
+
message: `Option '${path2.join(".")}' is null \u2014 expected a ${this.params.kind}`,
|
|
660
|
+
kind: "null_value"
|
|
661
|
+
});
|
|
662
|
+
} else {
|
|
663
|
+
errors?.warnings.push(
|
|
664
|
+
`Option '${path2.join(".")}' is null and will be treated as unset`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return new InvalidValue();
|
|
668
|
+
}
|
|
669
|
+
if (!Array.isArray(val) && typeof val === "object" && val || typeof val === "string" || typeof val === "number" || typeof val === "boolean" || typeof val === "undefined") {
|
|
670
|
+
return val;
|
|
671
|
+
}
|
|
672
|
+
if (Array.isArray(val)) {
|
|
673
|
+
return this.buildArrayOption(val, errors);
|
|
674
|
+
}
|
|
675
|
+
errors?.errors.push({
|
|
676
|
+
message: `Invalid path '${path2.join(".")}': ${typeof val}`,
|
|
677
|
+
kind: "invalid_path"
|
|
678
|
+
});
|
|
679
|
+
return new InvalidValue();
|
|
680
|
+
}
|
|
681
|
+
errors?.errors.push({
|
|
682
|
+
message: `Invalid path '${path2.join()}'`,
|
|
683
|
+
kind: "invalid_path"
|
|
684
|
+
});
|
|
685
|
+
return new InvalidValue();
|
|
686
|
+
}
|
|
687
|
+
// eslint-disable-next-line class-methods-use-this
|
|
688
|
+
buildArrayOption(_val, _errors) {
|
|
689
|
+
return new InvalidValue();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/option/object.ts
|
|
694
|
+
var ObjectOption = class extends OptionBase {
|
|
695
|
+
item;
|
|
696
|
+
constructor(params) {
|
|
697
|
+
if (!params.item) {
|
|
698
|
+
const hasOptionValues = Object.values(params).some(
|
|
699
|
+
(v) => v instanceof OptionBase
|
|
700
|
+
);
|
|
701
|
+
if (hasOptionValues) {
|
|
702
|
+
throw new Error(
|
|
703
|
+
"Invalid c.object() call: schema fields were passed directly instead of wrapped in { item: { ... } }. Use c.object({ item: { host: c.string() } }) instead of c.object({ host: c.string() })."
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
throw new Error(
|
|
707
|
+
"Invalid c.object() call: missing required 'item' property. Use c.object({ item: { host: c.string(), port: c.number() } })."
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
super({
|
|
711
|
+
kind: "object",
|
|
712
|
+
env: null,
|
|
713
|
+
cli: false,
|
|
714
|
+
help: "",
|
|
715
|
+
...params
|
|
716
|
+
});
|
|
717
|
+
this.item = params.item;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
// src/option/array.ts
|
|
722
|
+
var ArrayOption = class extends OptionBase {
|
|
723
|
+
item;
|
|
724
|
+
constructor(params) {
|
|
725
|
+
super({
|
|
726
|
+
kind: "array",
|
|
727
|
+
env: null,
|
|
728
|
+
cli: false,
|
|
729
|
+
help: "",
|
|
730
|
+
...params
|
|
731
|
+
});
|
|
732
|
+
this.item = params.item;
|
|
733
|
+
}
|
|
734
|
+
buildArrayOption(val, errors) {
|
|
735
|
+
if (this.item === null) {
|
|
736
|
+
errors?.errors.push({
|
|
737
|
+
message: `Array item cannot be null`,
|
|
738
|
+
kind: "invalid_state"
|
|
739
|
+
});
|
|
740
|
+
return new InvalidValue();
|
|
741
|
+
}
|
|
742
|
+
return new arrayOption_default(this.item, val);
|
|
743
|
+
}
|
|
744
|
+
checkType(val, path2, sourceOfVal, errors) {
|
|
745
|
+
if (val instanceof arrayOption_default) {
|
|
746
|
+
val.val.forEach((v, i) => {
|
|
747
|
+
if (this.item instanceof OptionBase && !(this.item instanceof ObjectOption)) {
|
|
748
|
+
this.item.checkType(v, [...path2, i], sourceOfVal, errors);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
return val;
|
|
752
|
+
}
|
|
753
|
+
errors?.errors.push({
|
|
754
|
+
message: `Invalid state. Invalid kind in ${sourceOfVal}`,
|
|
755
|
+
source: sourceOfVal,
|
|
756
|
+
kind: "invalid_state"
|
|
757
|
+
});
|
|
758
|
+
return new InvalidValue();
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/envFileLoader.ts
|
|
763
|
+
import * as fs2 from "fs";
|
|
764
|
+
var envFileCache = /* @__PURE__ */ new Map();
|
|
765
|
+
function loadEnvFile(filePath) {
|
|
766
|
+
const cached = envFileCache.get(filePath);
|
|
767
|
+
if (cached) return cached;
|
|
768
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
769
|
+
const entries = /* @__PURE__ */ new Map();
|
|
770
|
+
const lines = content.split("\n");
|
|
771
|
+
for (let i = 0; i < lines.length; i++) {
|
|
772
|
+
const raw = lines[i];
|
|
773
|
+
const trimmed = raw.trim();
|
|
774
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
775
|
+
const eqIndex = trimmed.indexOf("=");
|
|
776
|
+
if (eqIndex === -1) continue;
|
|
777
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
778
|
+
if (key === "") continue;
|
|
779
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
780
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
781
|
+
value = value.slice(1, -1);
|
|
782
|
+
}
|
|
783
|
+
const column = raw.indexOf(key) + 1;
|
|
784
|
+
entries.set(key, {
|
|
785
|
+
value,
|
|
786
|
+
line: i + 1,
|
|
787
|
+
column
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
const result = { entries, filePath };
|
|
791
|
+
envFileCache.set(filePath, result);
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
function clearEnvFileCache() {
|
|
795
|
+
envFileCache.clear();
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/option/errors.ts
|
|
799
|
+
var OptionErrors = class {
|
|
800
|
+
errors = [];
|
|
801
|
+
warnings = [];
|
|
802
|
+
clearAll() {
|
|
803
|
+
this.errors = [];
|
|
804
|
+
this.warnings = [];
|
|
805
|
+
clearFileCache();
|
|
806
|
+
clearEnvFileCache();
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/option/primitive.ts
|
|
811
|
+
var PrimitiveOption = class extends OptionBase {
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// src/sourceValidation.ts
|
|
815
|
+
import * as fs3 from "fs";
|
|
816
|
+
function validateFiles(files, dir) {
|
|
817
|
+
if (files && dir)
|
|
818
|
+
throw new ConfigFileError("Dir and files are specified, choose one");
|
|
819
|
+
let sourceFile = [];
|
|
820
|
+
if (files) {
|
|
821
|
+
if (Array.isArray(files)) {
|
|
822
|
+
const result = [];
|
|
823
|
+
files.forEach((file) => {
|
|
824
|
+
if (!fs3.existsSync(file)) {
|
|
825
|
+
throw new ConfigFileError(`Config file '${file}' does not exist`);
|
|
826
|
+
} else {
|
|
827
|
+
result.push(file);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
sourceFile = result;
|
|
831
|
+
} else {
|
|
832
|
+
if (!fs3.existsSync(files)) {
|
|
833
|
+
throw new ConfigFileError(`Config file '${files}' does not exist`);
|
|
834
|
+
}
|
|
835
|
+
sourceFile = files;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (dir) {
|
|
839
|
+
if (!(fs3.existsSync(dir) && fs3.lstatSync(dir).isDirectory())) {
|
|
840
|
+
throw new ConfigFileError(
|
|
841
|
+
`Config directory '${dir}' does not exist or is not a directory`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const filesInDirectory = fs3.readdirSync(dir).sort();
|
|
845
|
+
if (filesInDirectory.length === 0) {
|
|
846
|
+
throw new ConfigFileError(`Directory '${dir}' is empty`);
|
|
847
|
+
}
|
|
848
|
+
const result = [];
|
|
849
|
+
filesInDirectory.forEach((file) => {
|
|
850
|
+
result.push(`${dir}/${file}`);
|
|
851
|
+
});
|
|
852
|
+
sourceFile = result;
|
|
853
|
+
}
|
|
854
|
+
return sourceFile;
|
|
855
|
+
}
|
|
856
|
+
function loadEnvFiles(envFile, envData) {
|
|
857
|
+
const envFileResults = [];
|
|
858
|
+
if (!envFile) return { envFileResults, mergedEnvData: envData };
|
|
859
|
+
const envFiles = Array.isArray(envFile) ? envFile : [envFile];
|
|
860
|
+
for (const file of envFiles) {
|
|
861
|
+
if (!fs3.existsSync(file)) {
|
|
862
|
+
throw new ConfigFileError(`Invalid env file '${file}'`);
|
|
863
|
+
}
|
|
864
|
+
const result = loadEnvFile(file);
|
|
865
|
+
envFileResults.push(result);
|
|
866
|
+
}
|
|
867
|
+
const merged = {};
|
|
868
|
+
for (const result of envFileResults) {
|
|
869
|
+
for (const [key, entry] of result.entries) {
|
|
870
|
+
merged[key] = entry.value;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
for (const [key, value] of Object.entries(envData)) {
|
|
874
|
+
if (value !== void 0) {
|
|
875
|
+
merged[key] = value;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return { envFileResults, mergedEnvData: merged };
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/settings.ts
|
|
882
|
+
var Settings = class {
|
|
883
|
+
schema;
|
|
884
|
+
sources;
|
|
885
|
+
errors = new OptionErrors();
|
|
886
|
+
sourceFile = [];
|
|
887
|
+
argsData = {};
|
|
888
|
+
envData = {};
|
|
889
|
+
optionsTree = {};
|
|
890
|
+
defaultData = {};
|
|
891
|
+
envFileResults = [];
|
|
892
|
+
program;
|
|
893
|
+
constructor(schema2, sources) {
|
|
894
|
+
this.schema = schema2;
|
|
895
|
+
this.sources = sources;
|
|
896
|
+
this.program = new Command().allowUnknownOption(true).allowExcessArguments(true);
|
|
897
|
+
this.load();
|
|
898
|
+
}
|
|
899
|
+
validateAndLoadFiles() {
|
|
900
|
+
this.sourceFile = validateFiles(this.sources.files, this.sources.dir);
|
|
901
|
+
}
|
|
902
|
+
loadAndMergeEnvFiles() {
|
|
903
|
+
const { envFileResults, mergedEnvData } = loadEnvFiles(
|
|
904
|
+
this.sources.envFile,
|
|
905
|
+
this.envData
|
|
906
|
+
);
|
|
907
|
+
this.envFileResults = envFileResults;
|
|
908
|
+
this.envData = mergedEnvData;
|
|
909
|
+
}
|
|
910
|
+
checkEnvMappingsWithoutEnvLoading() {
|
|
911
|
+
if (this.sources.env || this.sources.envFile) return;
|
|
912
|
+
const envMappedOptions = [];
|
|
913
|
+
this.traverseOptions(this.schema, [], (node, path2) => {
|
|
914
|
+
if (node.params.env) {
|
|
915
|
+
envMappedOptions.push(path2.join("."));
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
if (envMappedOptions.length > 0) {
|
|
919
|
+
this.errors.warnings.push(
|
|
920
|
+
`Options [${envMappedOptions.join(", ")}] have env mappings but env loading is disabled. Set 'env: true' in load options to read from environment variables.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
load() {
|
|
925
|
+
this.validateAndLoadFiles();
|
|
926
|
+
this.checkEnvMappingsWithoutEnvLoading();
|
|
927
|
+
if (this.sources.env) {
|
|
928
|
+
this.envData = { ...process.env };
|
|
929
|
+
}
|
|
930
|
+
this.loadAndMergeEnvFiles();
|
|
931
|
+
if (this.sources.args) {
|
|
932
|
+
this.traverseOptions(this.schema, [], this.addArg.bind(this));
|
|
933
|
+
this.program.parse(process.argv);
|
|
934
|
+
this.argsData = this.program.opts();
|
|
935
|
+
}
|
|
936
|
+
if (this.sources.defaults) {
|
|
937
|
+
this.defaultData = this.sources.defaults;
|
|
938
|
+
}
|
|
939
|
+
this.traverseOptions(
|
|
940
|
+
this.schema,
|
|
941
|
+
[],
|
|
942
|
+
this.buildOption.bind(this, this.optionsTree, {
|
|
943
|
+
sourceFile: this.sourceFile,
|
|
944
|
+
envData: this.envData,
|
|
945
|
+
argsData: this.argsData,
|
|
946
|
+
defaultValue: this.defaultData,
|
|
947
|
+
envFileResults: this.envFileResults,
|
|
948
|
+
errors: this.errors
|
|
949
|
+
})
|
|
950
|
+
);
|
|
951
|
+
if (this.sources.strict && this.errors.warnings.length > 0) {
|
|
952
|
+
for (const warning of this.errors.warnings) {
|
|
953
|
+
this.errors.errors.push({ message: warning, kind: "strict" });
|
|
954
|
+
}
|
|
955
|
+
this.errors.warnings = [];
|
|
956
|
+
}
|
|
957
|
+
if (this.errors.errors.length > 0) {
|
|
958
|
+
throw new ConfigLoadError(
|
|
959
|
+
[...this.errors.errors],
|
|
960
|
+
[...this.errors.warnings]
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
traverseOptions(node, path2, callback) {
|
|
965
|
+
if (node instanceof ObjectOption) {
|
|
966
|
+
const item = node.item;
|
|
967
|
+
Object.keys(item).forEach((key) => {
|
|
968
|
+
this.traverseOptions(item[key], [...path2, key], callback);
|
|
969
|
+
});
|
|
970
|
+
} else if (node instanceof OptionBase) {
|
|
971
|
+
callback(node, path2);
|
|
972
|
+
} else {
|
|
973
|
+
Object.keys(node).forEach((key) => {
|
|
974
|
+
const val = node[key];
|
|
975
|
+
this.traverseOptions(val, [...path2, key], callback);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
buildOption(result, configData, node, path2) {
|
|
980
|
+
const {
|
|
981
|
+
sourceFile = [],
|
|
982
|
+
envData = {},
|
|
983
|
+
argsData = {},
|
|
984
|
+
defaultValue = {},
|
|
985
|
+
objectFromArray,
|
|
986
|
+
envFileResults,
|
|
987
|
+
errors
|
|
988
|
+
} = configData;
|
|
989
|
+
const value = node.getValue(
|
|
990
|
+
sourceFile,
|
|
991
|
+
envData,
|
|
992
|
+
argsData,
|
|
993
|
+
path2,
|
|
994
|
+
defaultValue,
|
|
995
|
+
objectFromArray,
|
|
996
|
+
envFileResults,
|
|
997
|
+
errors
|
|
998
|
+
);
|
|
999
|
+
if (value !== null) {
|
|
1000
|
+
this.setOption(result, path2, value);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
getValidatedArray(item, values, file) {
|
|
1004
|
+
if (item instanceof PrimitiveOption) {
|
|
1005
|
+
if (item.params.kind === "string") {
|
|
1006
|
+
return values.map((v) => v);
|
|
1007
|
+
}
|
|
1008
|
+
if (item.params.kind === "number") {
|
|
1009
|
+
return values.map((v) => parseInt(v, 10));
|
|
1010
|
+
}
|
|
1011
|
+
if (item.params.kind === "boolean") {
|
|
1012
|
+
return values.map((v) => {
|
|
1013
|
+
if (v === "true") return true;
|
|
1014
|
+
if (v === "1") return true;
|
|
1015
|
+
if (v === 1) return true;
|
|
1016
|
+
if (v === "false") return false;
|
|
1017
|
+
if (v === "0") return false;
|
|
1018
|
+
if (v === 0) return false;
|
|
1019
|
+
return v;
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
const arrayValues = values.map(
|
|
1024
|
+
(v) => this.processArrayWithSchema(item, v, file)
|
|
1025
|
+
);
|
|
1026
|
+
return new configNodeArray_default(arrayValues);
|
|
1027
|
+
}
|
|
1028
|
+
processArrayWithSchema(item, v, file) {
|
|
1029
|
+
const result = {};
|
|
1030
|
+
this.traverseOptions(
|
|
1031
|
+
item,
|
|
1032
|
+
[],
|
|
1033
|
+
this.buildOption.bind(this, result, {
|
|
1034
|
+
objectFromArray: {
|
|
1035
|
+
value: v,
|
|
1036
|
+
file
|
|
1037
|
+
},
|
|
1038
|
+
errors: this.errors
|
|
1039
|
+
})
|
|
1040
|
+
);
|
|
1041
|
+
return result;
|
|
1042
|
+
}
|
|
1043
|
+
setOption(options, path2, node) {
|
|
1044
|
+
if (path2.length > 1) {
|
|
1045
|
+
const [child, ...rest] = path2;
|
|
1046
|
+
if (!options[child]) {
|
|
1047
|
+
options[child] = {};
|
|
1048
|
+
}
|
|
1049
|
+
this.setOption(
|
|
1050
|
+
options[child],
|
|
1051
|
+
rest,
|
|
1052
|
+
node
|
|
1053
|
+
);
|
|
1054
|
+
} else if (path2.length === 1) {
|
|
1055
|
+
const [child] = path2;
|
|
1056
|
+
if (node != null) {
|
|
1057
|
+
if (node.value instanceof arrayOption_default) {
|
|
1058
|
+
options[child] = node;
|
|
1059
|
+
options[child].value = this.getValidatedArray(
|
|
1060
|
+
node.value.item,
|
|
1061
|
+
node.value.val,
|
|
1062
|
+
node.file || node.variableName || node.argName || ""
|
|
1063
|
+
);
|
|
1064
|
+
} else {
|
|
1065
|
+
options[child] = node;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
} else {
|
|
1069
|
+
throw new Error(
|
|
1070
|
+
`Invalid path '${node.path}' getting from '${node.argName || node.file || node.variableName || ""}' in ' ${node.sourceType}`
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
addArg(node, path2 = []) {
|
|
1075
|
+
if (node.params.cli) {
|
|
1076
|
+
const ident = path2.join(".");
|
|
1077
|
+
let help = node.params.help;
|
|
1078
|
+
if (node.params.oneOf) {
|
|
1079
|
+
const allowed = node.params.oneOf.map(String).join(", ");
|
|
1080
|
+
help = help ? `${help} (one of: ${allowed})` : `one of: ${allowed}`;
|
|
1081
|
+
}
|
|
1082
|
+
this.program.option(`--${ident} <value>`, help);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
getValuesFromTree(node) {
|
|
1086
|
+
if (node instanceof configNode_default) {
|
|
1087
|
+
if (node.value instanceof configNodeArray_default) {
|
|
1088
|
+
return node.value.arrayValues.map(this.getValuesFromTree.bind(this));
|
|
1089
|
+
}
|
|
1090
|
+
return node.value;
|
|
1091
|
+
}
|
|
1092
|
+
return Object.entries(node).reduce(
|
|
1093
|
+
(acc, [key, value]) => {
|
|
1094
|
+
acc[key] = this.getValuesFromTree(value);
|
|
1095
|
+
return acc;
|
|
1096
|
+
},
|
|
1097
|
+
{}
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
get() {
|
|
1101
|
+
return this.getValuesFromTree(
|
|
1102
|
+
this.optionsTree
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
getExtended() {
|
|
1106
|
+
return this.optionsTree;
|
|
1107
|
+
}
|
|
1108
|
+
getWarnings() {
|
|
1109
|
+
return [...this.errors.warnings];
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
var settings_default = Settings;
|
|
1113
|
+
|
|
1114
|
+
// src/watcher.ts
|
|
1115
|
+
import * as fs4 from "fs";
|
|
1116
|
+
|
|
1117
|
+
// src/diffConfig.ts
|
|
1118
|
+
var MASK = "***";
|
|
1119
|
+
function collectSensitivePaths(schema2, prefix = "") {
|
|
1120
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1121
|
+
for (const key of Object.keys(schema2)) {
|
|
1122
|
+
const opt = schema2[key];
|
|
1123
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
1124
|
+
if (opt instanceof PrimitiveOption && opt.params.sensitive) {
|
|
1125
|
+
paths.add(fullPath);
|
|
1126
|
+
}
|
|
1127
|
+
if (opt instanceof ObjectOption) {
|
|
1128
|
+
const childPaths = collectSensitivePaths(opt.item, fullPath);
|
|
1129
|
+
for (const p of childPaths) {
|
|
1130
|
+
paths.add(p);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return paths;
|
|
1135
|
+
}
|
|
1136
|
+
function diffObjects(oldObj, newObj, sensitivePaths, prefix = "") {
|
|
1137
|
+
const changes = [];
|
|
1138
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
1139
|
+
for (const key of allKeys) {
|
|
1140
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
1141
|
+
const isSensitive = sensitivePaths.has(fullPath);
|
|
1142
|
+
const oldVal = oldObj[key];
|
|
1143
|
+
const newVal = newObj[key];
|
|
1144
|
+
const hasOld = key in oldObj;
|
|
1145
|
+
const hasNew = key in newObj;
|
|
1146
|
+
if (hasNew && !hasOld) {
|
|
1147
|
+
changes.push({
|
|
1148
|
+
path: fullPath,
|
|
1149
|
+
type: "added",
|
|
1150
|
+
oldValue: void 0,
|
|
1151
|
+
newValue: isSensitive ? MASK : newVal
|
|
1152
|
+
});
|
|
1153
|
+
} else if (hasOld && !hasNew) {
|
|
1154
|
+
changes.push({
|
|
1155
|
+
path: fullPath,
|
|
1156
|
+
type: "removed",
|
|
1157
|
+
oldValue: isSensitive ? MASK : oldVal,
|
|
1158
|
+
newValue: void 0
|
|
1159
|
+
});
|
|
1160
|
+
} else if (oldVal !== null && newVal !== null && typeof oldVal === "object" && typeof newVal === "object" && !Array.isArray(oldVal) && !Array.isArray(newVal)) {
|
|
1161
|
+
changes.push(
|
|
1162
|
+
...diffObjects(
|
|
1163
|
+
oldVal,
|
|
1164
|
+
newVal,
|
|
1165
|
+
sensitivePaths,
|
|
1166
|
+
fullPath
|
|
1167
|
+
)
|
|
1168
|
+
);
|
|
1169
|
+
} else if (!Object.is(oldVal, newVal)) {
|
|
1170
|
+
if (Array.isArray(oldVal) && Array.isArray(newVal) && JSON.stringify(oldVal) === JSON.stringify(newVal)) {
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
changes.push({
|
|
1174
|
+
path: fullPath,
|
|
1175
|
+
type: "changed",
|
|
1176
|
+
oldValue: isSensitive ? MASK : oldVal,
|
|
1177
|
+
newValue: isSensitive ? MASK : newVal
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return changes;
|
|
1182
|
+
}
|
|
1183
|
+
function diffConfig(oldConfig, newConfig, schema2) {
|
|
1184
|
+
const sensitivePaths = schema2 ? collectSensitivePaths(schema2) : /* @__PURE__ */ new Set();
|
|
1185
|
+
return diffObjects(oldConfig, newConfig, sensitivePaths);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// src/watcher.ts
|
|
1189
|
+
function resolveFilePaths(sources) {
|
|
1190
|
+
const paths = [];
|
|
1191
|
+
const configFiles = validateFiles(
|
|
1192
|
+
sources.files ?? false,
|
|
1193
|
+
sources.dir ?? false
|
|
1194
|
+
);
|
|
1195
|
+
if (Array.isArray(configFiles)) {
|
|
1196
|
+
paths.push(...configFiles);
|
|
1197
|
+
} else if (configFiles) {
|
|
1198
|
+
paths.push(configFiles);
|
|
1199
|
+
}
|
|
1200
|
+
if (sources.envFile) {
|
|
1201
|
+
const envFiles = Array.isArray(sources.envFile) ? sources.envFile : [sources.envFile];
|
|
1202
|
+
paths.push(...envFiles);
|
|
1203
|
+
}
|
|
1204
|
+
return paths;
|
|
1205
|
+
}
|
|
1206
|
+
function createWatcher(schema2, sources, options) {
|
|
1207
|
+
const debounceMs = options.debounce ?? 100;
|
|
1208
|
+
const initialSettings = new settings_default(schema2, sources);
|
|
1209
|
+
let currentConfig = initialSettings.get();
|
|
1210
|
+
const filePaths = resolveFilePaths(sources);
|
|
1211
|
+
const watchers = [];
|
|
1212
|
+
let debounceTimer = null;
|
|
1213
|
+
let closed = false;
|
|
1214
|
+
function reload() {
|
|
1215
|
+
if (closed) return;
|
|
1216
|
+
try {
|
|
1217
|
+
clearFileCache();
|
|
1218
|
+
clearEnvFileCache();
|
|
1219
|
+
const settings = new settings_default(schema2, sources);
|
|
1220
|
+
const newConfig = settings.get();
|
|
1221
|
+
const changes = diffConfig(
|
|
1222
|
+
currentConfig,
|
|
1223
|
+
newConfig,
|
|
1224
|
+
schema2
|
|
1225
|
+
);
|
|
1226
|
+
if (changes.length > 0) {
|
|
1227
|
+
const oldConfig = currentConfig;
|
|
1228
|
+
currentConfig = newConfig;
|
|
1229
|
+
options.onChange(newConfig, oldConfig, changes);
|
|
1230
|
+
}
|
|
1231
|
+
} catch (err) {
|
|
1232
|
+
if (options.onError) {
|
|
1233
|
+
options.onError(err instanceof Error ? err : new Error(String(err)));
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
function scheduleReload() {
|
|
1238
|
+
if (closed) return;
|
|
1239
|
+
if (debounceTimer) {
|
|
1240
|
+
clearTimeout(debounceTimer);
|
|
1241
|
+
}
|
|
1242
|
+
debounceTimer = setTimeout(reload, debounceMs);
|
|
1243
|
+
debounceTimer.unref();
|
|
1244
|
+
}
|
|
1245
|
+
for (const filePath of filePaths) {
|
|
1246
|
+
try {
|
|
1247
|
+
const watcher = fs4.watch(filePath, () => {
|
|
1248
|
+
scheduleReload();
|
|
1249
|
+
});
|
|
1250
|
+
watcher.unref();
|
|
1251
|
+
watchers.push(watcher);
|
|
1252
|
+
} catch {
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
if (sources.dir && typeof sources.dir === "string") {
|
|
1256
|
+
try {
|
|
1257
|
+
const dirWatcher = fs4.watch(sources.dir, () => {
|
|
1258
|
+
scheduleReload();
|
|
1259
|
+
});
|
|
1260
|
+
dirWatcher.unref();
|
|
1261
|
+
watchers.push(dirWatcher);
|
|
1262
|
+
} catch {
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
function close() {
|
|
1266
|
+
if (closed) return;
|
|
1267
|
+
closed = true;
|
|
1268
|
+
if (debounceTimer) {
|
|
1269
|
+
clearTimeout(debounceTimer);
|
|
1270
|
+
debounceTimer = null;
|
|
1271
|
+
}
|
|
1272
|
+
for (const watcher of watchers) {
|
|
1273
|
+
watcher.close();
|
|
1274
|
+
}
|
|
1275
|
+
watchers.length = 0;
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
get config() {
|
|
1279
|
+
return currentConfig;
|
|
1280
|
+
},
|
|
1281
|
+
close
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/builder/settings.ts
|
|
1286
|
+
var SettingsBuilder = class {
|
|
1287
|
+
schema;
|
|
1288
|
+
constructor(schema2) {
|
|
1289
|
+
this.schema = schema2;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Loads and validates configuration, returning a fully-typed plain object.
|
|
1293
|
+
* @param sources - Which sources to read (env, args, files, etc.).
|
|
1294
|
+
* @returns The resolved configuration object matching the schema type.
|
|
1295
|
+
* @throws {ConfigLoadError} If validation fails (missing required fields, type errors, etc.).
|
|
1296
|
+
*/
|
|
1297
|
+
load(sources) {
|
|
1298
|
+
const settings = new settings_default(this.schema, sources);
|
|
1299
|
+
return settings.get();
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Loads configuration and returns raw node data with source metadata alongside warnings.
|
|
1303
|
+
* @param sources - Which sources to read (env, args, files, etc.).
|
|
1304
|
+
* @returns An `ExtendedResult` containing the node tree and any warnings.
|
|
1305
|
+
* @throws {ConfigLoadError} If validation fails.
|
|
1306
|
+
*/
|
|
1307
|
+
loadExtended(sources) {
|
|
1308
|
+
const settings = new settings_default(this.schema, sources);
|
|
1309
|
+
return {
|
|
1310
|
+
data: settings.getExtended(),
|
|
1311
|
+
warnings: settings.getWarnings()
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Watches config files for changes and reloads automatically.
|
|
1316
|
+
* File watchers are `.unref()`'d so they don't prevent the process from exiting.
|
|
1317
|
+
* @param sources - Which sources to read (env, args, files, etc.).
|
|
1318
|
+
* @param options - Callbacks and debounce configuration.
|
|
1319
|
+
* @returns A `ConfigWatcher` with the current config and a `close()` method.
|
|
1320
|
+
* @throws {ConfigLoadError} If the initial load fails.
|
|
1321
|
+
*/
|
|
1322
|
+
watch(sources, options) {
|
|
1323
|
+
return createWatcher(this.schema, sources, options);
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
// src/maskSecrets.ts
|
|
1328
|
+
var MASK2 = "***";
|
|
1329
|
+
function maskNodeTree(tree) {
|
|
1330
|
+
const result = {};
|
|
1331
|
+
for (const [key, entry] of Object.entries(tree)) {
|
|
1332
|
+
if (entry instanceof configNode_default) {
|
|
1333
|
+
if (entry.sensitive) {
|
|
1334
|
+
const masked = new configNode_default(
|
|
1335
|
+
MASK2,
|
|
1336
|
+
entry.path,
|
|
1337
|
+
entry.sourceType,
|
|
1338
|
+
entry.file,
|
|
1339
|
+
entry.variableName,
|
|
1340
|
+
entry.argName,
|
|
1341
|
+
entry.line,
|
|
1342
|
+
entry.column,
|
|
1343
|
+
entry.sensitive
|
|
1344
|
+
);
|
|
1345
|
+
if (entry.value instanceof configNodeArray_default) {
|
|
1346
|
+
masked.value = entry.value;
|
|
1347
|
+
}
|
|
1348
|
+
result[key] = masked;
|
|
1349
|
+
} else {
|
|
1350
|
+
result[key] = entry;
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
result[key] = maskNodeTree(entry);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
}
|
|
1358
|
+
function maskPlainObject(obj, schema2) {
|
|
1359
|
+
const result = {};
|
|
1360
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1361
|
+
const schemaNode = schema2[key];
|
|
1362
|
+
if (!schemaNode) {
|
|
1363
|
+
result[key] = value;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (schemaNode instanceof ObjectOption) {
|
|
1367
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1368
|
+
result[key] = maskPlainObject(
|
|
1369
|
+
value,
|
|
1370
|
+
schemaNode.item
|
|
1371
|
+
);
|
|
1372
|
+
} else {
|
|
1373
|
+
result[key] = value;
|
|
1374
|
+
}
|
|
1375
|
+
} else if (schemaNode instanceof OptionBase) {
|
|
1376
|
+
result[key] = schemaNode.params.sensitive ? MASK2 : value;
|
|
1377
|
+
} else {
|
|
1378
|
+
result[key] = value;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
return result;
|
|
1382
|
+
}
|
|
1383
|
+
function maskSecrets(resultOrConfig, schema2) {
|
|
1384
|
+
if ("data" in resultOrConfig && "warnings" in resultOrConfig && !schema2) {
|
|
1385
|
+
const extended = resultOrConfig;
|
|
1386
|
+
return {
|
|
1387
|
+
data: maskNodeTree(extended.data),
|
|
1388
|
+
warnings: [...extended.warnings]
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
if (schema2) {
|
|
1392
|
+
return maskPlainObject(resultOrConfig, schema2);
|
|
1393
|
+
}
|
|
1394
|
+
return resultOrConfig;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/printConfig.ts
|
|
1398
|
+
function truncate(str, max) {
|
|
1399
|
+
if (str.length <= max) return str;
|
|
1400
|
+
return str.slice(0, max - 3) + "...";
|
|
1401
|
+
}
|
|
1402
|
+
function formatValue(val) {
|
|
1403
|
+
if (val === null || val === void 0) return "";
|
|
1404
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
1405
|
+
if (typeof val === "string") return val;
|
|
1406
|
+
return `${val}`;
|
|
1407
|
+
}
|
|
1408
|
+
function formatDetail(node) {
|
|
1409
|
+
const parts = [];
|
|
1410
|
+
if (node.sourceType === "file" || node.sourceType === "envFile") {
|
|
1411
|
+
if (node.file) {
|
|
1412
|
+
let loc = node.file;
|
|
1413
|
+
if (node.line != null) {
|
|
1414
|
+
loc += `:${node.line}`;
|
|
1415
|
+
if (node.column != null) loc += `:${node.column}`;
|
|
1416
|
+
}
|
|
1417
|
+
parts.push(loc);
|
|
1418
|
+
}
|
|
1419
|
+
if (node.sourceType === "envFile" && node.variableName) {
|
|
1420
|
+
parts.push(`(${node.variableName})`);
|
|
1421
|
+
}
|
|
1422
|
+
} else if (node.sourceType === "env" && node.variableName) {
|
|
1423
|
+
parts.push(node.variableName);
|
|
1424
|
+
} else if (node.sourceType === "args" && node.argName) {
|
|
1425
|
+
parts.push(`--${node.argName}`);
|
|
1426
|
+
}
|
|
1427
|
+
return parts.join(" ");
|
|
1428
|
+
}
|
|
1429
|
+
function flattenTree(tree, prefix = "") {
|
|
1430
|
+
const rows = [];
|
|
1431
|
+
for (const [key, entry] of Object.entries(tree)) {
|
|
1432
|
+
const path2 = prefix ? `${prefix}.${key}` : key;
|
|
1433
|
+
if (entry instanceof configNode_default) {
|
|
1434
|
+
if (entry.value instanceof configNodeArray_default) {
|
|
1435
|
+
for (let i = 0; i < entry.value.arrayValues.length; i++) {
|
|
1436
|
+
const child = entry.value.arrayValues[i];
|
|
1437
|
+
const arrayPath = `${path2}[${i}]`;
|
|
1438
|
+
if (child.value && typeof child.value === "object" && !(child.value instanceof configNodeArray_default)) {
|
|
1439
|
+
const nested = child.value;
|
|
1440
|
+
rows.push(...flattenTree(nested, arrayPath));
|
|
1441
|
+
} else {
|
|
1442
|
+
rows.push({
|
|
1443
|
+
path: arrayPath,
|
|
1444
|
+
value: formatValue(child.value),
|
|
1445
|
+
source: child.sourceType,
|
|
1446
|
+
detail: formatDetail(child)
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
} else {
|
|
1451
|
+
rows.push({
|
|
1452
|
+
path: path2,
|
|
1453
|
+
value: entry.sensitive ? "***" : formatValue(entry.value),
|
|
1454
|
+
source: entry.sourceType,
|
|
1455
|
+
detail: formatDetail(entry)
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
} else {
|
|
1459
|
+
rows.push(...flattenTree(entry, path2));
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return rows;
|
|
1463
|
+
}
|
|
1464
|
+
function padRight(str, len) {
|
|
1465
|
+
return str + " ".repeat(Math.max(0, len - str.length));
|
|
1466
|
+
}
|
|
1467
|
+
function buildTable(rows) {
|
|
1468
|
+
const headers = {
|
|
1469
|
+
path: "Path",
|
|
1470
|
+
value: "Value",
|
|
1471
|
+
source: "Source",
|
|
1472
|
+
detail: "Detail"
|
|
1473
|
+
};
|
|
1474
|
+
const widths = {
|
|
1475
|
+
path: Math.max(headers.path.length, ...rows.map((r) => r.path.length)),
|
|
1476
|
+
value: Math.max(headers.value.length, ...rows.map((r) => r.value.length)),
|
|
1477
|
+
source: Math.max(
|
|
1478
|
+
headers.source.length,
|
|
1479
|
+
...rows.map((r) => r.source.length)
|
|
1480
|
+
),
|
|
1481
|
+
detail: Math.max(
|
|
1482
|
+
headers.detail.length,
|
|
1483
|
+
...rows.map((r) => r.detail.length)
|
|
1484
|
+
)
|
|
1485
|
+
};
|
|
1486
|
+
const hr = `\u251C${"\u2500".repeat(widths.path + 2)}\u253C${"\u2500".repeat(widths.value + 2)}\u253C${"\u2500".repeat(widths.source + 2)}\u253C${"\u2500".repeat(widths.detail + 2)}\u2524`;
|
|
1487
|
+
const top = `\u250C${"\u2500".repeat(widths.path + 2)}\u252C${"\u2500".repeat(widths.value + 2)}\u252C${"\u2500".repeat(widths.source + 2)}\u252C${"\u2500".repeat(widths.detail + 2)}\u2510`;
|
|
1488
|
+
const bottom = `\u2514${"\u2500".repeat(widths.path + 2)}\u2534${"\u2500".repeat(widths.value + 2)}\u2534${"\u2500".repeat(widths.source + 2)}\u2534${"\u2500".repeat(widths.detail + 2)}\u2518`;
|
|
1489
|
+
const formatRow = (r) => `\u2502 ${padRight(r.path, widths.path)} \u2502 ${padRight(r.value, widths.value)} \u2502 ${padRight(r.source, widths.source)} \u2502 ${padRight(r.detail, widths.detail)} \u2502`;
|
|
1490
|
+
const lines = [top, formatRow(headers), hr, ...rows.map(formatRow), bottom];
|
|
1491
|
+
return lines.join("\n");
|
|
1492
|
+
}
|
|
1493
|
+
function printConfig(result, options) {
|
|
1494
|
+
const maxLen = options?.maxValueLength ?? 50;
|
|
1495
|
+
const rows = flattenTree(result.data).map((r) => ({
|
|
1496
|
+
...r,
|
|
1497
|
+
value: truncate(r.value, maxLen)
|
|
1498
|
+
}));
|
|
1499
|
+
let output;
|
|
1500
|
+
if (rows.length === 0) {
|
|
1501
|
+
output = "No configuration values loaded.";
|
|
1502
|
+
} else {
|
|
1503
|
+
output = buildTable(rows);
|
|
1504
|
+
}
|
|
1505
|
+
if (result.warnings.length > 0) {
|
|
1506
|
+
output += "\n\nWarnings:";
|
|
1507
|
+
for (const w of result.warnings) {
|
|
1508
|
+
output += `
|
|
1509
|
+
- ${w}`;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (!options?.silent) {
|
|
1513
|
+
console.log(output);
|
|
1514
|
+
}
|
|
1515
|
+
return output;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/index.ts
|
|
1519
|
+
var DEFAULTS = {
|
|
1520
|
+
required: false,
|
|
1521
|
+
env: null,
|
|
1522
|
+
cli: false,
|
|
1523
|
+
help: ""
|
|
1524
|
+
};
|
|
1525
|
+
function string(opts) {
|
|
1526
|
+
return new PrimitiveOption({
|
|
1527
|
+
kind: "string",
|
|
1528
|
+
...DEFAULTS,
|
|
1529
|
+
...opts
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
function number(opts) {
|
|
1533
|
+
return new PrimitiveOption({
|
|
1534
|
+
kind: "number",
|
|
1535
|
+
...DEFAULTS,
|
|
1536
|
+
...opts
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
function bool(opts) {
|
|
1540
|
+
return new PrimitiveOption({
|
|
1541
|
+
kind: "boolean",
|
|
1542
|
+
...DEFAULTS,
|
|
1543
|
+
...opts
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
var array = (opts) => {
|
|
1547
|
+
return new ArrayOption({
|
|
1548
|
+
...DEFAULTS,
|
|
1549
|
+
...opts
|
|
1550
|
+
});
|
|
1551
|
+
};
|
|
1552
|
+
var object = (opts) => {
|
|
1553
|
+
return new ObjectOption({
|
|
1554
|
+
required: false,
|
|
1555
|
+
...opts
|
|
1556
|
+
});
|
|
1557
|
+
};
|
|
1558
|
+
var schema = (theSchema) => {
|
|
1559
|
+
return new SettingsBuilder(theSchema);
|
|
1560
|
+
};
|
|
1561
|
+
var option = {
|
|
1562
|
+
string,
|
|
1563
|
+
number,
|
|
1564
|
+
bool,
|
|
1565
|
+
array,
|
|
1566
|
+
object,
|
|
1567
|
+
schema
|
|
1568
|
+
};
|
|
1569
|
+
var index_default = option;
|
|
1570
|
+
export {
|
|
1571
|
+
ConfigFileError,
|
|
1572
|
+
ConfigLoadError,
|
|
1573
|
+
configNode_default as ConfigNode,
|
|
1574
|
+
configNodeArray_default as ConfigNodeArray,
|
|
1575
|
+
index_default as default,
|
|
1576
|
+
diffConfig,
|
|
1577
|
+
maskSecrets,
|
|
1578
|
+
printConfig
|
|
1579
|
+
};
|