@meltstudio/config-loader 1.0.0 → 1.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.
package/README.md CHANGED
@@ -1,19 +1,18 @@
1
1
  # Config Loader
2
2
  ## Project Description
3
3
 
4
- The Config Loader package is a powerful and user-friendly tool that simplifies the process of retrieving and collecting variables from one or multiple files for your project. It provides an efficient way to extract specific information from files and access those variables in your code. The resulting data is in JSON format, making it easy to work with in various applications.
4
+ The Config Loader package is a powerful and user-friendly tool that simplifies the process of retrieving and collecting variables from one or multiple files for your project. It provides an efficient way to extract specific information from files and access those variables in your code. The result is a JSON object, making it easy to work with in various applications.
5
5
 
6
6
  ## Features
7
7
  - Retrieve and collect variables from one or multiple files in your project.
8
- - Yaml file support, support for JSON,YAML, XML files coming soon.
9
- - Allows you to specify filters to select only the desired variables.
10
- - Compatible with TypeScript environments, making it suitable for Node.js or browser-based projects.
8
+ - YAML file support (support for other file types coming soon.)
9
+ - Data can also be retrieved from CLI or environment variables .
10
+ - Compatible with TypeScript/JavaScript environments, making it suitable for Node.js projects.
11
11
 
12
12
  ## Table of Contents
13
13
 
