@meltstudio/config-loader 1.1.0 → 2.0.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.
Files changed (4) hide show
  1. package/README.md +306 -169
  2. package/dist/index.d.ts +138 -68
  3. package/dist/index.js +570 -259
  4. package/package.json +39 -28
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
8
  var __export = (target, all) => {
10
9
  for (var name in all)
11
10
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,37 +26,58 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
26
  mod
28
27
  ));
29
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
- var __publicField = (obj, key, value) => {
31
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
32
- return value;
33
- };
34
29
 
35
30
  // src/index.ts
36
- var src_exports = {};
37
- __export(src_exports, {
38
- default: () => src_default
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ConfigFileError: () => ConfigFileError,
34
+ ConfigLoadError: () => ConfigLoadError,
35
+ default: () => index_default
39
36
  });
40
- module.exports = __toCommonJS(src_exports);
37
+ module.exports = __toCommonJS(index_exports);
41
38
 
42
39
  // src/settings.ts
43
40
  var import_commander = require("commander");
44
- var fs2 = __toESM(require("fs"));
41
+
42
+ // src/errors.ts
43
+ var ConfigLoadError = class extends Error {
44
+ errors;
45
+ warnings;
46
+ constructor(errors, warnings) {
47
+ const message = `Configuration loading failed with ${errors.length} error${errors.length === 1 ? "" : "s"}`;
48
+ super(message);
49
+ this.name = "ConfigLoadError";
50
+ this.errors = errors;
51
+ this.warnings = warnings;
52
+ }
53
+ };
54
+ var ConfigFileError = class extends ConfigLoadError {
55
+ constructor(message) {
56
+ super([{ message, kind: "file_validation" }], []);
57
+ this.name = "ConfigFileError";
58
+ this.message = message;
59
+ }
60
+ };
45
61
 
46
62
  // src/nodes/configNode.ts
