@meltstudio/config-loader 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +330 -175
  2. package/dist/index.d.ts +135 -84
  3. package/dist/index.js +605 -283
  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,18 +26,70 @@ 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,
39
- option: () => option
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ConfigFileError: () => ConfigFileError,
34
+ ConfigLoadError: () => ConfigLoadError,
35
+ default: () => index_default
40
36
  });
41
- module.exports = __toCommonJS(src_exports);
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/settings.ts
40
+ var import_commander = require("commander");
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
+ };
61
+
62
+ // src/nodes/configNode.ts
63
+ var ConfigNode = class {
64
+ value;
65
+ path;
66
+ sourceType;
67
+ file;
68
+ variableName;
69
+ argName;
70
+ line;
71
+ column;
72
+ constructor(value, path2, sourceType, file, variableName, argName, line = null, column = null) {
73
+ this.value = value;
74
+ this.path = path2;
75
+ this.sourceType = sourceType;
76
+ this.file = file;
77
+ this.variableName = variableName;
78
+ this.argName = argName;
79
+ this.line = line;
80
+ this.column = column;
81
+ }
82
+ };
83
+ var configNode_default = ConfigNode;
84
+
85
+ // src/nodes/configNodeArray.ts
86
+ var ConfigNodeArray = class {
87
+ arrayValues;
88
+ constructor(arrayValues) {
89
+ this.arrayValues = arrayValues;
90
+ }
91
+ };
92
+ var configNodeArray_default = ConfigNodeArray;
42
93
 
43
94
  // src/types.ts
44
95
  var InvalidValue = class {
@@ -55,57 +106,175 @@ var ArrayValueContainer = class {
55
106
  };
56
107
  var arrayOption_default = ArrayValueContainer;
57
108
 
58
- // src/option/base.ts
109
+ // src/fileLoader.ts
59
110
  var fs = __toESM(require("fs"));
60
111
  var import_js_yaml = __toESM(require("js-yaml"));
61
-
62
- // src/nodes/configNode.ts
63
- var ConfigNode = class {
64
- value;
65
- path;
66
- source_type;
67
- file;
68
- variable_name;
69
- arg_name;
70
- constructor(value, path, source_type, file, variable_name, arg_name) {
71
- this.value = value;
72
- this.path = path;
73
- this.source_type = source_type;
74
- this.file = file;
75
- this.variable_name = variable_name;
76
- this.arg_name = arg_name;
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);
77
174
  }
78
175
  };
79
- var configNode_default = ConfigNode;
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
+ }
80
205
 
81
206
  // src/utils.ts
82
207
  function valueIsInvalid(val) {
83
208
  return val instanceof InvalidValue || val === null || val === void 0;
84
209
  }
85
210
 
