@rosen-bridge/config 0.4.0 → 1.1.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.
- package/CHANGELOG.md +18 -0
- package/dist/cli.js +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -6
- package/dist/schema/Validators/fieldProperties.d.ts +10 -10
- package/dist/schema/Validators/fieldProperties.d.ts.map +1 -1
- package/dist/schema/Validators/fieldProperties.js +21 -11
- package/dist/utils.js +1 -1
- package/dist/value/validators.d.ts.map +1 -1
- package/dist/value/validators.js +11 -6
- package/package.json +22 -16
- package/.eslintignore +0 -1
- package/lib/cli.ts +0 -113
- package/lib/config.ts +0 -733
- package/lib/index.ts +0 -1
- package/lib/schema/Validators/fieldProperties.ts +0 -365
- package/lib/schema/types/fields.ts +0 -54
- package/lib/schema/types/validations.ts +0 -63
- package/lib/utils.ts +0 -68
- package/lib/value/validators.ts +0 -273
- package/tests/.gitkeep +0 -0
- package/tests/config.spec.ts +0 -1363
- package/tests/configEnvSetup.ts +0 -34
- package/tests/configTestData.ts +0 -1735
- package/tests/configTestFiles/custom-environment-variables.json +0 -5
- package/tests/configTestFiles/default.json +0 -12
- package/tests/configTestFiles/local.json +0 -5
- package/tests/utils.spec.ts +0 -117
- package/tests/utilsTestData.ts +0 -26
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -8
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -18
package/lib/config.ts
DELETED
|
@@ -1,733 +0,0 @@
|
|
|
1
|
-
import { IConfig, IConfigSource } from 'config';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as yaml from 'js-yaml';
|
|
4
|
-
import JsonBigIntFactory from 'json-bigint';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import {
|
|
7
|
-
propertyValidators,
|
|
8
|
-
supportedTypes,
|
|
9
|
-
} from './schema/Validators/fieldProperties';
|
|
10
|
-
import { ConfigField, ConfigSchema } from './schema/types/fields';
|
|
11
|
-
import { When } from './schema/types/validations';
|
|
12
|
-
import { getSourceName, getValueFromConfigSources } from './utils';
|
|
13
|
-
import { valueValidations, valueValidators } from './value/validators';
|
|
14
|
-
|
|
15
|
-
export class ConfigValidator {
|
|
16
|
-
constructor(private schema: ConfigSchema) {
|
|
17
|
-
this.validateSchema();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* validates the passed config against the instance's schema
|
|
22
|
-
*
|
|
23
|
-
* @param {Record<string, any>} config
|
|
24
|
-
*/
|
|
25
|
-
public validateConfig(config: Record<string, any>) {
|
|
26
|
-
this.validateValue(
|
|
27
|
-
config,
|
|
28
|
-
{ type: 'object', children: this.schema },
|
|
29
|
-
config
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
this.validateSubConfig(config, config, this.schema, []);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* traverses and validates a subconfig using the subschema
|
|
37
|
-
*
|
|
38
|
-
* @private
|
|
39
|
-
* @param {Record<string, any>} config
|
|
40
|
-
* @param {Record<string, any>} subConfig
|
|
41
|
-
* @param {ConfigSchema} subSchema
|
|
42
|
-
* @param {string[]} path
|
|
43
|
-
* @memberof ConfigValidator
|
|
44
|
-
*/
|
|
45
|
-
private validateSubConfig(
|
|
46
|
-
config: Record<string, any>,
|
|
47
|
-
subConfig: Record<string, any>,
|
|
48
|
-
subSchema: ConfigSchema,
|
|
49
|
-
path: string[]
|
|
50
|
-
) {
|
|
51
|
-
const errorPreamble = (path: Array<string>) =>
|
|
52
|
-
`config validation failed for "${path.join('.')}" field`;
|
|
53
|
-
for (const name of Object.keys(subSchema)) {
|
|
54
|
-
const childPath = path.concat([name]);
|
|
55
|
-
try {
|
|
56
|
-
const field = subSchema[name];
|
|
57
|
-
let value = undefined;
|
|
58
|
-
if (subConfig != undefined && Object.hasOwn(subConfig, name)) {
|
|
59
|
-
value = subConfig[name];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.validateValue(value, field, config);
|
|
63
|
-
|
|
64
|
-
// if a node/field is of type object and thus is a subtree, traverse it
|
|
65
|
-
if (field.type === 'object') {
|
|
66
|
-
this.validateSubConfig(config, value, field.children, childPath);
|
|
67
|
-
} else if (field.type === 'array') {
|
|
68
|
-
if (Array.isArray(value)) {
|
|
69
|
-
for (const item of value) {
|
|
70
|
-
ConfigValidator.modifyObject(config, item, childPath);
|
|
71
|
-
this.validateSubConfig(
|
|
72
|
-
config,
|
|
73
|
-
{ [name]: item },
|
|
74
|
-
{ [name]: field.items },
|
|
75
|
-
childPath
|
|
76
|
-
);
|
|
77
|
-
ConfigValidator.modifyObject(config, value, childPath);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch (error: any) {
|
|
82
|
-
throw new Error(`${errorPreamble(childPath)}: ${error.message}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* sets an object's specific subtree to the specified value
|
|
89
|
-
*
|
|
90
|
-
* @static
|
|
91
|
-
* @param {Record<string, any>} obj
|
|
92
|
-
* @param {*} newValue
|
|
93
|
-
* @param {string[]} path
|
|
94
|
-
* @return {*}
|
|
95
|
-
* @memberof ConfigValidator
|
|
96
|
-
*/
|
|
97
|
-
static modifyObject(obj: Record<string, any>, newValue: any, path: string[]) {
|
|
98
|
-
let value: any = obj;
|
|
99
|
-
for (const key of path.slice(0, -1)) {
|
|
100
|
-
if (value != undefined && Object.hasOwn(value, key)) {
|
|
101
|
-
value = value[key];
|
|
102
|
-
} else {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const lastKey = path.at(-1);
|
|
108
|
-
if (lastKey != undefined) {
|
|
109
|
-
value[lastKey] = newValue;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* validates a value in config object
|
|
115
|
-
*
|
|
116
|
-
* @private
|
|
117
|
-
* @param {*} value
|
|
118
|
-
* @param {ConfigField} field the field specification in schema
|
|
119
|
-
* @param {Record<string, any>} config the config object
|
|
120
|
-
*/
|
|
121
|
-
private validateValue = (
|
|
122
|
-
value: any,
|
|
123
|
-
field: ConfigField,
|
|
124
|
-
config: Record<string, any>
|
|
125
|
-
) => {
|
|
126
|
-
if (value != undefined) {
|
|
127
|
-
if (field.type === 'bigint') {
|
|
128
|
-
value = BigInt(value);
|
|
129
|
-
} else if (field.type === 'number' && value && !isNaN(value)) {
|
|
130
|
-
value = Number(value);
|
|
131
|
-
}
|
|
132
|
-
valueValidators[field.type](value, field);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
field.type !== 'object' &&
|
|
137
|
-
field.type !== 'array' &&
|
|
138
|
-
field.validations
|
|
139
|
-
) {
|
|
140
|
-
for (const validation of field.validations) {
|
|
141
|
-
const name = Object.keys(validation).filter(
|
|
142
|
-
(key) => key !== 'when' && key !== 'error'
|
|
143
|
-
)[0];
|
|
144
|
-
if (Object.hasOwn(valueValidations[field.type], name)) {
|
|
145
|
-
try {
|
|
146
|
-
valueValidations[field.type][name](value, validation, config, this);
|
|
147
|
-
} catch (error: any) {
|
|
148
|
-
if (validation.error != undefined) {
|
|
149
|
-
throw new Error(validation.error);
|
|
150
|
-
}
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* determines if a when clause in validations section of a schema field is
|
|
160
|
-
* satisfied
|
|
161
|
-
*
|
|
162
|
-
* @param {When} when
|
|
163
|
-
* @param {Record<string, any>} config
|
|
164
|
-
* @return {boolean}
|
|
165
|
-
*/
|
|
166
|
-
public isWhenTrue = (when: When, config: Record<string, any>): boolean => {
|
|
167
|
-
const pathParts = when.path.split('.');
|
|
168
|
-
const value = ConfigValidator.valueAt(config, pathParts);
|
|
169
|
-
return value != undefined && value === when.value;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* returns the value at specified path in config object
|
|
174
|
-
*
|
|
175
|
-
* @static
|
|
176
|
-
* @param {Record<string, any>} config
|
|
177
|
-
* @param {string[]} path
|
|
178
|
-
* @return {*}
|
|
179
|
-
*/
|
|
180
|
-
static valueAt = (config: Record<string, any>, path: string[]) => {
|
|
181
|
-
let value: any = config;
|
|
182
|
-
for (const key of path) {
|
|
183
|
-
if (value != undefined && Object.hasOwn(value, key)) {
|
|
184
|
-
value = value[key];
|
|
185
|
-
} else {
|
|
186
|
-
return undefined;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return value;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* validates this.schema and throws exception if any errors found
|
|
195
|
-
*/
|
|
196
|
-
private validateSchema = () => {
|
|
197
|
-
const errorPreamble = (path: Array<string>) =>
|
|
198
|
-
`Schema validation failed for "${path.join('.')}" field`;
|
|
199
|
-
|
|
200
|
-
const stack: Array<{
|
|
201
|
-
subSchema: ConfigSchema;
|
|
202
|
-
parentPath: Array<string>;
|
|
203
|
-
}> = [
|
|
204
|
-
{
|
|
205
|
-
subSchema: this.schema,
|
|
206
|
-
parentPath: [],
|
|
207
|
-
},
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
// Traverses the schema object tree depth first and validate fields
|
|
211
|
-
while (stack.length > 0) {
|
|
212
|
-
const { subSchema, parentPath } = stack.pop()!;
|
|
213
|
-
|
|
214
|
-
// process children of current object field
|
|
215
|
-
for (const name of Object.keys(subSchema).reverse()) {
|
|
216
|
-
const path = parentPath.concat([name]);
|
|
217
|
-
try {
|
|
218
|
-
this.validateConfigName(name);
|
|
219
|
-
const field = subSchema[name];
|
|
220
|
-
|
|
221
|
-
if (!Object.hasOwn(field, 'type') || typeof field.type !== 'string') {
|
|
222
|
-
throw new Error(
|
|
223
|
-
`every schema field must have a "type" property of type "string"`
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (!supportedTypes.includes(field.type)) {
|
|
228
|
-
throw new Error(`unsupported field type "${field.type}"`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
this.validateSchemaField(field);
|
|
232
|
-
|
|
233
|
-
// if the child is an object field itself add it to stack for
|
|
234
|
-
// processing
|
|
235
|
-
if (field.type === 'object') {
|
|
236
|
-
stack.push({
|
|
237
|
-
subSchema: field.children,
|
|
238
|
-
parentPath: path,
|
|
239
|
-
});
|
|
240
|
-
} else if (field.type === 'array') {
|
|
241
|
-
stack.push({
|
|
242
|
-
subSchema: { [name]: field.items },
|
|
243
|
-
parentPath: path,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
} catch (error: any) {
|
|
247
|
-
throw new Error(`${errorPreamble(path)}: ${error.message}`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* validates config key name
|
|
255
|
-
*
|
|
256
|
-
* @param {string} name
|
|
257
|
-
*/
|
|
258
|
-
private validateConfigName = (name: string) => {
|
|
259
|
-
if (name.includes('.')) {
|
|
260
|
-
throw new Error(`config key name can not contain the '.' character`);
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* validates passed schema field structure
|
|
266
|
-
*
|
|
267
|
-
* @param {ConfigField} field
|
|
268
|
-
*/
|
|
269
|
-
private validateSchemaField = (field: ConfigField) => {
|
|
270
|
-
for (const key of Object.keys(field)) {
|
|
271
|
-
if (
|
|
272
|
-
!Object.hasOwn(propertyValidators.all, key) &&
|
|
273
|
-
!(
|
|
274
|
-
!['object', 'array'].includes(field.type) &&
|
|
275
|
-
Object.hasOwn(propertyValidators.primitive, key)
|
|
276
|
-
) &&
|
|
277
|
-
!Object.hasOwn(propertyValidators[field.type], key)
|
|
278
|
-
) {
|
|
279
|
-
throw new Error(`schema field has unknown property "${key}"`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
for (const validator of Object.values(propertyValidators.all)) {
|
|
284
|
-
validator(field, this);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (field.type !== 'object' && field.type !== 'array') {
|
|
288
|
-
for (const validator of Object.values(propertyValidators.primitive)) {
|
|
289
|
-
validator(field, this);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
for (const validator of Object.values(propertyValidators[field.type])) {
|
|
294
|
-
validator(field, this);
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* returns a field corresponding to a path in schema tree
|
|
300
|
-
*
|
|
301
|
-
* @param {string[]} path
|
|
302
|
-
* @return {(ConfigField | undefined)} returns undefined if field is not found
|
|
303
|
-
*/
|
|
304
|
-
getSchemaField = (path: string[]): ConfigField | undefined => {
|
|
305
|
-
let subTree: ConfigSchema | undefined = this.schema;
|
|
306
|
-
let field: ConfigField | undefined = undefined;
|
|
307
|
-
for (const part of path) {
|
|
308
|
-
if (subTree != undefined && Object.hasOwn(subTree, part)) {
|
|
309
|
-
field = subTree[part];
|
|
310
|
-
subTree =
|
|
311
|
-
'children' in field
|
|
312
|
-
? field.children
|
|
313
|
-
: 'items' in field && 'children' in field.items
|
|
314
|
-
? field.items.children
|
|
315
|
-
: undefined;
|
|
316
|
-
} else {
|
|
317
|
-
return undefined;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return field;
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* extracts default values from a schema
|
|
325
|
-
*
|
|
326
|
-
* @return {Record<string, any>} object of default values
|
|
327
|
-
*/
|
|
328
|
-
generateDefault = (options?: { validate?: boolean }): Record<string, any> => {
|
|
329
|
-
const valueTree = this.buildDefaultsForSchema(this.schema);
|
|
330
|
-
if (options?.validate) {
|
|
331
|
-
this.validateConfig(valueTree);
|
|
332
|
-
}
|
|
333
|
-
return valueTree;
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
// Builds default values for a schema subtree (objects and arrays), recursively
|
|
337
|
-
private buildDefaultsForSchema = (
|
|
338
|
-
schema: ConfigSchema
|
|
339
|
-
): Record<string, any> => {
|
|
340
|
-
const defaults: Record<string, any> = Object.create(null);
|
|
341
|
-
for (const key of Object.keys(schema)) {
|
|
342
|
-
const field = schema[key];
|
|
343
|
-
if (field.type === 'object') {
|
|
344
|
-
const childDefaults = this.buildDefaultsForSchema(field.children);
|
|
345
|
-
if (Object.keys(childDefaults).length > 0) {
|
|
346
|
-
defaults[key] = childDefaults;
|
|
347
|
-
}
|
|
348
|
-
} else if (field.type === 'array') {
|
|
349
|
-
if ((field as any).default != undefined) {
|
|
350
|
-
if (field.items.type === 'object') {
|
|
351
|
-
const itemDefaults = this.buildDefaultsForSchema(
|
|
352
|
-
field.items.children
|
|
353
|
-
);
|
|
354
|
-
defaults[key] = (field as any).default.map((elem: any) => {
|
|
355
|
-
if (
|
|
356
|
-
elem != null &&
|
|
357
|
-
typeof elem === 'object' &&
|
|
358
|
-
!Array.isArray(elem)
|
|
359
|
-
) {
|
|
360
|
-
return { ...itemDefaults, ...elem };
|
|
361
|
-
}
|
|
362
|
-
return elem;
|
|
363
|
-
});
|
|
364
|
-
} else {
|
|
365
|
-
defaults[key] = (field as any).default;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
} else {
|
|
369
|
-
if ((field as any).default != undefined) {
|
|
370
|
-
defaults[key] = (field as any).default;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return defaults;
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* generates compatible TypeScript interface for this instance's schema
|
|
379
|
-
*
|
|
380
|
-
* @param {string} name the name of root type
|
|
381
|
-
* @return {string}
|
|
382
|
-
*/
|
|
383
|
-
generateTSTypes = (name: string): string => {
|
|
384
|
-
const errorPreamble = (path: Array<string>) =>
|
|
385
|
-
`TypeScript type generation failed for "${path.join('.')}" field`;
|
|
386
|
-
|
|
387
|
-
const types: Array<string> = [];
|
|
388
|
-
const emittedTypeNames: Set<string> = new Set<string>();
|
|
389
|
-
|
|
390
|
-
const stack: Array<{
|
|
391
|
-
subSchema: ConfigSchema;
|
|
392
|
-
children: string[];
|
|
393
|
-
parentPath: Array<string>;
|
|
394
|
-
typeName: string;
|
|
395
|
-
attributes: Array<[string, string]>;
|
|
396
|
-
}> = [
|
|
397
|
-
{
|
|
398
|
-
subSchema: this.schema,
|
|
399
|
-
children: Object.keys(this.schema),
|
|
400
|
-
parentPath: [],
|
|
401
|
-
typeName: name,
|
|
402
|
-
attributes: [],
|
|
403
|
-
},
|
|
404
|
-
];
|
|
405
|
-
|
|
406
|
-
// Traverses the schema object tree depth first
|
|
407
|
-
while (stack.length > 0) {
|
|
408
|
-
const { subSchema, children, parentPath, typeName, attributes } =
|
|
409
|
-
stack.at(-1)!;
|
|
410
|
-
let path: string[] = parentPath;
|
|
411
|
-
try {
|
|
412
|
-
// if a subtree's processing is finished go to the previous level
|
|
413
|
-
if (children.length == 0) {
|
|
414
|
-
if (!emittedTypeNames.has(typeName)) {
|
|
415
|
-
types.push(this.genTSInterface(typeName, attributes));
|
|
416
|
-
emittedTypeNames.add(typeName);
|
|
417
|
-
}
|
|
418
|
-
stack.pop();
|
|
419
|
-
continue;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const childName = children.pop()!;
|
|
423
|
-
path = parentPath.concat([childName]);
|
|
424
|
-
const field = subSchema[childName];
|
|
425
|
-
|
|
426
|
-
// if a node/field is of type object and thus is a subtree, add it to
|
|
427
|
-
// the stack to be traversed later. Otherwise it's a leaf and needs no
|
|
428
|
-
// traversal.
|
|
429
|
-
if (
|
|
430
|
-
field.type === 'object' ||
|
|
431
|
-
(field.type === 'array' && field.items.type === 'object')
|
|
432
|
-
) {
|
|
433
|
-
// Create unique type name from schema path excluding root interface name
|
|
434
|
-
const pathParts = path;
|
|
435
|
-
const childTypeName = pathParts
|
|
436
|
-
.map((part) => part[0].toUpperCase() + part.substring(1))
|
|
437
|
-
.join('');
|
|
438
|
-
|
|
439
|
-
const children =
|
|
440
|
-
field.type === 'array' && field.items.type === 'object'
|
|
441
|
-
? field.items.children
|
|
442
|
-
: field.type === 'object'
|
|
443
|
-
? field.children
|
|
444
|
-
: {};
|
|
445
|
-
|
|
446
|
-
stack.push({
|
|
447
|
-
subSchema: children,
|
|
448
|
-
children: Object.keys(children).reverse(),
|
|
449
|
-
parentPath: path,
|
|
450
|
-
typeName: childTypeName,
|
|
451
|
-
attributes: [],
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
attributes.push([
|
|
455
|
-
childName,
|
|
456
|
-
field.type === 'array' ? `${childTypeName}[]` : childTypeName,
|
|
457
|
-
]);
|
|
458
|
-
} else {
|
|
459
|
-
let fieldType: string =
|
|
460
|
-
field.type === 'array' ? field.items.type : field.type;
|
|
461
|
-
let isOptional = true;
|
|
462
|
-
if (field.type !== 'array' && field.validations != undefined) {
|
|
463
|
-
for (const validation of field.validations) {
|
|
464
|
-
if (field.type === 'string' && 'choices' in validation) {
|
|
465
|
-
fieldType = validation.choices.map((c) => `'${c}'`).join(' | ');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if ('required' in validation && !('when' in validation)) {
|
|
469
|
-
isOptional = false;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
attributes.push([
|
|
474
|
-
isOptional ? `${childName}?` : childName,
|
|
475
|
-
field.type === 'array' ? `${fieldType}[]` : fieldType,
|
|
476
|
-
]);
|
|
477
|
-
}
|
|
478
|
-
} catch (error: any) {
|
|
479
|
-
throw new Error(`${errorPreamble(path)}: ${error.message}`);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return types.reverse().join('\n\n') + '\n';
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* generates a TypeScript interface definition for passed name and attributes
|
|
488
|
-
*
|
|
489
|
-
* @param {string} name
|
|
490
|
-
* @param {Array<[string, string]>} attributes
|
|
491
|
-
* @return {string}
|
|
492
|
-
*/
|
|
493
|
-
private genTSInterface = (
|
|
494
|
-
name: string,
|
|
495
|
-
attributes: Array<[string, string]>
|
|
496
|
-
): string => {
|
|
497
|
-
return `export interface ${name} {
|
|
498
|
-
${attributes.map((attr) => `${attr[0]}: ${attr[1]};`).join('\n ')}
|
|
499
|
-
}`;
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* returns a characteristic object for values at a specific node config level
|
|
504
|
-
*
|
|
505
|
-
* @param {IConfig} config
|
|
506
|
-
* @param {string} level
|
|
507
|
-
* @return {Record<string, any>}
|
|
508
|
-
*/
|
|
509
|
-
getConfigForLevel(config: IConfig, level: string): Record<string, any> {
|
|
510
|
-
const confLevels = ConfigValidator.getNodeConfigLevels(config);
|
|
511
|
-
const levelIndex = confLevels.indexOf(level);
|
|
512
|
-
if (levelIndex === -1) {
|
|
513
|
-
throw new Error(
|
|
514
|
-
`The "${level}" level not found in the current system configuration levels`
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
const higherLevelSources = config.util
|
|
518
|
-
.getConfigSources()
|
|
519
|
-
.filter(
|
|
520
|
-
(source) => confLevels.indexOf(getSourceName(source)) > levelIndex
|
|
521
|
-
);
|
|
522
|
-
const currentLevelSource = config.util
|
|
523
|
-
.getConfigSources()
|
|
524
|
-
.filter((source) => getSourceName(source) === level)
|
|
525
|
-
.at(0);
|
|
526
|
-
const lowerLevelSources = config.util
|
|
527
|
-
.getConfigSources()
|
|
528
|
-
.filter(
|
|
529
|
-
(source) => confLevels.indexOf(getSourceName(source)) < levelIndex
|
|
530
|
-
);
|
|
531
|
-
|
|
532
|
-
// Traverses the schema object tree depth first
|
|
533
|
-
const valueTree = ConfigValidator.processConfigForLevelNode(
|
|
534
|
-
this.schema,
|
|
535
|
-
[],
|
|
536
|
-
higherLevelSources,
|
|
537
|
-
currentLevelSource,
|
|
538
|
-
lowerLevelSources
|
|
539
|
-
);
|
|
540
|
-
|
|
541
|
-
return valueTree;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
*traverses the config schema depth first to produce characteristic object
|
|
546
|
-
*
|
|
547
|
-
* @private
|
|
548
|
-
* @static
|
|
549
|
-
* @param {ConfigSchema} schema
|
|
550
|
-
* @param {string[]} path
|
|
551
|
-
* @param {IConfigSource[]} higherLevelSources
|
|
552
|
-
* @param {(IConfigSource | undefined)} currentLevelSource
|
|
553
|
-
* @param {IConfigSource[]} lowerLevelSources
|
|
554
|
-
* @return {Record<string, any>}
|
|
555
|
-
* @memberof ConfigValidator
|
|
556
|
-
*/
|
|
557
|
-
private static processConfigForLevelNode(
|
|
558
|
-
schema: ConfigSchema,
|
|
559
|
-
path: string[],
|
|
560
|
-
higherLevelSources: IConfigSource[],
|
|
561
|
-
currentLevelSource: IConfigSource | undefined,
|
|
562
|
-
lowerLevelSources: IConfigSource[]
|
|
563
|
-
): Record<string, any> {
|
|
564
|
-
const value = Object.create(null);
|
|
565
|
-
for (const childName of Object.keys(schema).reverse()) {
|
|
566
|
-
const childPath = path.concat([childName]);
|
|
567
|
-
const field = schema[childName];
|
|
568
|
-
// if a field is of type object and thus is a subtree, recurse on it.
|
|
569
|
-
// Otherwise it's a leaf and needs no traversal.
|
|
570
|
-
value[childName] = Object.create(null);
|
|
571
|
-
if (field.type === 'object') {
|
|
572
|
-
value[childName] = ConfigValidator.processConfigForLevelNode(
|
|
573
|
-
field.children,
|
|
574
|
-
childPath,
|
|
575
|
-
higherLevelSources,
|
|
576
|
-
currentLevelSource,
|
|
577
|
-
lowerLevelSources
|
|
578
|
-
);
|
|
579
|
-
} else {
|
|
580
|
-
value[childName]['label'] =
|
|
581
|
-
field.label != undefined ? field.label : null;
|
|
582
|
-
value[childName]['description'] =
|
|
583
|
-
field.description != undefined ? field.description : null;
|
|
584
|
-
value[childName]['default'] = getValueFromConfigSources(
|
|
585
|
-
lowerLevelSources,
|
|
586
|
-
childPath
|
|
587
|
-
);
|
|
588
|
-
value[childName]['value'] = getValueFromConfigSources(
|
|
589
|
-
[...(currentLevelSource != undefined ? [currentLevelSource] : [])],
|
|
590
|
-
childPath
|
|
591
|
-
);
|
|
592
|
-
value[childName]['override'] = getValueFromConfigSources(
|
|
593
|
-
higherLevelSources,
|
|
594
|
-
childPath
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return value;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* returns a list of config sources used by node config package, ordered from
|
|
604
|
-
* the lowest to the highest priority
|
|
605
|
-
*
|
|
606
|
-
* @static
|
|
607
|
-
* @param {IConfig} config
|
|
608
|
-
* @return {string[]}
|
|
609
|
-
*/
|
|
610
|
-
private static getNodeConfigLevels = (config: IConfig): string[] => {
|
|
611
|
-
const instance = config.util.getEnv('NODE_APP_INSTANCE');
|
|
612
|
-
let deployment = config.util.getEnv('NODE_ENV');
|
|
613
|
-
deployment = config.util.getEnv('NODE_CONFIG_ENV');
|
|
614
|
-
const fullHostname = config.util.getEnv('HOSTNAME');
|
|
615
|
-
const shortHostname =
|
|
616
|
-
fullHostname != undefined ? fullHostname.split('.')[0] : undefined;
|
|
617
|
-
|
|
618
|
-
const configLevels = [
|
|
619
|
-
'default',
|
|
620
|
-
...(instance != undefined ? [`default-${instance}`] : []),
|
|
621
|
-
...(deployment != undefined ? [`${deployment}`] : []),
|
|
622
|
-
...(instance != undefined && deployment != undefined
|
|
623
|
-
? [`${deployment}-${instance}`]
|
|
624
|
-
: []),
|
|
625
|
-
...(shortHostname != undefined ? [`${shortHostname}`] : []),
|
|
626
|
-
...(shortHostname != undefined && instance != undefined
|
|
627
|
-
? [`${shortHostname}-${instance}`]
|
|
628
|
-
: []),
|
|
629
|
-
...(shortHostname != undefined && deployment != undefined
|
|
630
|
-
? [`${shortHostname}-${deployment}`]
|
|
631
|
-
: []),
|
|
632
|
-
...(shortHostname != undefined &&
|
|
633
|
-
deployment != undefined &&
|
|
634
|
-
instance != undefined
|
|
635
|
-
? [`${shortHostname}-${deployment}-${instance}`]
|
|
636
|
-
: []),
|
|
637
|
-
...(fullHostname != undefined ? [`${fullHostname}`] : []),
|
|
638
|
-
...(fullHostname != undefined && instance != undefined
|
|
639
|
-
? [`${fullHostname}-${instance}`]
|
|
640
|
-
: []),
|
|
641
|
-
...(fullHostname != undefined && deployment != undefined
|
|
642
|
-
? [`${fullHostname}-${deployment}`]
|
|
643
|
-
: []),
|
|
644
|
-
...(fullHostname != undefined &&
|
|
645
|
-
deployment != undefined &&
|
|
646
|
-
instance != undefined
|
|
647
|
-
? [`${fullHostname}-${deployment}-${instance}`]
|
|
648
|
-
: []),
|
|
649
|
-
`local`,
|
|
650
|
-
...(instance != undefined ? [`local-${instance}`] : []),
|
|
651
|
-
...(deployment != undefined ? [`local-${deployment}`] : []),
|
|
652
|
-
...(deployment != undefined && instance != undefined
|
|
653
|
-
? [`local-${deployment}-${instance}`]
|
|
654
|
-
: []),
|
|
655
|
-
'$NODE_CONFIG',
|
|
656
|
-
'custom-environment-variables',
|
|
657
|
-
];
|
|
658
|
-
|
|
659
|
-
return configLevels;
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* validates a config object and writes it to the node-config file
|
|
664
|
-
* corresponding to the passed level
|
|
665
|
-
*
|
|
666
|
-
* @param {Record<string, any>} configObj
|
|
667
|
-
* @param {IConfig} config
|
|
668
|
-
* @param {string} level output node-config file level
|
|
669
|
-
* @param {string} format the format of the output file
|
|
670
|
-
*/
|
|
671
|
-
validateAndWriteConfig = (
|
|
672
|
-
configObj: Record<string, any>,
|
|
673
|
-
config: IConfig,
|
|
674
|
-
level: string,
|
|
675
|
-
format: string
|
|
676
|
-
) => {
|
|
677
|
-
const confLevels = ConfigValidator.getNodeConfigLevels(config).filter(
|
|
678
|
-
(l) => l !== 'custom-environment-variables'
|
|
679
|
-
);
|
|
680
|
-
const levelIndex = confLevels.indexOf(level);
|
|
681
|
-
if (levelIndex === -1) {
|
|
682
|
-
throw new Error(
|
|
683
|
-
`The [${level}] level not found in the current system's configuration levels`
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const configDir =
|
|
688
|
-
process.env['NODE_CONFIG_DIR'] != undefined
|
|
689
|
-
? process.env['NODE_CONFIG_DIR']
|
|
690
|
-
: './config';
|
|
691
|
-
let output = '';
|
|
692
|
-
let ext = '';
|
|
693
|
-
switch (format) {
|
|
694
|
-
case 'json': {
|
|
695
|
-
const JsonBigInt = JsonBigIntFactory({
|
|
696
|
-
alwaysParseAsBig: false,
|
|
697
|
-
useNativeBigInt: true,
|
|
698
|
-
});
|
|
699
|
-
output = JsonBigInt.stringify(configObj);
|
|
700
|
-
ext = 'json';
|
|
701
|
-
break;
|
|
702
|
-
}
|
|
703
|
-
case 'yaml': {
|
|
704
|
-
output = yaml.dump(configObj);
|
|
705
|
-
ext = 'yaml';
|
|
706
|
-
break;
|
|
707
|
-
}
|
|
708
|
-
default:
|
|
709
|
-
throw Error(`Invalid format: ${format}`);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
const outputPath = path.join(configDir, `${level}.${ext}`);
|
|
713
|
-
const backupPath = path.join(configDir, `${level}-backup.${ext}`);
|
|
714
|
-
const confFileExists = fs.existsSync(outputPath);
|
|
715
|
-
if (confFileExists) {
|
|
716
|
-
fs.renameSync(outputPath, backupPath);
|
|
717
|
-
}
|
|
718
|
-
fs.writeFileSync(outputPath, output);
|
|
719
|
-
|
|
720
|
-
const updatedConfObj = config.util.loadFileConfigs();
|
|
721
|
-
|
|
722
|
-
try {
|
|
723
|
-
this.validateConfig(updatedConfObj);
|
|
724
|
-
fs.unlinkSync(backupPath);
|
|
725
|
-
} catch (error) {
|
|
726
|
-
fs.unlinkSync(outputPath);
|
|
727
|
-
if (confFileExists) {
|
|
728
|
-
fs.renameSync(backupPath, outputPath);
|
|
729
|
-
}
|
|
730
|
-
throw error;
|
|
731
|
-
}
|
|
732
|
-
};
|
|
733
|
-
}
|