47
63
  var ConfigNode = class {
48
64
  value;
49
65
  path;
50
- source_type;
66
+ sourceType;
51
67
  file;
52
- variable_name;
53
- arg_name;
54
- constructor(value, path, source_type, file, variable_name, arg_name) {
68
+ variableName;
69
+ argName;
70
+ line;
71
+ column;
72
+ constructor(value, path2, sourceType, file, variableName, argName, line = null, column = null) {
55
73
  this.value = value;
56
- this.path = path;
57
- this.source_type = source_type;
74
+ this.path = path2;
75
+ this.sourceType = sourceType;
58
76
  this.file = file;
59
- this.variable_name = variable_name;
60
- this.arg_name = arg_name;
77
+ this.variableName = variableName;
78
+ this.argName = argName;
79
+ this.line = line;
80
+ this.column = column;
61
81
  }
62
82
  };
63
83
  var configNode_default = ConfigNode;
@@ -86,38 +106,175 @@ var ArrayValueContainer = class {
86
106
  };
87
107
  var arrayOption_default = ArrayValueContainer;
88
108
 
89
- // src/option/base.ts
109
+ // src/fileLoader.ts
90
110
  var fs = __toESM(require("fs"));
91
111
  var import_js_yaml = __toESM(require("js-yaml"));
112
+ var import_js_yaml_source_map = __toESM(require("js-yaml-source-map"));
113
+ var path = __toESM(require("path"));
114
+ var fileCache = /* @__PURE__ */ new Map();
115
+ var JsonSourceMap = class {
116
+ locations = /* @__PURE__ */ new Map();
117
+ constructor(content) {
118
+ this.buildMap(content);
119
+ }
120
+ buildMap(content, prefix = []) {
121
+ const lines = content.split("\n");
122
+ for (let i = 0; i < lines.length; i++) {
123
+ const line = lines[i];
124
+ const keyRegex = /^(\s*)"([^"]+)"\s*:/g;
125
+ let match;
126
+ while ((match = keyRegex.exec(line)) !== null) {
127
+ const key = match[2];
128
+ const column = match[1].length + 1;
129
+ this.locations.set(key, {
130
+ line: i + 1,
131
+ column,
132
+ position: 0
133
+ });
134
+ }
135
+ }
136
+ try {
137
+ const data = JSON.parse(content);
138
+ this.walkObject(data, prefix, lines);
139
+ } catch {
140
+ }
141
+ }
142
+ walkObject(obj, prefix, lines) {
143
+ for (const key of Object.keys(obj)) {
144
+ const fullPath = [...prefix, key].join(".");
145
+ for (let i = 0; i < lines.length; i++) {
146
+ const line = lines[i];
147
+ const keyPattern = `"${key}"`;
148
+ const idx = line.indexOf(keyPattern);
149
+ if (idx !== -1) {
150
+ const afterKey = line.slice(idx + keyPattern.length).trim();
151
+ if (afterKey.startsWith(":")) {
152
+ this.locations.set(fullPath, {
153
+ line: i + 1,
154
+ column: idx + 1,
155
+ position: 0
156
+ });
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ const val = obj[key];
162
+ if (val && typeof val === "object" && !Array.isArray(val)) {
163
+ this.walkObject(
164
+ val,
165
+ [...prefix, key],
166
+ lines
167
+ );
168
+ }
169
+ }
170
+ }
171
+ lookup(path2) {
172
+ const key = Array.isArray(path2) ? path2.join(".") : path2;
173
+ return this.locations.get(key);
174
+ }
175
+ };
176
+ function loadConfigFile(filePath) {
177
+ const cached = fileCache.get(filePath);
178
+ if (cached) return cached;
179
+ const content = fs.readFileSync(filePath, "utf-8");
180
+ const ext = path.extname(filePath).toLowerCase();
181
+ try {
182
+ if (ext === ".json") {
183
+ const result2 = {
184
+ data: JSON.parse(content),
185
+ sourceMap: new JsonSourceMap(content)
186
+ };
187
+ fileCache.set(filePath, result2);
188
+ return result2;
189
+ }
190
+ const sourceMap = new import_js_yaml_source_map.default();
191
+ const data = import_js_yaml.default.load(content, { listener: sourceMap.listen() });
192
+ const result = { data, sourceMap };
193
+ fileCache.set(filePath, result);
194
+ return result;
195
+ } catch (err) {
196
+ const message = err instanceof Error ? err.message : "Unknown parsing error";
197
+ throw new ConfigFileError(
198
+ `Failed to parse config file '${filePath}': ${message}`
199
+ );
200
+ }
201
+ }
202
+ function clearFileCache() {
203
+ fileCache.clear();
204
+ }
92
205
 
93
206
  // src/utils.ts
94
207
  function valueIsInvalid(val) {
95
208
  return val instanceof InvalidValue || val === null || val === void 0;
96
209
  }
97
210
 
98
- // src/option/errors.ts
99
- var _OptionErrors = class {
100
- static clearAll() {
101
- _OptionErrors.errors = [];
102
- _OptionErrors.warnings = [];
103
- }
104
- };
105
- var OptionErrors = _OptionErrors;
106
- __publicField(OptionErrors, "errors", []);
107
- __publicField(OptionErrors, "warnings", []);
108
-
109
211
  // src/option/base.ts
212
+ function valueToString(val) {
213
+ if (typeof val === "object" && val !== null) {
214
+ return JSON.stringify(val);
215
+ }
216
+ return String(val);
217
+ }
218
+ function lookupLocation(sourceMap, path2) {
219
+ if (!sourceMap) return null;
220
+ const loc = sourceMap.lookup(path2.map(String));
221
+ if (!loc) return null;
222
+ return { line: loc.line, column: loc.column };
223
+ }
224
+ function findEnvFileSource(envKey, currentValue, envFileResults) {
225
+ if (!envFileResults) return null;
226
+ for (let i = envFileResults.length - 1; i >= 0; i--) {
227
+ const result = envFileResults[i];
228
+ const entry = result.entries.get(envKey);
229
+ if (entry && entry.value === currentValue) {
230
+ return {
231
+ filePath: result.filePath,
232
+ line: entry.line,
233
+ column: entry.column
234
+ };
235
+ }
236
+ }
237
+ return null;
238
+ }
239
+ function checkNumberType(val, pathStr, sourceOfVal, errors) {
240
+ if (typeof val === "string") {
241
+ const parseVal = parseInt(val, 10);
242
+ if (Number.isNaN(parseVal)) {
243
+ errors?.errors.push({
244
+ message: `Cannot convert value '${val}' for '${pathStr}' to number in ${sourceOfVal}.`,
245
+ path: pathStr,
246
+ source: sourceOfVal,
247
+ kind: "type_conversion"
248
+ });
249
+ return new InvalidValue();
250
+ }
251
+ errors?.warnings.push(
252
+ `The option ${pathStr} is stated as a number but is provided as a string`
253
+ );
254
+ return parseVal;
255
+ }
256
+ errors?.errors.push({
257
+ message: `Invalid state. Invalid kind in ${sourceOfVal}`,
258
+ source: sourceOfVal,
259
+ kind: "invalid_state"
260
+ });
261
+ return new InvalidValue();
262
+ }
263
+ function formatFileLocation(file, loc) {
264
+ if (!loc) return file;
265
+ return `${file}:${loc.line}:${loc.column}`;
266
+ }
110
267
  var OptionBase = class {
111
268
  params;
112
269
  constructor(params) {
113
270
  this.params = params;
114
271
  }
115
- getValue(sourceFile, env, args, path, defaultValues, objectFromArray) {
116
- const ident = path.join(".");
272
+ getValue(sourceFile, env, args, path2, defaultValues, objectFromArray, envFileResults, errors) {
273
+ const ident = path2.join(".");
117
274
  if (this.params.cli && args) {
118
275
  if (ident in args) {
119
276
  return new configNode_default(
120
- this.checkType(args[ident], path, "args"),
277
+ this.checkType(args[ident], path2, "args", errors),
121
278
  ident,
122
279
  "args",
123
280
  null,
@@ -130,8 +287,25 @@ var OptionBase = class {
130
287
  if (this.params.env in env) {
131
288
  const val = env[this.params.env];
132
289
  if (val) {
290
+ const envFileSource = findEnvFileSource(
291
+ this.params.env,
292
+ val,
293
+ envFileResults
294
+ );
295
+ if (envFileSource) {
296
+ return new configNode_default(
297
+ this.checkType(val, path2, "envFile", errors),
298
+ ident,
299
+ "envFile",
300
+ envFileSource.filePath,
301
+ this.params.env,
302
+ null,
303
+ envFileSource.line,
304
+ envFileSource.column
305
+ );
306
+ }
133
307
  return new configNode_default(
134
- this.checkType(val, path, "env"),
308
+ this.checkType(val, path2, "env", errors),
135
309
  ident,
136
310
  "env",
137
311
  null,
@@ -142,88 +316,52 @@ var OptionBase = class {
142
316
  }
143
317
  }
144
318
  if (typeof sourceFile === "string") {
145
- const data = import_js_yaml.default.load(
146
- fs.readFileSync(sourceFile, "utf-8")
319
+ const { data, sourceMap } = loadConfigFile(sourceFile);
320
+ const node = this.resolveFromFileData(
321
+ data || {},
322
+ sourceFile,
323
+ sourceMap,
324
+ path2,
325
+ ident,
326
+ errors
147
327
  );
148
- const val = this.findInObject(data || {}, path);
149
- if (val instanceof arrayOption_default) {
150
- return new configNode_default(
151
- this.checkType(val, path, sourceFile),
152
- ident,
153
- "file",
154
- sourceFile,
155
- null,
156
- null
157
- );
158
- }
159
- if (!valueIsInvalid(val)) {
160
- return new configNode_default(
161
- this.checkType(val, path, sourceFile),
162
- ident,
163
- "file",
164
- sourceFile,
165
- null,
166
- null
167
- );
168
- }
328
+ if (node) return node;
169
329
  }
170
330
  if (Array.isArray(sourceFile)) {
171
331
  for (let index = 0; index < sourceFile.length; index += 1) {
172
332
  const file = sourceFile[index];
173
- const data = import_js_yaml.default.load(
174
- fs.readFileSync(file, "utf-8")
333
+ const { data, sourceMap } = loadConfigFile(file);
334
+ const node = this.resolveFromFileData(
335
+ data || {},
336
+ file,
337
+ sourceMap,
338
+ path2,
339
+ ident,
340
+ errors
175
341
  );
176
- const val = this.findInObject(data || {}, path);
177
- if (val instanceof arrayOption_default) {
178
- return new configNode_default(
179
- this.checkType(val, path, file),
180
- ident,
181
- "file",
182
- file,
183
- null,
184
- null
185
- );
186
- }
187
- if (!valueIsInvalid(val)) {
188
- return new configNode_default(
189
- this.checkType(val, path, file),
190
- ident,
191
- "file",
192
- file,
193
- null,
194
- null
195
- );
196
- }
342
+ if (node) return node;
197
343
  }
198
344
  }
199
345
  if (objectFromArray) {
200
- const val = this.findInObject(objectFromArray.value, path);
201
- if (val instanceof arrayOption_default) {
202
- return new configNode_default(
203
- this.checkType(val, path, objectFromArray.file),
204
- ident,
205
- "file",
206
- objectFromArray.file,
207
- null,
208
- null
209
- );
210
- }
211
- if (!valueIsInvalid(val)) {
212
- return new configNode_default(
213
- this.checkType(val, path, objectFromArray.file),
214
- ident,
215
- "file",
216
- objectFromArray.file,
217
- null,
218
- null
219
- );
220
- }
346
+ const node = this.resolveFromFileData(
347
+ objectFromArray.value,
348
+ objectFromArray.file,
349
+ objectFromArray.sourceMap ?? null,
350
+ path2,
351
+ ident,
352
+ errors
353
+ );
354
+ if (node) return node;
221
355
  }
222
356
  if (defaultValues) {
223
- const val = this.findInObject(defaultValues, path);
357
+ const val = this.findInObject(
358
+ defaultValues,
359
+ path2,
360
+ errors
361
+ );
224
362
  if (val instanceof arrayOption_default) {
225
363
  return new configNode_default(
226
- this.checkType(val, path, "default"),
364
+ this.checkType(val, path2, "default", errors),
227
365
  ident,
228
366
  "default",
229
367
  null,
@@ -233,7 +371,7 @@ var OptionBase = class {
233
371
  }
234
372
  if (!valueIsInvalid(val)) {
235
373
  return new configNode_default(
236
- this.checkType(val, path, "default"),
374
+ this.checkType(val, path2, "default", errors),
237
375
  ident,
238
376
  "default",
239
377
  null,
@@ -244,17 +382,18 @@ var OptionBase = class {
244
382
  }
245
383
  if (this.params.defaultValue !== void 0) {
246
384
  let defaultValue;
247
- if (typeof this.params.defaultValue === "function") {
248
- defaultValue = this.params.defaultValue();
385
+ const rawDefault = this.params.defaultValue;
386
+ if (typeof rawDefault === "function") {
387
+ defaultValue = rawDefault();
249
388
  } else {
250
- defaultValue = this.params.defaultValue;
389
+ defaultValue = rawDefault;
251
390
  }
252
391
  if (this.params.kind === "array" && Array.isArray(defaultValue)) {
253
- defaultValue = this.buildArrayOption(defaultValue);
392
+ defaultValue = this.buildArrayOption(defaultValue, errors);
254
393
  }
255
394
  if (!valueIsInvalid(defaultValue)) {
256
395
  return new configNode_default(
257
- this.checkType(defaultValue, path, "default"),
396
+ this.checkType(defaultValue, path2, "default", errors),
258
397
  ident,
259
398
  "default",
260
399
  null,
@@ -264,32 +403,39 @@ var OptionBase = class {
264
403
  }
265
404
  }
266
405
  if (this.params.required) {
267
- OptionErrors.errors.push(`Required option '${ident}' not provided.`);
406
+ errors?.errors.push({
407
+ message: `Required option '${ident}' not provided.`,
408
+ path: ident,
409
+ kind: "required"
410
+ });
268
411
  }
269
412
  return null;
270
413
  }
271
- // eslint-disable-next-line class-methods-use-this
272
- checkNumberType(val, pathStr, sourceOfVal) {
273
- if (typeof val === "string") {
274
- const parseVal = parseInt(val, 10);
275
- if (Number.isNaN(parseVal)) {
276
- OptionErrors.errors.push(
277
- `Cannot convert value '${val}' for '${pathStr}' to number in ${sourceOfVal}.`
278
- );
279
- return new InvalidValue();
280
- }
281
- OptionErrors.warnings.push(
282
- `The option ${pathStr} is stated as a number but is provided as a string`
414
+ resolveFromFileData(data, file, sourceMap, path2, ident, errors) {
415
+ const val = this.findInObject(data, path2, errors);
416
+ const loc = lookupLocation(sourceMap, path2);
417
+ if (val instanceof arrayOption_default || !valueIsInvalid(val)) {
418
+ return new configNode_default(
419
+ this.checkType(val, path2, formatFileLocation(file, loc), errors),
420
+ ident,
421
+ "file",
422
+ file,
423
+ null,
424
+ null,
425
+ loc?.line ?? null,
426
+ loc?.column ?? null
283
427
  );
284
- return parseVal;
285
428
  }
286
- OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
287
- return new InvalidValue();
429
+ return null;
288
430
  }
289
- checkType(val, path, sourceOfVal) {
290
- const ident = path.join(".");
431
+ checkType(val, path2, sourceOfVal, errors) {
432
+ const ident = path2.join(".");
291
433
  if (valueIsInvalid(val)) {
292
- OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
434
+ errors?.errors.push({
435
+ message: `Invalid state. Invalid kind in ${sourceOfVal}`,
436
+ source: sourceOfVal,
437
+ kind: "invalid_state"
438
+ });
293
439
  return val;
294
440
  }
295
441
  if (typeof val === this.params.kind) {
@@ -297,14 +443,19 @@ var OptionBase = class {
297
443
  }
298
444
  if (this.params.kind === "string") {
299
445
  if (typeof val === "number") {
300
- OptionErrors.warnings.push(
446
+ errors?.warnings.push(
301
447
  `The option ${ident} is stated as a string but is provided as a number`
302
448
  );
303
449
  return val.toString();
304
450
  }
305
- OptionErrors.errors.push(
306
- `Cannot convert value '${val.toString()}' for '${ident}' to string in ${sourceOfVal}.`
307
- );
451
+ errors?.errors.push({
452
+ message: `Cannot convert value '${valueToString(
453
+ val
454
+ )}' for '${ident}' to string in ${sourceOfVal}.`,
455
+ path: ident,
456
+ source: sourceOfVal,
457
+ kind: "type_conversion"
458
+ });
308
459
  return new InvalidValue();
309
460
  }
310
461
  if (this.params.kind === "boolean") {
@@ -316,73 +467,106 @@ var OptionBase = class {
316
467
  return false;
317
468
  }
318
469
  }
319
- OptionErrors.errors.push(
320
- `Cannot convert value '${val.toString()}' for '${ident}' to boolean in ${sourceOfVal}.`
321
- );
470
+ errors?.errors.push({
471
+ message: `Cannot convert value '${valueToString(
472
+ val
473
+ )}' for '${ident}' to boolean in ${sourceOfVal}.`,
474
+ path: ident,
475
+ source: sourceOfVal,
476
+ kind: "type_conversion"
477
+ });
322
478
  return new InvalidValue();
323
479
  }
324
480
  if (this.params.kind === "number") {
325
- return this.checkNumberType(val, ident, sourceOfVal);
326
- }
327
- if (this.params.kind === "any") {
328
- return val;
481
+ return checkNumberType(val, ident, sourceOfVal, errors);
329
482
  }
330
- OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
483
+ errors?.errors.push({
484
+ message: `Invalid state. Invalid kind in ${sourceOfVal}`,
485
+ source: sourceOfVal,
486
+ kind: "invalid_state"
487
+ });
331
488
  throw new Error(
332
- "Invalid kind. Must be 'string', 'number', 'boolean', 'array' or 'any'"
489
+ "Invalid kind. Must be 'string', 'number', 'boolean' or 'array'"
333
490
  );
334
491
  }
335
- findInObject(obj, path) {
336
- if (path.length > 1) {
337
- const [child, ...rest] = path;
492
+ findInObject(obj, path2, errors) {
493
+ if (path2.length > 1) {
494
+ const [child, ...rest] = path2;
338
495
  const val = obj[child];
339
496
  if (typeof val === "string") {
340
- OptionErrors.errors.push(`Cant get path from string value '${val}'`);
497
+ errors?.errors.push({
498
+ message: `Cant get path from string value '${val}'`,
499
+ kind: "invalid_path"
500
+ });
341
501
  return new InvalidValue();
342
502
  }
343
503
  if (typeof val === "number") {
344
- OptionErrors.errors.push(`Cant get path from number value '${val}'`);
504
+ errors?.errors.push({
505
+ message: `Cant get path from number value '${val}'`,
506
+ kind: "invalid_path"
507
+ });
345
508
  return new InvalidValue();
346
509
  }
347
510
  if (typeof val === "boolean") {
348
- OptionErrors.errors.push(
349
- `Cant get path from boolean value '${val.toString()}'`
350
- );
511
+ errors?.errors.push({
512
+ message: `Cant get path from boolean value '${val.toString()}'`,
513
+ kind: "invalid_path"
514
+ });
351
515
  return new InvalidValue();
352
516
  }
353
517
  if (Array.isArray(val)) {
354
- OptionErrors.errors.push(
355
- `Cant get path from array value '${val.toString()}'`
356
- );
518
+ errors?.errors.push({
519
+ message: `Cant get path from array value '${valueToString(val)}'`,
520
+ kind: "invalid_path"
521
+ });
357
522
  return new InvalidValue();
358
523
  }
359
524
  if (val == null) {
360
525
  return new InvalidValue();
361
526
  }
362
- return this.findInObject(val, rest);
527
+ return this.findInObject(val, rest, errors);
363
528
  }
364
- if (path.length === 1) {
365
- const val = obj[path[0]];
529
+ if (path2.length === 1) {
530
+ const val = obj[path2[0]];
366
531
  if (!Array.isArray(val) && typeof val === "object" && val || typeof val === "string" || typeof val === "number" || typeof val === "boolean" || typeof val === "undefined") {
367
532
  return val;
368
533
  }
369
534
  if (Array.isArray(val)) {
370
- return this.buildArrayOption(val);
535
+ return this.buildArrayOption(val, errors);
371
536
  }
372
- OptionErrors.errors.push(
373
- `Invalid path '${path.join(".")}': ${typeof val}`
374
- );
537
+ errors?.errors.push({
538
+ message: `Invalid path '${path2.join(".")}': ${typeof val}`,
539
+ kind: "invalid_path"
540
+ });
375
541
  return new InvalidValue();
376
542
  }
377
- OptionErrors.errors.push(`Invalid path '${path.join()}'`);
543
+ errors?.errors.push({
544
+ message: `Invalid path '${path2.join()}'`,
545
+ kind: "invalid_path"
546
+ });
378
547
  return new InvalidValue();
379
548
  }
380
549
  // eslint-disable-next-line class-methods-use-this
381
- buildArrayOption(_val) {
550
+ buildArrayOption(_val, _errors) {
382
551
  return new InvalidValue();
383
552
  }
384
553
  };
385
554
 
555
+ // src/option/object.ts
556
+ var ObjectOption = class extends OptionBase {
557
+ item;
558
+ constructor(params) {
559
+ super({
560
+ kind: "object",
561
+ env: null,
562
+ cli: false,
563
+ help: "",
564
+ ...params
565
+ });
566
+ this.item = params.item;
567
+ }
568
+ };
569
+
386
570
  // src/option/array.ts
387
571
  var ArrayOption = class extends OptionBase {
388
572
  item;
@@ -396,41 +580,162 @@ var ArrayOption = class extends OptionBase {
396
580
  });
397
581
  this.item = params.item;
398
582
  }
399
- buildArrayOption(val) {
583
+ buildArrayOption(val, errors) {
400
584
  if (this.item === null) {
401
- OptionErrors.errors.push(`Array item cannot be null`);
585
+ errors?.errors.push({
586
+ message: `Array item cannot be null`,
587
+ kind: "invalid_state"
588
+ });
402
589
  return new InvalidValue();
403
590
  }
404
591
  return new arrayOption_default(this.item, val);
405
592
  }
406
- // eslint-disable-next-line class-methods-use-this
407
- checkType(val, path, sourceOfVal) {
593
+ checkType(val, path2, sourceOfVal, errors) {
408
594
  if (val instanceof arrayOption_default) {
409
595
  val.val.forEach((v, i) => {
410
- if (this.item instanceof OptionBase) {
411
- this.item.checkType(v, [...path, i], sourceOfVal);
596
+ if (this.item instanceof OptionBase && !(this.item instanceof ObjectOption)) {
597
+ this.item.checkType(v, [...path2, i], sourceOfVal, errors);
412
598
  }
413
599
  });
414
600
  return val;
415
601
  }
416
- OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
602
+ errors?.errors.push({
603
+ message: `Invalid state. Invalid kind in ${sourceOfVal}`,
604
+ source: sourceOfVal,
605
+ kind: "invalid_state"
606
+ });
417
607
  return new InvalidValue();
418
608
  }
419
609
  };
420
610
 
611
+ // src/envFileLoader.ts
612
+ var fs2 = __toESM(require("fs"));
613
+ var envFileCache = /* @__PURE__ */ new Map();
614
+ function loadEnvFile(filePath) {
615
+ const cached = envFileCache.get(filePath);
616
+ if (cached) return cached;
617
+ const content = fs2.readFileSync(filePath, "utf-8");
618
+ const entries = /* @__PURE__ */ new Map();
619
+ const lines = content.split("\n");
620
+ for (let i = 0; i < lines.length; i++) {
621
+ const raw = lines[i];
622
+ const trimmed = raw.trim();
623
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
624
+ const eqIndex = trimmed.indexOf("=");
625
+ if (eqIndex === -1) continue;
626
+ const key = trimmed.slice(0, eqIndex).trim();
627
+ if (key === "") continue;
628
+ let value = trimmed.slice(eqIndex + 1).trim();
629
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
630
+ value = value.slice(1, -1);
631
+ }
632
+ const column = raw.indexOf(key) + 1;
633
+ entries.set(key, {
634
+ value,
635
+ line: i + 1,
636
+ column
637
+ });
638
+ }
639
+ const result = { entries, filePath };
640
+ envFileCache.set(filePath, result);
641
+ return result;
642
+ }
643
+ function clearEnvFileCache() {
644
+ envFileCache.clear();
645
+ }
646
+
647
+ // src/option/errors.ts
648
+ var OptionErrors = class {
649
+ errors = [];
650
+ warnings = [];
651
+ clearAll() {
652
+ this.errors = [];
653
+ this.warnings = [];
654
+ clearFileCache();
655
+ clearEnvFileCache();
656
+ }
657
+ };
658
+
421
659
  // src/option/primitive.ts
422
660
  var PrimitiveOption = class extends OptionBase {
423
661
  };
424
662
 
663
+ // src/sourceValidation.ts
664
+ var fs3 = __toESM(require("fs"));
665
+ function validateFiles(files, dir) {
666
+ if (files && dir)
667
+ throw new ConfigFileError("Dir and files are specified, choose one");
668
+ let sourceFile = [];
669
+ if (files) {
670
+ if (Array.isArray(files)) {
671
+ const result = [];
672
+ files.forEach((file) => {
673
+ if (!fs3.existsSync(file)) {
674
+ throw new ConfigFileError(`Invalid config file '${file}'`);
675
+ } else {
676
+ result.push(file);
677
+ }
678
+ });
679
+ sourceFile = result;
680
+ } else {
681
+ if (!fs3.existsSync(files)) {
682
+ throw new ConfigFileError(`Invalid config file '${files}'`);
683
+ }
684
+ sourceFile = files;
685
+ }
686
+ }
687
+ if (dir) {
688
+ if (!(fs3.existsSync(dir) && fs3.lstatSync(dir).isDirectory())) {
689
+ throw new ConfigFileError(`'${dir}' not exists or is not a dir`);
690
+ }
691
+ const filesInDirectory = fs3.readdirSync(dir).sort();
692
+ if (filesInDirectory.length === 0) {
693
+ throw new ConfigFileError(`Directory '${dir}' is empty`);
694
+ }
695
+ const result = [];
696
+ filesInDirectory.forEach((file) => {
697
+ result.push(`${dir}/${file}`);
698
+ });
699
+ sourceFile = result;
700
+ }
701
+ return sourceFile;
702
+ }
703
+ function loadEnvFiles(envFile, envData) {
704
+ const envFileResults = [];
705
+ if (!envFile) return { envFileResults, mergedEnvData: envData };
706
+ const envFiles = Array.isArray(envFile) ? envFile : [envFile];
707
+ for (const file of envFiles) {
708
+ if (!fs3.existsSync(file)) {
709
+ throw new ConfigFileError(`Invalid env file '${file}'`);
710
+ }
711
+ const result = loadEnvFile(file);
712
+ envFileResults.push(result);
713
+ }
714
+ const merged = {};
715
+ for (const result of envFileResults) {
716
+ for (const [key, entry] of result.entries) {
717
+ merged[key] = entry.value;
718
+ }
719
+ }
720
+ for (const [key, value] of Object.entries(envData)) {
721
+ if (value !== void 0) {
722
+ merged[key] = value;
723
+ }
724
+ }
725
+ return { envFileResults, mergedEnvData: merged };
726
+ }
727
+
425
728
  // src/settings.ts
426
729
  var Settings = class {
427
730
  schema;
428
731
  sources;
732
+ errors = new OptionErrors();
429
733
  sourceFile = [];
430
734
  argsData = {};
431
735
  envData = {};
432
736
  optionsTree = {};
433
737
  defaultData = {};
738
+ envFileResults = [];
434
739
  program;
435
740
  constructor(schema2, sources) {
436
741
  this.schema = schema2;
@@ -438,50 +743,23 @@ var Settings = class {
438
743
  this.program = new import_commander.Command().allowUnknownOption(true).allowExcessArguments(true);
439
744
  this.load();
440
745
  }
441
- validateFiles() {
442
- const { files, dir } = this.sources;
443
- if (files && dir)
444
- throw new Error("Dir and files are specified, choose one");
445
- if (files) {
446
- if (Array.isArray(files)) {
447
- files.forEach((file) => {
448
- if (!fs2.existsSync(file)) {
449
- throw new Error(`Invalid config file '${file}'`);
450
- } else {
451
- if (!Array.isArray(this.sourceFile)) {
452
- this.sourceFile = [];
453
- }
454
- this.sourceFile.push(file);
455
- }
456
- });
457
- } else {
458
- if (!fs2.existsSync(files)) {
459
- throw new Error(`Invalid config file '${files}'`);
460
- }
461
- this.sourceFile = files;
462
- }
463
- }
464
- if (dir) {
465
- if (!(fs2.existsSync(dir) && fs2.lstatSync(dir).isDirectory())) {
466
- throw new Error(`'${dir}' not exists or is not a dir`);
467
- }
468
- const filesInDirectory = fs2.readdirSync(dir).sort();
469
- if (filesInDirectory.length === 0) {
470
- throw new Error(`Directory '${dir}' is empty`);
471
- }
472
- filesInDirectory.forEach((file) => {
473
- if (!Array.isArray(this.sourceFile)) {
474
- this.sourceFile = [];
475
- }
476
- this.sourceFile.push(`${dir}/${file}`);
477
- });
478
- }
746
+ validateAndLoadFiles() {
747
+ this.sourceFile = validateFiles(this.sources.files, this.sources.dir);
748
+ }
749
+ loadAndMergeEnvFiles() {
750
+ const { envFileResults, mergedEnvData } = loadEnvFiles(
751
+ this.sources.envFile,
752
+ this.envData
753
+ );
754
+ this.envFileResults = envFileResults;
755
+ this.envData = mergedEnvData;
479
756
  }
480
757
  load() {
481
- this.validateFiles();
758
+ this.validateAndLoadFiles();
482
759
  if (this.sources.env) {
483
- this.envData = process.env;
760
+ this.envData = { ...process.env };
484
761
  }
762
+ this.loadAndMergeEnvFiles();
485
763
  if (this.sources.args) {
486
764
  this.traverseOptions(this.schema, [], this.addArg.bind(this));
487
765
  this.program.parse(process.argv);
@@ -497,44 +775,66 @@ var Settings = class {
497
775
  sourceFile: this.sourceFile,
498
776
  envData: this.envData,
499
777
  argsData: this.argsData,
500
- defaultValue: this.defaultData
778
+ defaultValue: this.defaultData,
779
+ envFileResults: this.envFileResults,
780
+ errors: this.errors
501
781
  })
502
782
  );
503
- if (OptionErrors.warnings.length > 0) {
504
- for (let index = 0; index < OptionErrors.warnings.length; index += 1) {
505
- console.warn(`[Warning]: ${OptionErrors.warnings[index]}`);
783
+ if (this.errors.warnings.length > 0) {
784
+ for (let index = 0; index < this.errors.warnings.length; index += 1) {
785
+ console.warn(`[Warning]: ${this.errors.warnings[index]}`);
506
786
  }
507
787
  }
508
- if (OptionErrors.errors.length > 0) {
509
- for (let index = 0; index < OptionErrors.errors.length; index += 1) {
510
- console.error(`[Error]: ${OptionErrors.errors[index]}`);
788
+ if (this.errors.errors.length > 0) {
789
+ if (this.sources.exitOnError) {
790
+ for (let index = 0; index < this.errors.errors.length; index += 1) {
791
+ console.error(`[Error]: ${this.errors.errors[index].message}`);
792
+ }
793
+ process.exit(1);
511
794
  }
512
- process.exit(1);
795
+ throw new ConfigLoadError(
796
+ [...this.errors.errors],
797
+ [...this.errors.warnings]
798
+ );
513
799
  }
514
800
  }
515
- traverseOptions(node, path, callback) {
516
- if (node instanceof OptionBase) {
517
- callback(node, path);
801
+ traverseOptions(node, path2, callback) {
802
+ if (node instanceof ObjectOption) {
803
+ const item = node.item;
804
+ Object.keys(item).forEach((key) => {
805
+ this.traverseOptions(item[key], [...path2, key], callback);
806
+ });
807
+ } else if (node instanceof OptionBase) {
808
+ callback(node, path2);
518
809
  } else {
519
810
  Object.keys(node).forEach((key) => {
520
811
  const val = node[key];
521
- this.traverseOptions(val, [...path, key], callback);
812
+ this.traverseOptions(val, [...path2, key], callback);
522
813
  });
523
814
  }
524
815
  }
525
- buildOption(result, configData, node, path) {
526
- const { sourceFile, envData, argsData, defaultValue, objectFromArray } = configData;
816
+ buildOption(result, configData, node, path2) {
817
+ const {
818
+ sourceFile = [],
819
+ envData = {},
820
+ argsData = {},
821
+ defaultValue = {},
822
+ objectFromArray,
823
+ envFileResults,
824
+ errors
825
+ } = configData;
527
826
  const value = node.getValue(
528
827
  sourceFile,
529
828
  envData,
530
829
  argsData,
531
- path,
830
+ path2,
532
831
  defaultValue,
533
- objectFromArray
832
+ objectFromArray,
833
+ envFileResults,
834
+ errors
534
835
  );
535
- if (value === null) {
536
- } else {
537
- this.setOption(result, path, value);
836
+ if (value !== null) {
837
+ this.setOption(result, path2, value);
538
838
  }
539
839
  }
540
840
  getValidatedArray(item, values, file) {
@@ -547,27 +847,18 @@ var Settings = class {
547
847
  }
548
848
  if (item.params.kind === "boolean") {
549
849
  return values.map((v) => {
550
- if (v === "true")
551
- return true;
552
- if (v === "1")
553
- return true;
554
- if (v === 1)
555
- return true;
556
- if (v === "false")
557
- return false;
558
- if (v === "0")
559
- return false;
560
- if (v === 0)
561
- return false;
850
+ if (v === "true") return true;
851
+ if (v === "1") return true;
852
+ if (v === 1) return true;
853
+ if (v === "false") return false;
854
+ if (v === "0") return false;
855
+ if (v === 0) return false;
562
856
  return v;
563
857
  });
564
858
  }
565
859
  }
566
860
  const arrayValues = values.map(
567
- (v) => (
568
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
569
- this.processArrayWithSchema(item, v, file)
570
- )
861
+ (v) => this.processArrayWithSchema(item, v, file)
571
862
  );
572
863
  return new configNodeArray_default(arrayValues);
573
864
  }
@@ -578,30 +869,34 @@ var Settings = class {
578
869
  [],
579
870
  this.buildOption.bind(this, result, {
580
871
  objectFromArray: {
581
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
582
872
  value: v,
583
873
  file
584
- }
874
+ },
875
+ errors: this.errors
585
876
  })
586
877
  );
587
878
  return result;
588
879
  }
589
- setOption(options, path, node) {
590
- if (path.length > 1) {
591
- const [child, ...rest] = path;
880
+ setOption(options, path2, node) {
881
+ if (path2.length > 1) {
882
+ const [child, ...rest] = path2;
592
883
  if (!options[child]) {
593
884
  options[child] = {};
594
885
  }
595
- this.setOption(options[child], rest, node);
596
- } else if (path.length === 1) {
597
- const [child] = path;
886
+ this.setOption(
887
+ options[child],
888
+ rest,
889
+ node
890
+ );
891
+ } else if (path2.length === 1) {
892
+ const [child] = path2;
598
893
  if (node != null) {
599
894
  if (node.value instanceof arrayOption_default) {
600
895
  options[child] = node;
601
896
  options[child].value = this.getValidatedArray(
602
897
  node.value.item,
603
898
  node.value.val,
604
- node.file || node.variable_name || node.arg_name || ""
899
+ node.file || node.variableName || node.argName || ""
605
900
  );
606
901
  } else {
607
902
  options[child] = node;
@@ -609,13 +904,13 @@ var Settings = class {
609
904
  }
610
905
  } else {
611
906
  throw new Error(
612
- `Invalid path '${node.path}' getting from '${node.arg_name || node.file || node.variable_name || ""}' in ' ${node.source_type}`
907
+ `Invalid path '${node.path}' getting from '${node.argName || node.file || node.variableName || ""}' in ' ${node.sourceType}`
613
908
  );
614
909
  }
615
910
  }
616
- addArg(node, path = []) {
911
+ addArg(node, path2 = []) {
617
912
  if (node.params.cli) {
618
- const ident = path.join(".");
913
+ const ident = path2.join(".");
619
914
  this.program.option(`--${ident} <value>`, node.params.help);
620
915
  }
621
916
  }
@@ -636,7 +931,9 @@ var Settings = class {
636
931
  );
637
932
  }
638
933
  get() {
639
- return this.getValuesFromTree(this.optionsTree);
934
+ return this.getValuesFromTree(
935
+ this.optionsTree
936
+ );
640
937
  }
641
938
  getExtended() {
642
939
  return this.optionsTree;
@@ -654,6 +951,10 @@ var SettingsBuilder = class {
654
951
  const settings = new settings_default(this.schema, sources);
655
952
  return settings.get();
656
953
  }
954
+ loadExtended(sources) {
955
+ const settings = new settings_default(this.schema, sources);
956
+ return settings.getExtended();
957
+ }
657
958
  };
658
959
 
659
960
  // src/index.ts
@@ -662,7 +963,6 @@ var DEFAULTS = {
662
963
  env: null,
663
964
  cli: false,
664
965
  help: ""
665
- // properties: {},
666
966
  };
667
967
  var string = (opts) => {
668
968
  return new PrimitiveOption({
@@ -691,6 +991,12 @@ var array = (opts) => {
691
991
  ...opts
692
992
  });
693
993
  };
994
+ var object = (opts) => {
995
+ return new ObjectOption({
996
+ required: false,
997
+ ...opts
998
+ });
999
+ };
694
1000
  var schema = (theSchema) => {
695
1001
  return new SettingsBuilder(theSchema);
696
1002
  };
@@ -698,8 +1004,13 @@ var option = {
698
1004
  string,
699
1005
  number,
700
1006
  bool,
701
- // object,
702
1007
  array,
1008
+ object,
703
1009
  schema
704
1010
  };
705
- var src_default = option;
1011
+ var index_default = option;
1012
+ // Annotate the CommonJS export names for ESM import in node:
1013
+ 0 && (module.exports = {
1014
+ ConfigFileError,
1015
+ ConfigLoadError
1016
+ });