86
- // src/option/errors.ts
87
- var _OptionErrors = class {
88
- static clearAll() {
89
- _OptionErrors.errors = [];
90
- _OptionErrors.warnings = [];
91
- }
92
- };
93
- var OptionErrors = _OptionErrors;
94
- __publicField(OptionErrors, "errors", []);
95
- __publicField(OptionErrors, "warnings", []);
96
-
97
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
+ }
98
267
  var OptionBase = class {
99
268
  params;
100
269
  constructor(params) {
101
270
  this.params = params;
102
271
  }
103
- getValue(sourceFile, env, args, path, defaultValues, objectFromArray) {
104
- const ident = path.join(".");
272
+ getValue(sourceFile, env, args, path2, defaultValues, objectFromArray, envFileResults, errors) {
273
+ const ident = path2.join(".");
105
274
  if (this.params.cli && args) {
106
275
  if (ident in args) {
107
276
  return new configNode_default(
108
- this.checkType(args[ident], path, "args"),
277
+ this.checkType(args[ident], path2, "args", errors),
109
278
  ident,
110
279
  "args",
111
280
  null,
@@ -118,8 +287,25 @@ var OptionBase = class {
118
287
  if (this.params.env in env) {
119
288
  const val = env[this.params.env];
120
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
+ }
121
307
  return new configNode_default(
122
- this.checkType(val, path, "env"),
308
+ this.checkType(val, path2, "env", errors),
123
309
  ident,
124
310
  "env",
125
311
  null,
@@ -130,88 +316,52 @@ var OptionBase = class {
130
316
  }
131
317
  }
132
318
  if (typeof sourceFile === "string") {
133
- const data = import_js_yaml.default.load(
134
- 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
135
327
  );
136
- const val = this.findInObject(data || {}, path);
137
- if (val instanceof arrayOption_default) {
138
- return new configNode_default(
139
- this.checkType(val, path, sourceFile),
140
- ident,
141
- "file",
142
- sourceFile,
143
- null,
144
- null
145
- );
146
- }
147
- if (!valueIsInvalid(val)) {
148
- return new configNode_default(
149
- this.checkType(val, path, sourceFile),
150
- ident,
151
- "file",
152
- sourceFile,
153
- null,
154
- null
155
- );
156
- }
328
+ if (node) return node;
157
329
  }
158
330
  if (Array.isArray(sourceFile)) {
159
331
  for (let index = 0; index < sourceFile.length; index += 1) {
160
332
  const file = sourceFile[index];
161
- const data = import_js_yaml.default.load(
162
- 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
163
341
  );
164
- const val = this.findInObject(data || {}, path);
165
- if (val instanceof arrayOption_default) {
166
- return new configNode_default(
167
- this.checkType(val, path, file),
168
- ident,
169
- "file",
170
- file,
171
- null,
172
- null
173
- );
174
- }
175
- if (!valueIsInvalid(val)) {
176
- return new configNode_default(
177
- this.checkType(val, path, file),
178
- ident,
179
- "file",
180
- file,
181
- null,
182
- null
183
- );
184
- }
342
+ if (node) return node;
185
343
  }
186
344
  }
187
345
  if (objectFromArray) {
188
- const val = this.findInObject(objectFromArray.value, path);
189
- if (val instanceof arrayOption_default) {
190
- return new configNode_default(
191
- this.checkType(val, path, objectFromArray.file),
192
- ident,
193
- "file",
194
- objectFromArray.file,
195
- null,
196
- null
197
- );
198
- }
199
- if (!valueIsInvalid(val)) {
200
- return new configNode_default(
201
- this.checkType(val, path, objectFromArray.file),
202
- ident,
203
- "file",
204
- objectFromArray.file,
205
- null,
206
- null
207
- );
208
- }
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;
209
355
  }
210
356
  if (defaultValues) {
211
- const val = this.findInObject(defaultValues, path);
357
+ const val = this.findInObject(
358
+ defaultValues,
359
+ path2,
360
+ errors
361
+ );
212
362
  if (val instanceof arrayOption_default) {
213
363
  return new configNode_default(
214
- this.checkType(val, path, "default"),
364
+ this.checkType(val, path2, "default", errors),
215
365
  ident,
216
366
  "default",
217
367
  null,
@@ -221,7 +371,7 @@ var OptionBase = class {
221
371
  }
222
372
  if (!valueIsInvalid(val)) {
223
373
  return new configNode_default(
224
- this.checkType(val, path, "default"),
374
+ this.checkType(val, path2, "default", errors),
225
375
  ident,
226
376
  "default",
227
377
  null,
@@ -232,17 +382,18 @@ var OptionBase = class {
232
382
  }
233
383
  if (this.params.defaultValue !== void 0) {
234
384
  let defaultValue;
235
- if (typeof this.params.defaultValue === "function") {
236
- defaultValue = this.params.defaultValue();
385
+ const rawDefault = this.params.defaultValue;
386
+ if (typeof rawDefault === "function") {
387
+ defaultValue = rawDefault();
237
388
  } else {
238
- defaultValue = this.params.defaultValue;
389
+ defaultValue = rawDefault;
239
390
  }
240
391
  if (this.params.kind === "array" && Array.isArray(defaultValue)) {
241
- defaultValue = this.buildArrayOption(defaultValue);
392
+ defaultValue = this.buildArrayOption(defaultValue, errors);
242
393
  }
243
394
  if (!valueIsInvalid(defaultValue)) {
244
395
  return new configNode_default(
245
- this.checkType(defaultValue, path, "default"),
396
+ this.checkType(defaultValue, path2, "default", errors),
246
397
  ident,
247
398
  "default",
248
399
  null,
@@ -252,32 +403,39 @@ var OptionBase = class {
252
403
  }
253
404
  }
254
405
  if (this.params.required) {
255
- 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
+ });
256
411
  }
257
412
  return null;
258
413
  }
259
- // eslint-disable-next-line class-methods-use-this
260
- checkNumberType(val, pathStr, sourceOfVal) {
261
- if (typeof val === "string") {
262
- const parseVal = parseInt(val, 10);
263
- if (Number.isNaN(parseVal)) {
264
- OptionErrors.errors.push(
265
- `Cannot convert value '${val}' for '${pathStr}' to number in ${sourceOfVal}.`
266
- );
267
- return new InvalidValue();
268
- }
269
- OptionErrors.warnings.push(
270
- `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
271
427
  );
272
- return parseVal;
273
428
  }
274
- OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
275
- return new InvalidValue();
429
+ return null;
276
430
  }
277
- checkType(val, path, sourceOfVal) {
278
- const ident = path.join(".");
431
+ checkType(val, path2, sourceOfVal, errors) {
432
+ const ident = path2.join(".");
279
433
  if (valueIsInvalid(val)) {
280
- 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
+ });
281
439
  return val;
282
440
  }
283
441
  if (typeof val === this.params.kind) {
@@ -285,14 +443,19 @@ var OptionBase = class {
285
443
  }
286
444
  if (this.params.kind === "string") {
287
445
  if (typeof val === "number") {
288
- OptionErrors.warnings.push(
446
+ errors?.warnings.push(
289
447
  `The option ${ident} is stated as a string but is provided as a number`
290
448
  );
291
449
  return val.toString();
292
450
  }
293
- OptionErrors.errors.push(
294
- `Cannot convert value '${val.toString()}' for '${ident}' to string in ${sourceOfVal}.`
295
- );
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
+ });
296
459
  return new InvalidValue();
297
460
  }
298
461
  if (this.params.kind === "boolean") {
@@ -304,73 +467,106 @@ var OptionBase = class {
304
467
  return false;
305
468
  }
306
469
  }
307
- OptionErrors.errors.push(
308
- `Cannot convert value '${val.toString()}' for '${ident}' to boolean in ${sourceOfVal}.`
309
- );
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
+ });
310
478
  return new InvalidValue();
311
479
  }
312
480
  if (this.params.kind === "number") {
313
- return this.checkNumberType(val, ident, sourceOfVal);
481
+ return checkNumberType(val, ident, sourceOfVal, errors);
314
482
  }
315
- if (this.params.kind === "any") {
316
- return val;
317
- }
318
- 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
+ });
319
488
  throw new Error(
320
- "Invalid kind. Must be 'string', 'number', 'boolean', 'array' or 'any'"
489
+ "Invalid kind. Must be 'string', 'number', 'boolean' or 'array'"
321
490
  );
322
491
  }
323
- findInObject(obj, path) {
324
- if (path.length > 1) {
325
- const [child, ...rest] = path;
492
+ findInObject(obj, path2, errors) {
493
+ if (path2.length > 1) {
494
+ const [child, ...rest] = path2;
326
495
  const val = obj[child];
327
496
  if (typeof val === "string") {
328
- 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
+ });
329
501
  return new InvalidValue();
330
502
  }
331
503
  if (typeof val === "number") {
332
- 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
+ });
333
508
  return new InvalidValue();
334
509
  }
335
510
  if (typeof val === "boolean") {
336
- OptionErrors.errors.push(
337
- `Cant get path from boolean value '${val.toString()}'`
338
- );
511
+ errors?.errors.push({
512
+ message: `Cant get path from boolean value '${val.toString()}'`,
513
+ kind: "invalid_path"
514
+ });
339
515
  return new InvalidValue();
340
516
  }
341
517
  if (Array.isArray(val)) {
342
- OptionErrors.errors.push(
343
- `Cant get path from array value '${val.toString()}'`
344
- );
518
+ errors?.errors.push({
519
+ message: `Cant get path from array value '${valueToString(val)}'`,
520
+ kind: "invalid_path"
521
+ });
345
522
  return new InvalidValue();
346
523
  }
347
524
  if (val == null) {
348
525
  return new InvalidValue();
349
526
  }
350
- return this.findInObject(val, rest);
527
+ return this.findInObject(val, rest, errors);
351
528
  }
352
- if (path.length === 1) {
353
- const val = obj[path[0]];
529
+ if (path2.length === 1) {
530
+ const val = obj[path2[0]];
354
531
  if (!Array.isArray(val) && typeof val === "object" && val || typeof val === "string" || typeof val === "number" || typeof val === "boolean" || typeof val === "undefined") {
355
532
  return val;
356
533
  }
357
534
  if (Array.isArray(val)) {
358
- return this.buildArrayOption(val);
535
+ return this.buildArrayOption(val, errors);
359
536
  }
360
- OptionErrors.errors.push(
361
- `Invalid path '${path.join(".")}': ${typeof val}`
362
- );
537
+ errors?.errors.push({
538
+ message: `Invalid path '${path2.join(".")}': ${typeof val}`,
539
+ kind: "invalid_path"
540
+ });
363
541
  return new InvalidValue();
364
542
  }
365
- OptionErrors.errors.push(`Invalid path '${path.join()}'`);
543
+ errors?.errors.push({
544
+ message: `Invalid path '${path2.join()}'`,
545
+ kind: "invalid_path"
546
+ });
366
547
  return new InvalidValue();
367
548
  }
368
549
  // eslint-disable-next-line class-methods-use-this
369
- buildArrayOption(_val) {
550
+ buildArrayOption(_val, _errors) {
370
551
  return new InvalidValue();
371
552
  }
372
553
  };
373
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
+
374
570
  // src/option/array.ts
375
571
  var ArrayOption = class extends OptionBase {
376
572
  item;
@@ -384,105 +580,186 @@ var ArrayOption = class extends OptionBase {
384
580
  });
385
581
  this.item = params.item;
386
582
  }
387
- buildArrayOption(val) {
583
+ buildArrayOption(val, errors) {
388
584
  if (this.item === null) {
389
- OptionErrors.errors.push(`Array item cannot be null`);
585
+ errors?.errors.push({
586
+ message: `Array item cannot be null`,
587
+ kind: "invalid_state"
588
+ });
390
589
  return new InvalidValue();
391
590
  }
392
591
  return new arrayOption_default(this.item, val);
393
592
  }
394
- // eslint-disable-next-line class-methods-use-this
395
- checkType(val, path, sourceOfVal) {
593
+ checkType(val, path2, sourceOfVal, errors) {
396
594
  if (val instanceof arrayOption_default) {
397
595
  val.val.forEach((v, i) => {
398
- if (this.item instanceof OptionBase) {
399
- 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);
400
598
  }
401
599
  });
402
600
  return val;
403
601
  }
404
- 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
+ });
405
607
  return new InvalidValue();
406
608
  }
407
609
  };
408
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
+
409
659
  // src/option/primitive.ts
410
660
  var PrimitiveOption = class extends OptionBase {
411
661
  };
412
662
 
413
- // src/settings.ts
414
- var import_commander = require("commander");
415
- var fs2 = __toESM(require("fs"));
416
-
417
- // src/nodes/configNodeArray.ts
418
- var ConfigNodeArray = class {
419
- arrayValues;
420
- constructor(arrayValues) {
421
- this.arrayValues = arrayValues;
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
+ }
422
686
  }
423
- };
424
- var configNodeArray_default = ConfigNodeArray;
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
+ }
425
727
 
426
728
  // src/settings.ts
427
729
  var Settings = class {
428
730
  schema;
429
731
  sources;
732
+ errors = new OptionErrors();
430
733
  sourceFile = [];
431
734
  argsData = {};
432
735
  envData = {};
433
736
  optionsTree = {};
434
737
  defaultData = {};
738
+ envFileResults = [];
435
739
  program;
436
- constructor(schema, sources) {
437
- this.schema = schema;
740
+ constructor(schema2, sources) {
741
+ this.schema = schema2;
438
742
  this.sources = sources;
439
743
  this.program = new import_commander.Command().allowUnknownOption(true).allowExcessArguments(true);
440
744
  this.load();
441
745
  }
442
- validateFiles() {
443
- const { files, dir } = this.sources;
444
- if (files && dir)
445
- throw new Error("Dir and files are specified, choose one");
446
- if (files) {
447
- if (Array.isArray(files)) {
448
- files.forEach((file) => {
449
- if (!fs2.existsSync(file)) {
450
- throw new Error(`Invalid config file '${file}'`);
451
- } else {
452
- if (!Array.isArray(this.sourceFile)) {
453
- this.sourceFile = [];
454
- }
455
- this.sourceFile.push(file);
456
- }
457
- });
458
- } else {
459
- if (!fs2.existsSync(files)) {
460
- throw new Error(`Invalid config file '${files}'`);
461
- }
462
- this.sourceFile = files;
463
- }
464
- }
465
- if (dir) {
466
- if (!(fs2.existsSync(dir) && fs2.lstatSync(dir).isDirectory())) {
467
- throw new Error(`'${dir}' not exists or is not a dir`);
468
- }
469
- const filesInDirectory = fs2.readdirSync(dir).sort();
470
- if (filesInDirectory.length === 0) {
471
- throw new Error(`Directory '${dir}' is empty`);
472
- }
473
- filesInDirectory.forEach((file) => {
474
- if (!Array.isArray(this.sourceFile)) {
475
- this.sourceFile = [];
476
- }
477
- this.sourceFile.push(`${dir}/${file}`);
478
- });
479
- }
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;
480
756
  }
481
757
  load() {
482
- this.validateFiles();
758
+ this.validateAndLoadFiles();
483
759
  if (this.sources.env) {
484
- this.envData = process.env;
760
+ this.envData = { ...process.env };
485
761
  }
762
+ this.loadAndMergeEnvFiles();
486
763
  if (this.sources.args) {
487
764
  this.traverseOptions(this.schema, [], this.addArg.bind(this));
488
765
  this.program.parse(process.argv);
@@ -498,44 +775,66 @@ var Settings = class {
498
775
  sourceFile: this.sourceFile,
499
776
  envData: this.envData,
500
777
  argsData: this.argsData,
501
- defaultValue: this.defaultData
778
+ defaultValue: this.defaultData,
779
+ envFileResults: this.envFileResults,
780
+ errors: this.errors
502
781
  })
503
782
  );
504
- if (OptionErrors.warnings.length > 0) {
505
- for (let index = 0; index < OptionErrors.warnings.length; index += 1) {
506
- 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]}`);
507
786
  }
508
787
  }
509
- if (OptionErrors.errors.length > 0) {
510
- for (let index = 0; index < OptionErrors.errors.length; index += 1) {
511
- 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);
512
794
  }
513
- process.exit(1);
795
+ throw new ConfigLoadError(
796
+ [...this.errors.errors],
797
+ [...this.errors.warnings]
798
+ );
514
799
  }
515
800
  }
516
- traverseOptions(node, path, callback) {
517
- if (node instanceof OptionBase) {
518
- 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);
519
809
  } else {
520
810
  Object.keys(node).forEach((key) => {
521
811
  const val = node[key];
522
- this.traverseOptions(val, [...path, key], callback);
812
+ this.traverseOptions(val, [...path2, key], callback);
523
813
  });
524
814
  }
525
815
  }
526
- buildOption(result, configData, node, path) {
527
- 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;
528
826
  const value = node.getValue(
529
827
  sourceFile,
530
828
  envData,
531
829
  argsData,
532
- path,
830
+ path2,
533
831
  defaultValue,
534
- objectFromArray
832
+ objectFromArray,
833
+ envFileResults,
834
+ errors
535
835
  );
536
- if (value === null) {
537
- } else {
538
- this.setOption(result, path, value);
836
+ if (value !== null) {
837
+ this.setOption(result, path2, value);
539
838
  }
540
839
  }
541
840
  getValidatedArray(item, values, file) {
@@ -548,27 +847,18 @@ var Settings = class {
548
847
  }
549
848
  if (item.params.kind === "boolean") {
550
849
  return values.map((v) => {
551
- if (v === "true")
552
- return true;
553
- if (v === "1")
554
- return true;
555
- if (v === 1)
556
- return true;
557
- if (v === "false")
558
- return false;
559
- if (v === "0")
560
- return false;
561
- if (v === 0)
562
- 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;
563
856
  return v;
564
857
  });
565
858
  }
566
859
  }
567
860
  const arrayValues = values.map(
568
- (v) => (
569
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
570
- this.processArrayWithSchema(item, v, file)
571
- )
861
+ (v) => this.processArrayWithSchema(item, v, file)
572
862
  );
573
863
  return new configNodeArray_default(arrayValues);
574
864
  }
@@ -579,30 +869,34 @@ var Settings = class {
579
869
  [],
580
870
  this.buildOption.bind(this, result, {
581
871
  objectFromArray: {
582
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
583
872
  value: v,
584
873
  file
585
- }
874
+ },
875
+ errors: this.errors
586
876
  })
587
877
  );
588
878
  return result;
589
879
  }
590
- setOption(options, path, node) {
591
- if (path.length > 1) {
592
- const [child, ...rest] = path;
880
+ setOption(options, path2, node) {
881
+ if (path2.length > 1) {
882
+ const [child, ...rest] = path2;
593
883
  if (!options[child]) {
594
884
  options[child] = {};
595
885
  }
596
- this.setOption(options[child], rest, node);
597
- } else if (path.length === 1) {
598
- const [child] = path;
886
+ this.setOption(
887
+ options[child],
888
+ rest,
889
+ node
890
+ );
891
+ } else if (path2.length === 1) {
892
+ const [child] = path2;
599
893
  if (node != null) {
600
894
  if (node.value instanceof arrayOption_default) {
601
895
  options[child] = node;
602
896
  options[child].value = this.getValidatedArray(
603
897
  node.value.item,
604
898
  node.value.val,
605
- node.file || node.variable_name || node.arg_name || ""
899
+ node.file || node.variableName || node.argName || ""
606
900
  );
607
901
  } else {
608
902
  options[child] = node;
@@ -610,13 +904,13 @@ var Settings = class {
610
904
  }
611
905
  } else {
612
906
  throw new Error(
613
- `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}`
614
908
  );
615
909
  }
616
910
  }
617
- addArg(node, path = []) {
911
+ addArg(node, path2 = []) {
618
912
  if (node.params.cli) {
619
- const ident = path.join(".");
913
+ const ident = path2.join(".");
620
914
  this.program.option(`--${ident} <value>`, node.params.help);
621
915
  }
622
916
  }
@@ -637,7 +931,9 @@ var Settings = class {
637
931
  );
638
932
  }
639
933
  get() {
640
- return this.getValuesFromTree(this.optionsTree);
934
+ return this.getValuesFromTree(
935
+ this.optionsTree
936
+ );
641
937
  }
642
938
  getExtended() {
643
939
  return this.optionsTree;
@@ -645,14 +941,28 @@ var Settings = class {
645
941
  };
646
942
  var settings_default = Settings;
647
943
 
944
+ // src/builder/settings.ts
945
+ var SettingsBuilder = class {
946
+ schema;
947
+ constructor(schema2) {
948
+ this.schema = schema2;
949
+ }
950
+ load(sources) {
951
+ const settings = new settings_default(this.schema, sources);
952
+ return settings.get();
953
+ }
954
+ loadExtended(sources) {
955
+ const settings = new settings_default(this.schema, sources);
956
+ return settings.getExtended();
957
+ }
958
+ };
959
+
648
960
  // src/index.ts
649
- var src_default = settings_default;
650
961
  var DEFAULTS = {
651
962
  required: false,
652
963
  env: null,
653
964
  cli: false,
654
965
  help: ""
655
- // properties: {},
656
966
  };
657
967
  var string = (opts) => {
658
968
  return new PrimitiveOption({
@@ -681,14 +991,26 @@ var array = (opts) => {
681
991
  ...opts
682
992
  });
683
993
  };
994
+ var object = (opts) => {
995
+ return new ObjectOption({
996
+ required: false,
997
+ ...opts
998
+ });
999
+ };
1000
+ var schema = (theSchema) => {
1001
+ return new SettingsBuilder(theSchema);
1002
+ };
684
1003
  var option = {
685
1004
  string,
686
1005
  number,
687
1006
  bool,
688
- // object,
689
- array
1007
+ array,
1008
+ object,
1009
+ schema
690
1010
  };
1011
+ var index_default = option;
691
1012
  // Annotate the CommonJS export names for ESM import in node:
692
1013
  0 && (module.exports = {
693
- option
1014
+ ConfigFileError,
1015
+ ConfigLoadError
694
1016
  });