14
14
  - [Installation](#installation)
15
15
  - [Usage](#usage)
16
- - [Contributing](#contributing)
17
16
  - [License](#license)
18
17
  - [Acknowledgements](#acknowledgements)
19
18
 
@@ -43,6 +42,7 @@ import Settings, { option } from "@/src";
43
42
  const run = (): void => {
44
43
  const settings = new Settings(
45
44
  {
45
+ version: option.string({ required: true, cli: true }),
46
46
  website: {
47
47
  title: option.string({ required: true }),
48
48
  url: option.string({
@@ -66,12 +66,10 @@ const run = (): void => {
66
66
  }),
67
67
  features: option.array({
68
68
  required: true,
69
- item: option.object({
70
- item: {
71
- cosa: option.string(),
72
- test: option.bool(),
73
- },
74
- }),
69
+ item: {
70
+ name: option.string(),
71
+ enabled: option.bool(),
72
+ },
75
73
  }),
76
74
  },
77
75
  {
@@ -86,9 +84,40 @@ const run = (): void => {
86
84
 
87
85
  run();
88
86
  ```
87
+
88
+ With a config.yaml file with the following contents:
89
+ ```yaml
90
+ version: 1.0.0
91
+ website:
92
+ title: My Website
93
+ description: A simple and elegant website
94
+ port: 3000
95
+ isProduction: false
96
+
97
+ database:
98
+ host: localhost
99
+ port: 5432
100
+ credentials:
101
+ username: admin
102
+ password: secret
103
+
104
+ socialMedia: [https://twitter.com/example, https://instagram.com/example]
105
+
106
+ features:
107
+ - name: Store
108
+ enabled: true
109
+ - name: Admin
110
+ enabled: false
111
+
112
+ apiKeys:
113
+ googleMaps: ${GOOGLE_MAPS_API_KEY}
114
+ sendGrid: ${SENDGRID_API_KEY}
115
+ ```
116
+
89
117
  The expected output would be:
90
118
  ```json
91
119
  {
120
+ "version": "1.0.0",
92
121
  "website": {
93
122
  "title": "My Website",
94
123
  "url": "www.mywebsite.dev",
@@ -126,7 +155,7 @@ You can try executing our example in your project by following these steps with
126
155
  yarn example:run
127
156
  ```
128
157
  ### Usage with CLI
129
- When using our package with cli, is important have the cli attribute must be set to true.
158
+ When using our package with cli, it is important to have the cli attribute set to true.
130
159
  This will allow values to be sent when running the package from the command line.
131
160
  ```typescript
132
161
  import path from "path";
@@ -157,14 +186,19 @@ now for use it you need to send the property name on the command line with the n
157
186
  ```bash
158
187
  yarn example:run --version 2.0.0
159
188
  ```
189
+ Having the following config.yaml file:
190
+ ```yaml
191
+ version: 1.0.0
192
+ ```
160
193
  The expected output would be:
161
194
  ```json
162
195
  {
163
196
  "version": "2.0.0",
164
197
  }
165
198
  ```
199
+ You can see that the CLI variable overrode the yaml file variable
166
200
  ### Usage with Environment Variables
167
- The Config Loader package allows you to harness the power of environment variables in your system configuration. You can specify variable names in your configuration and get them. to use this feature you need to set **env: true**
201
+ The Config Loader package allows you to use environment variables in your system configuration. You can specify variable names in your configuration and get them. To use this feature you need to set **env: true**
168
202
  ```typescript
169
203
  import path from "path";
170
204
 
@@ -197,10 +231,19 @@ const run = (): void => {
197
231
 
198
232
  run();
199
233
  ```
234
+ With the following config.yaml file:
235
+ ```yaml
236
+ database:
237
+ host: localhost
238
+ port: 5432
239
+ credentials:
240
+ username: admin
241
+ password: IGNORED_PASSWORD
242
+ ```
200
243
  ```bash
201
244
  yarn example:run
202
245
  ```
203
- The expected output would be:
246
+ If you have the environment variable `DB_PASSWORD=ENV_USED_PASSWORD`, the expected output would be:
204
247
  ```json
205
248
  {
206
249
  "database": {
@@ -208,29 +251,11 @@ The expected output would be:
208
251
  "port": 5432,
209
252
  "credentials": {
210
253
  "username": "admin",
211
- "password": "MY_PASSWORD"
254
+ "password": "ENV_USED_PASSWORD"
212
255
  }
213
256
  }
214
257
  }
215
258
  ```
216
-
217
- ## Contributing
218
- Explain how others can contribute to the project. Include guidelines for submitting issues, pull requests, or feature requests.
219
-
220
- 1. Fork the repository
221
- 2. Create a new branch
222
- 3. Implement your changes
223
- 4. Open a pull request
224
-
259
+ You can notice that the environment variable overrode the value in the config.yaml file
225
260
  ## License
226
261
  This package is licensed under the Apache License 2.0. For more information, please see the [LICENSE](./LICENSE) file.
227
-
228
- ## Acknowledgements
229
-
230
- 🙌 We would like to express our gratitude to the following individuals and resources that have contributed to this project:
231
-
232
- - [Manuael Zapata](https://github.com/author1) 🚀: Co-creator and lead developer of the project.
233
- - [Pablo Piedrahita](https://github.com/author2) 👏: Co-creator and lead developer of the project.
234
-
235
-
236
- Thank you all for your valuable contributions and support! 🎉
@@ -0,0 +1,130 @@
1
+ type SourceTypes = "file" | "env" | "args" | "default";
2
+ declare class ConfigNode {
3
+ value: Value | ArrayValueContainer;
4
+ path: string;
5
+ source_type: SourceTypes;
6
+ file: string | null;
7
+ variable_name: string | null;
8
+ arg_name: string | null;
9
+ constructor(value: Value | ArrayValue, path: string, source_type: SourceTypes, file: string | null, variable_name: string | null, arg_name: string | null);
10
+ }
11
+
12
+ type NodeTree = {
13
+ [key: string]: NodeTree | ConfigNode;
14
+ };
15
+ type SettingsSources<T> = {
16
+ env: boolean;
17
+ args: boolean;
18
+ files?: string | string[] | false;
19
+ dir?: string | false;
20
+ defaults?: Partial<T>;
21
+ };
22
+ type OptionKind = "boolean" | "string" | "number" | "any" | "array" | "object";
23
+ type Path = Array<string | number>;
24
+ type ConfigFileStructure<T> = {
25
+ [key: string]: string | T | number | boolean | Array<T> | string[];
26
+ };
27
+ interface ConfigFileData extends ConfigFileStructure<ConfigFileData> {
28
+ }
29
+ type ArrayValue = Array<any>;
30
+ declare class InvalidValue {
31
+ }
32
+
33
+ declare class ArrayValueContainer {
34
+ readonly val: ArrayValue;
35
+ readonly item: Node | OptionTypes;
36
+ constructor(item: Node | OptionTypes, val: ArrayValue);
37
+ }
38
+
39
+ type Value = boolean | string | number | object | InvalidValue;
40
+ type DefaultValue = Value | (() => string) | (() => number);
41
+ type RecursiveNode<T> = {
42
+ [key: string]: OptionBase | T;
43
+ };
44
+ interface Node extends RecursiveNode<Node> {
45
+ }
46
+ interface OptionClassParams {
47
+ kind: OptionKind;
48
+ required: boolean;
49
+ env: string | null;
50
+ cli: boolean;
51
+ help: string;
52
+ defaultValue?: DefaultValue;
53
+ }
54
+ declare class OptionBase {
55
+ readonly params: OptionClassParams;
56
+ constructor(params: OptionClassParams);
57
+ getValue<T>(sourceFile: string | string[], env: {
58
+ [key: string]: string | undefined;
59
+ }, args: {
60
+ [key: string]: string | boolean;
61
+ }, path: Path, defaultValues?: Partial<T>, objectFromArray?: {
62
+ value: ConfigFileData;
63
+ file: string;
64
+ }): ConfigNode | null;
65
+ protected checkNumberType(val: Value, pathStr: string, sourceOfVal: string): Value;
66
+ checkType(val: Value, path: Path, sourceOfVal: string): Value;
67
+ protected findInObject(obj: ConfigFileData, path: Path): Value | ArrayValue;
68
+ buildArrayOption(_val: string[] | ConfigFileData[]): ArrayValueContainer | InvalidValue;
69
+ }
70
+
71
+ interface ArrayOptionClassParams {
72
+ required: boolean;
73
+ defaultValue?: DefaultValue;
74
+ item: Node | OptionTypes;
75
+ }
76
+ declare class ArrayOption extends OptionBase {
77
+ item: Node | OptionTypes;
78
+ constructor(params: ArrayOptionClassParams);
79
+ buildArrayOption(val: string[] | ConfigFileData[]): ArrayValueContainer | InvalidValue;
80
+ checkType(val: Value, path: Path, sourceOfVal: string): Value;
81
+ }
82
+
83
+ declare class PrimitiveOption extends OptionBase {
84
+ }
85
+
86
+ type OptionTypes = PrimitiveOption | ArrayOption;
87
+
88
+ declare class Settings<T> {
89
+ private readonly schema;
90
+ private readonly sources;
91
+ private sourceFile;
92
+ private argsData;
93
+ private envData;
94
+ private optionsTree;
95
+ private defaultData;
96
+ private program;
97
+ constructor(schema: Node, sources: SettingsSources<T>);
98
+ private validateFiles;
99
+ private load;
100
+ private traverseOptions;
101
+ private buildOption;
102
+ private getValidatedArray;
103
+ private processArrayWithSchema;
104
+ private setOption;
105
+ private addArg;
106
+ private getValuesFromTree;
107
+ get(): T;
108
+ getExtended(): NodeTree;
109
+ }
110
+
111
+ interface OptionPropsArgs {
112
+ required?: boolean;
113
+ env?: string | null;
114
+ cli?: boolean;
115
+ defaultValue?: DefaultValue;
116
+ help?: string;
117
+ }
118
+ interface ArrayOptionPropsArgs {
119
+ required?: boolean;
120
+ item: Node | OptionTypes;
121
+ defaultValue?: DefaultValue;
122
+ }
123
+ declare const option: {
124
+ string: (opts?: OptionPropsArgs) => PrimitiveOption;
125
+ number: (opts?: OptionPropsArgs) => PrimitiveOption;
126
+ bool: (opts?: OptionPropsArgs) => PrimitiveOption;
127
+ array: (opts: ArrayOptionPropsArgs) => ArrayOption;
128
+ };
129
+
130
+ export { Settings as default, option };
package/dist/index.js ADDED
@@ -0,0 +1,694 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
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
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ 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
+
35
+ // src/index.ts
36
+ var src_exports = {};
37
+ __export(src_exports, {
38
+ default: () => src_default,
39
+ option: () => option
40
+ });
41
+ module.exports = __toCommonJS(src_exports);
42
+
43
+ // src/types.ts
44
+ var InvalidValue = class {
45
+ };
46
+
47
+ // src/option/arrayOption.ts
48
+ var ArrayValueContainer = class {
49
+ val;
50
+ item;
51
+ constructor(item, val) {
52
+ this.val = val;
53
+ this.item = item;
54
+ }
55
+ };
56
+ var arrayOption_default = ArrayValueContainer;
57
+
58
+ // src/option/base.ts
59
+ var fs = __toESM(require("fs"));
60
+ 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;
77
+ }
78
+ };
79
+ var configNode_default = ConfigNode;
80
+
81
+ // src/utils.ts
82
+ function valueIsInvalid(val) {
83
+ return val instanceof InvalidValue || val === null || val === void 0;
84
+ }
85
+
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
+ // src/option/base.ts
98
+ var OptionBase = class {
99
+ params;
100
+ constructor(params) {
101
+ this.params = params;
102
+ }
103
+ getValue(sourceFile, env, args, path, defaultValues, objectFromArray) {
104
+ const ident = path.join(".");
105
+ if (this.params.cli && args) {
106
+ if (ident in args) {
107
+ return new configNode_default(
108
+ this.checkType(args[ident], path, "args"),
109
+ ident,
110
+ "args",
111
+ null,
112
+ null,
113
+ ident
114
+ );
115
+ }
116
+ }
117
+ if (this.params.env && env) {
118
+ if (this.params.env in env) {
119
+ const val = env[this.params.env];
120
+ if (val) {
121
+ return new configNode_default(
122
+ this.checkType(val, path, "env"),
123
+ ident,
124
+ "env",
125
+ null,
126
+ this.params.env,
127
+ null
128
+ );
129
+ }
130
+ }
131
+ }
132
+ if (typeof sourceFile === "string") {
133
+ const data = import_js_yaml.default.load(
134
+ fs.readFileSync(sourceFile, "utf-8")
135
+ );
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
+ }
157
+ }
158
+ if (Array.isArray(sourceFile)) {
159
+ for (let index = 0; index < sourceFile.length; index += 1) {
160
+ const file = sourceFile[index];
161
+ const data = import_js_yaml.default.load(
162
+ fs.readFileSync(file, "utf-8")
163
+ );
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
+ }
185
+ }
186
+ }
187
+ 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
+ }
209
+ }
210
+ if (defaultValues) {
211
+ const val = this.findInObject(defaultValues, path);
212
+ if (val instanceof arrayOption_default) {
213
+ return new configNode_default(
214
+ this.checkType(val, path, "default"),
215
+ ident,
216
+ "default",
217
+ null,
218
+ null,
219
+ null
220
+ );
221
+ }
222
+ if (!valueIsInvalid(val)) {
223
+ return new configNode_default(
224
+ this.checkType(val, path, "default"),
225
+ ident,
226
+ "default",
227
+ null,
228
+ null,
229
+ null
230
+ );
231
+ }
232
+ }
233
+ if (this.params.defaultValue !== void 0) {
234
+ if (typeof this.params.defaultValue === "function") {
235
+ return new configNode_default(
236
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
237
+ this.checkType(this.params.defaultValue(), path, "default"),
238
+ ident,
239
+ "default",
240
+ null,
241
+ null,
242
+ null
243
+ );
244
+ }
245
+ return new configNode_default(
246
+ this.checkType(this.params.defaultValue, path, "default"),
247
+ ident,
248
+ "default",
249
+ null,
250
+ null,
251
+ null
252
+ );
253
+ }
254
+ if (this.params.required) {
255
+ OptionErrors.errors.push(`Required option '${ident}' not provided.`);
256
+ }
257
+ return null;
258
+ }
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`
271
+ );
272
+ return parseVal;
273
+ }
274
+ OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
275
+ return new InvalidValue();
276
+ }
277
+ checkType(val, path, sourceOfVal) {
278
+ const ident = path.join(".");
279
+ if (valueIsInvalid(val)) {
280
+ OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
281
+ return val;
282
+ }
283
+ if (typeof val === this.params.kind) {
284
+ return val;
285
+ }
286
+ if (this.params.kind === "string") {
287
+ if (typeof val === "number") {
288
+ OptionErrors.warnings.push(
289
+ `The option ${ident} is stated as a string but is provided as a number`
290
+ );
291
+ return val.toString();
292
+ }
293
+ OptionErrors.errors.push(
294
+ `Cannot convert value '${val.toString()}' for '${ident}' to string in ${sourceOfVal}.`
295
+ );
296
+ return new InvalidValue();
297
+ }
298
+ if (this.params.kind === "boolean") {
299
+ if (typeof val !== "boolean" && typeof val !== "object") {
300
+ if ([1, "1", "true"].includes(val)) {
301
+ return true;
302
+ }
303
+ if ([0, "0", "false"].includes(val)) {
304
+ return false;
305
+ }
306
+ }
307
+ OptionErrors.errors.push(
308
+ `Cannot convert value '${val.toString()}' for '${ident}' to boolean in ${sourceOfVal}.`
309
+ );
310
+ return new InvalidValue();
311
+ }
312
+ if (this.params.kind === "number") {
313
+ return this.checkNumberType(val, ident, sourceOfVal);
314
+ }
315
+ if (this.params.kind === "any") {
316
+ return val;
317
+ }
318
+ OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
319
+ throw new Error(
320
+ "Invalid kind. Must be 'string', 'number', 'boolean', 'array' or 'any'"
321
+ );
322
+ }
323
+ findInObject(obj, path) {
324
+ if (path.length > 1) {
325
+ const [child, ...rest] = path;
326
+ const val = obj[child];
327
+ if (typeof val === "string") {
328
+ OptionErrors.errors.push(`Cant get path from string value '${val}'`);
329
+ return new InvalidValue();
330
+ }
331
+ if (typeof val === "number") {
332
+ OptionErrors.errors.push(`Cant get path from number value '${val}'`);
333
+ return new InvalidValue();
334
+ }
335
+ if (typeof val === "boolean") {
336
+ OptionErrors.errors.push(
337
+ `Cant get path from boolean value '${val.toString()}'`
338
+ );
339
+ return new InvalidValue();
340
+ }
341
+ if (Array.isArray(val)) {
342
+ OptionErrors.errors.push(
343
+ `Cant get path from array value '${val.toString()}'`
344
+ );
345
+ return new InvalidValue();
346
+ }
347
+ if (val == null) {
348
+ return new InvalidValue();
349
+ }
350
+ return this.findInObject(val, rest);
351
+ }
352
+ if (path.length === 1) {
353
+ const val = obj[path[0]];
354
+ if (!Array.isArray(val) && typeof val === "object" && val || typeof val === "string" || typeof val === "number" || typeof val === "boolean" || typeof val === "undefined") {
355
+ return val;
356
+ }
357
+ if (Array.isArray(val)) {
358
+ return this.buildArrayOption(val);
359
+ }
360
+ OptionErrors.errors.push(
361
+ `Invalid path '${path.join(".")}': ${typeof val}`
362
+ );
363
+ return new InvalidValue();
364
+ }
365
+ OptionErrors.errors.push(`Invalid path '${path.join()}'`);
366
+ return new InvalidValue();
367
+ }
368
+ // eslint-disable-next-line class-methods-use-this
369
+ buildArrayOption(_val) {
370
+ return new InvalidValue();
371
+ }
372
+ };
373
+
374
+ // src/option/array.ts
375
+ var ArrayOption = class extends OptionBase {
376
+ item;
377
+ constructor(params) {
378
+ super({
379
+ kind: "array",
380
+ env: null,
381
+ cli: false,
382
+ help: "",
383
+ ...params
384
+ });
385
+ this.item = params.item;
386
+ }
387
+ buildArrayOption(val) {
388
+ if (this.item === null) {
389
+ OptionErrors.errors.push(`Array item cannot be null`);
390
+ return new InvalidValue();
391
+ }
392
+ return new arrayOption_default(this.item, val);
393
+ }
394
+ // eslint-disable-next-line class-methods-use-this
395
+ checkType(val, path, sourceOfVal) {
396
+ if (val instanceof arrayOption_default) {
397
+ val.val.forEach((v, i) => {
398
+ if (this.item instanceof OptionBase) {
399
+ this.item.checkType(v, [...path, i], sourceOfVal);
400
+ }
401
+ });
402
+ return val;
403
+ }
404
+ OptionErrors.errors.push(`Invalid state. Invalid kind in ${sourceOfVal}`);
405
+ return new InvalidValue();
406
+ }
407
+ };
408
+
409
+ // src/option/primitive.ts
410
+ var PrimitiveOption = class extends OptionBase {
411
+ };
412
+
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;
422
+ }
423
+ };
424
+ var configNodeArray_default = ConfigNodeArray;
425
+
426
+ // src/settings.ts
427
+ var Settings = class {
428
+ schema;
429
+ sources;
430
+ sourceFile = [];
431
+ argsData = {};
432
+ envData = {};
433
+ optionsTree = {};
434
+ defaultData = {};
435
+ program;
436
+ constructor(schema, sources) {
437
+ this.schema = schema;
438
+ this.sources = sources;
439
+ this.program = new import_commander.Command();
440
+ this.load();
441
+ }
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);
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.unshift(`${dir}/${file}`);
478
+ });
479
+ }
480
+ }
481
+ load() {
482
+ this.validateFiles();
483
+ if (this.sources.env) {
484
+ this.envData = process.env;
485
+ }
486
+ if (this.sources.args) {
487
+ this.traverseOptions(this.schema, [], this.addArg.bind(this));
488
+ this.program.parse(process.argv);
489
+ this.argsData = this.program.opts();
490
+ }
491
+ if (this.sources.defaults) {
492
+ this.defaultData = this.sources.defaults;
493
+ }
494
+ this.traverseOptions(
495
+ this.schema,
496
+ [],
497
+ this.buildOption.bind(this, this.optionsTree, {
498
+ sourceFile: this.sourceFile,
499
+ envData: this.envData,
500
+ argsData: this.argsData,
501
+ defaultValue: this.defaultData
502
+ })
503
+ );
504
+ if (OptionErrors.warnings.length > 0) {
505
+ for (let index = 0; index < OptionErrors.warnings.length; index += 1) {
506
+ console.warn(`[Warning]: ${OptionErrors.warnings[index]}`);
507
+ }
508
+ }
509
+ if (OptionErrors.errors.length > 0) {
510
+ for (let index = 0; index < OptionErrors.errors.length; index += 1) {
511
+ console.error(`[Error]: ${OptionErrors.errors[index]}`);
512
+ }
513
+ process.exit(1);
514
+ }
515
+ }
516
+ traverseOptions(node, path, callback) {
517
+ if (node instanceof OptionBase) {
518
+ callback(node, path);
519
+ } else {
520
+ Object.keys(node).forEach((key) => {
521
+ const val = node[key];
522
+ this.traverseOptions(val, [...path, key], callback);
523
+ });
524
+ }
525
+ }
526
+ buildOption(result, configData, node, path) {
527
+ const { sourceFile, envData, argsData, defaultValue, objectFromArray } = configData;
528
+ const value = node.getValue(
529
+ sourceFile,
530
+ envData,
531
+ argsData,
532
+ path,
533
+ defaultValue,
534
+ objectFromArray
535
+ );
536
+ if (value === null) {
537
+ } else {
538
+ this.setOption(result, path, value);
539
+ }
540
+ }
541
+ getValidatedArray(item, values, file) {
542
+ if (item instanceof PrimitiveOption) {
543
+ if (item.params.kind === "string") {
544
+ return values.map((v) => v);
545
+ }
546
+ if (item.params.kind === "number") {
547
+ return values.map((v) => parseInt(v, 10));
548
+ }
549
+ if (item.params.kind === "boolean") {
550
+ 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;
563
+ return v;
564
+ });
565
+ }
566
+ }
567
+ const arrayValues = values.map(
568
+ (v) => (
569
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
570
+ this.processArrayWithSchema(item, v, file)
571
+ )
572
+ );
573
+ return new configNodeArray_default(arrayValues);
574
+ }
575
+ processArrayWithSchema(item, v, file) {
576
+ const result = {};
577
+ this.traverseOptions(
578
+ item,
579
+ [],
580
+ this.buildOption.bind(this, result, {
581
+ objectFromArray: {
582
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
583
+ value: v,
584
+ file
585
+ }
586
+ })
587
+ );
588
+ return result;
589
+ }
590
+ setOption(options, path, node) {
591
+ if (path.length > 1) {
592
+ const [child, ...rest] = path;
593
+ if (!options[child]) {
594
+ options[child] = {};
595
+ }
596
+ this.setOption(options[child], rest, node);
597
+ } else if (path.length === 1) {
598
+ const [child] = path;
599
+ if (node != null) {
600
+ if (node.value instanceof arrayOption_default) {
601
+ options[child] = node;
602
+ options[child].value = this.getValidatedArray(
603
+ node.value.item,
604
+ node.value.val,
605
+ node.file || node.variable_name || node.arg_name || ""
606
+ );
607
+ } else {
608
+ options[child] = node;
609
+ }
610
+ }
611
+ } else {
612
+ throw new Error(
613
+ `Invalid path '${node.path}' getting from '${node.arg_name || node.file || node.variable_name || ""}' in ' ${node.source_type}`
614
+ );
615
+ }
616
+ }
617
+ addArg(node, path = []) {
618
+ if (node.params.cli) {
619
+ const ident = path.join(".");
620
+ this.program.option(`--${ident} <value>`, node.params.help);
621
+ }
622
+ }
623
+ getValuesFromTree(node) {
624
+ if (node instanceof configNode_default) {
625
+ if (node.value instanceof configNodeArray_default) {
626
+ return node.value.arrayValues.map(this.getValuesFromTree.bind(this));
627
+ }
628
+ return node.value;
629
+ }
630
+ return Object.entries(node).reduce(
631
+ (acc, item) => {
632
+ const [key, value] = item;
633
+ acc[key] = this.getValuesFromTree(value);
634
+ return acc;
635
+ },
636
+ {}
637
+ );
638
+ }
639
+ get() {
640
+ return this.getValuesFromTree(this.optionsTree);
641
+ }
642
+ getExtended() {
643
+ return this.optionsTree;
644
+ }
645
+ };
646
+ var settings_default = Settings;
647
+
648
+ // src/index.ts
649
+ var src_default = settings_default;
650
+ var DEFAULTS = {
651
+ required: false,
652
+ env: null,
653
+ cli: false,
654
+ help: ""
655
+ // properties: {},
656
+ };
657
+ var string = (opts) => {
658
+ return new PrimitiveOption({
659
+ kind: "string",
660
+ ...DEFAULTS,
661
+ ...opts
662
+ });
663
+ };
664
+ var number = (opts) => {
665
+ return new PrimitiveOption({
666
+ kind: "number",
667
+ ...DEFAULTS,
668
+ ...opts
669
+ });
670
+ };
671
+ var bool = (opts) => {
672
+ return new PrimitiveOption({
673
+ kind: "boolean",
674
+ ...DEFAULTS,
675
+ ...opts
676
+ });
677
+ };
678
+ var array = (opts) => {
679
+ return new ArrayOption({
680
+ ...DEFAULTS,
681
+ ...opts
682
+ });
683
+ };
684
+ var option = {
685
+ string,
686
+ number,
687
+ bool,
688
+ // object,
689
+ array
690
+ };
691
+ // Annotate the CommonJS export names for ESM import in node:
692
+ 0 && (module.exports = {
693
+ option
694
+ });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@meltstudio/config-loader",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Melt Studio's tool for loading configurations into a Node.js application.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "repository": "https://github.com/MeltStudio/config-loader",
8
8
  "author": "MeltStudio <dev@meltstudio.co>",
9
- "license": "MIT",
9
+ "license": "Apache-2.0",
10
10
  "keywords": [
11
11
  "configuration",
12
12
  "config",
@@ -20,12 +20,12 @@
20
20
  "README.md"
21
21
  ],
22
22
  "scripts": {
23
- "build": "rimraf ./dist && tsup src/index.ts --dts --config tsconfig.build.json && yarn replace-tspaths",
23
+ "build": "rimraf ./dist && tsup src/index.ts --dts --config tsconfig.build.json",
24
24
  "lint": "eslint --ext .ts --max-warnings=0 --fix .",
25
25
  "type-check": "tsc --noEmit",
26
26
  "test": "jest --verbose",
27
- "replace-tspaths": "./scripts/replace-tspaths/index.mjs",
28
- "example:run": "ts-node -r tsconfig-paths/register ./example/index.ts"
27
+ "example:run": "ts-node -r tsconfig-paths/register ./example/index.ts",
28
+ "prepare": "husky install"
29
29
  },
30
30
  "dependencies": {
31
31
  "@types/js-yaml": "^4.0.5",