config 4.0.0 → 4.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/LICENSE +1 -1
- package/README.md +22 -22
- package/lib/config.js +192 -678
- package/lib/util.js +1237 -0
- package/package.json +2 -1
- package/parser.js +3 -0
package/lib/config.js
CHANGED
|
@@ -4,24 +4,12 @@
|
|
|
4
4
|
// http://lorenwest.github.com/node-config
|
|
5
5
|
|
|
6
6
|
// Dependencies
|
|
7
|
-
const DeferredConfig = require('../defer').DeferredConfig;
|
|
8
7
|
const RawConfig = require('../raw').RawConfig;
|
|
9
|
-
|
|
8
|
+
const { Util, Load } = require('./util.js');
|
|
10
9
|
const Path = require('path');
|
|
11
|
-
const FileSystem = require('fs');
|
|
12
10
|
|
|
13
|
-
// Static members
|
|
14
11
|
const DEFAULT_CLONE_DEPTH = 20;
|
|
15
|
-
let CONFIG_DIR;
|
|
16
|
-
let NODE_ENV;
|
|
17
|
-
let APP_INSTANCE;
|
|
18
|
-
let CONFIG_SKIP_GITCRYPT;
|
|
19
|
-
let NODE_ENV_VAR_NAME;
|
|
20
|
-
let NODE_CONFIG_PARSER;
|
|
21
|
-
const env = {};
|
|
22
|
-
const configSources = []; // Configuration sources - array of {name, original, parsed}
|
|
23
12
|
let checkMutability = true; // Check for mutability/immutability on first get
|
|
24
|
-
const gitCryptTestRegex = /^.GITCRYPT/; // regular expression to test for gitcrypt files.
|
|
25
13
|
|
|
26
14
|
/**
|
|
27
15
|
* <p>Application Configurations</p>
|
|
@@ -99,7 +87,7 @@ const gitCryptTestRegex = /^.GITCRYPT/; // regular expression to test for gitcry
|
|
|
99
87
|
* </script>
|
|
100
88
|
*
|
|
101
89
|
* @method constructor
|
|
102
|
-
* @return CONFIG {
|
|
90
|
+
* @return CONFIG {Object} - The top level configuration object
|
|
103
91
|
*/
|
|
104
92
|
const Config = function() {
|
|
105
93
|
const t = this;
|
|
@@ -112,7 +100,8 @@ const Config = function() {
|
|
|
112
100
|
}
|
|
113
101
|
|
|
114
102
|
// Merge configurations into this
|
|
115
|
-
|
|
103
|
+
_init(FIRST_LOAD);
|
|
104
|
+
Util.extendDeep(t, FIRST_LOAD.config);
|
|
116
105
|
util.attachProtoDeep(t);
|
|
117
106
|
|
|
118
107
|
// Perform strictness checks and possibly throw an exception.
|
|
@@ -124,30 +113,6 @@ const Config = function() {
|
|
|
124
113
|
*/
|
|
125
114
|
const util = Config.prototype.util = {};
|
|
126
115
|
|
|
127
|
-
/**
|
|
128
|
-
* Underlying get mechanism
|
|
129
|
-
*
|
|
130
|
-
* @private
|
|
131
|
-
* @method getImpl
|
|
132
|
-
* @param object {object} - Object to get the property for
|
|
133
|
-
* @param property {string|string[]} - The property name to get (as an array or '.' delimited string)
|
|
134
|
-
* @return value {*} - Property value, including undefined if not defined.
|
|
135
|
-
*/
|
|
136
|
-
const getImpl= function(object, property) {
|
|
137
|
-
const t = this;
|
|
138
|
-
const elems = Array.isArray(property) ? property : property.split('.');
|
|
139
|
-
const name = elems[0];
|
|
140
|
-
const value = object[name];
|
|
141
|
-
if (elems.length <= 1) {
|
|
142
|
-
return value;
|
|
143
|
-
}
|
|
144
|
-
// Note that typeof null === 'object'
|
|
145
|
-
if (value === null || typeof value !== 'object') {
|
|
146
|
-
return undefined;
|
|
147
|
-
}
|
|
148
|
-
return getImpl(value, elems.slice(1));
|
|
149
|
-
};
|
|
150
|
-
|
|
151
116
|
/**
|
|
152
117
|
* <p>Get a configuration value</p>
|
|
153
118
|
*
|
|
@@ -168,13 +133,13 @@ Config.prototype.get = function(property) {
|
|
|
168
133
|
|
|
169
134
|
// Make configurations immutable after first get (unless disabled)
|
|
170
135
|
if (checkMutability) {
|
|
171
|
-
if (!
|
|
136
|
+
if (!FIRST_LOAD.initParam('ALLOW_CONFIG_MUTATIONS', false)) {
|
|
172
137
|
util.makeImmutable(config);
|
|
173
138
|
}
|
|
174
139
|
checkMutability = false;
|
|
175
140
|
}
|
|
176
141
|
const t = this;
|
|
177
|
-
const value =
|
|
142
|
+
const value = Util.getPath(t, property);
|
|
178
143
|
|
|
179
144
|
// Produce an exception if the property doesn't exist
|
|
180
145
|
if (typeof value === "undefined") {
|
|
@@ -197,7 +162,7 @@ Config.prototype.get = function(property) {
|
|
|
197
162
|
*
|
|
198
163
|
* @method has
|
|
199
164
|
* @param property {string} - The configuration property to test. Can include '.' sub-properties.
|
|
200
|
-
* @return
|
|
165
|
+
* @return {boolean} - True if the property is defined, false if not defined.
|
|
201
166
|
*/
|
|
202
167
|
Config.prototype.has = function(property) {
|
|
203
168
|
// While get() throws an exception for undefined input, has() is designed to test validity, so false is appropriate
|
|
@@ -205,7 +170,7 @@ Config.prototype.has = function(property) {
|
|
|
205
170
|
return false;
|
|
206
171
|
}
|
|
207
172
|
const t = this;
|
|
208
|
-
return typeof
|
|
173
|
+
return typeof Util.getPath(t, property) !== "undefined";
|
|
209
174
|
};
|
|
210
175
|
|
|
211
176
|
/**
|
|
@@ -238,43 +203,32 @@ Config.prototype.has = function(property) {
|
|
|
238
203
|
*
|
|
239
204
|
* @method setModuleDefaults
|
|
240
205
|
* @param moduleName {string} - Name of your module.
|
|
241
|
-
* @param defaultProperties {
|
|
242
|
-
* @return moduleConfig {
|
|
206
|
+
* @param defaultProperties {Object} - The default module configuration.
|
|
207
|
+
* @return moduleConfig {Object} - The module level configuration object.
|
|
243
208
|
*/
|
|
244
209
|
util.setModuleDefaults = function (moduleName, defaultProperties) {
|
|
245
210
|
|
|
246
211
|
// Copy the properties into a new object
|
|
247
212
|
const t = this;
|
|
248
|
-
const
|
|
213
|
+
const path = moduleName.split('.');
|
|
214
|
+
const moduleConfig = FIRST_LOAD.setModuleDefaults(moduleName, defaultProperties);
|
|
215
|
+
let existing = Util.getPath(t, path);
|
|
249
216
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
parsed: {}
|
|
255
|
-
});
|
|
217
|
+
if (existing === undefined) {
|
|
218
|
+
Util.setPath(t, path, Util.cloneDeep(moduleConfig));
|
|
219
|
+
} else {
|
|
220
|
+
Util.extendDeep(existing, moduleConfig);
|
|
256
221
|
}
|
|
257
|
-
util.setPath(configSources[0].parsed, moduleName.split('.'), {});
|
|
258
|
-
util.extendDeep(getImpl(configSources[0].parsed, moduleName), defaultProperties);
|
|
259
|
-
|
|
260
|
-
// Create a top level config for this module if it doesn't exist
|
|
261
|
-
util.setPath(t, moduleName.split('.'), getImpl(t, moduleName) || {});
|
|
262
|
-
|
|
263
|
-
// Extend local configurations into the module config
|
|
264
|
-
util.extendDeep(moduleConfig, getImpl(t, moduleName));
|
|
265
|
-
|
|
266
|
-
// Merge the extended configs without replacing the original
|
|
267
|
-
util.extendDeep(getImpl(t, moduleName), moduleConfig);
|
|
268
222
|
|
|
269
223
|
// reset the mutability check for "config.get" method.
|
|
270
224
|
// we are not making t[moduleName] immutable immediately,
|
|
271
225
|
// since there might be more modifications before the first config.get
|
|
272
|
-
if (!
|
|
226
|
+
if (!FIRST_LOAD.initParam('ALLOW_CONFIG_MUTATIONS', false)) {
|
|
273
227
|
checkMutability = true;
|
|
274
228
|
}
|
|
275
229
|
|
|
276
230
|
// Attach handlers & watchers onto the module config object
|
|
277
|
-
return util.attachProtoDeep(
|
|
231
|
+
return util.attachProtoDeep(Util.getPath(t, path));
|
|
278
232
|
};
|
|
279
233
|
|
|
280
234
|
/**
|
|
@@ -306,29 +260,15 @@ util.setModuleDefaults = function (moduleName, defaultProperties) {
|
|
|
306
260
|
* CONFIG.util.makeHidden(CONFIG.amazonS3, 'secret_key');
|
|
307
261
|
* </pre>
|
|
308
262
|
*
|
|
263
|
+
* @deprecated use Util.makeHidden
|
|
309
264
|
* @method makeHidden
|
|
310
|
-
* @param object {
|
|
265
|
+
* @param object {Object} - The object to make a hidden property into.
|
|
311
266
|
* @param property {string} - The name of the property to make hidden.
|
|
312
267
|
* @param value {*} - (optional) Set the property value to this (otherwise leave alone)
|
|
313
|
-
* @return object {
|
|
268
|
+
* @return object {Object} - The original object is returned - for chaining.
|
|
314
269
|
*/
|
|
315
270
|
util.makeHidden = function(object, property, value) {
|
|
316
|
-
|
|
317
|
-
// If the new value isn't specified, just mark the property as hidden
|
|
318
|
-
if (typeof value === 'undefined') {
|
|
319
|
-
Object.defineProperty(object, property, {
|
|
320
|
-
enumerable : false
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
// Otherwise set the value and mark it as hidden
|
|
324
|
-
else {
|
|
325
|
-
Object.defineProperty(object, property, {
|
|
326
|
-
value : value,
|
|
327
|
-
enumerable : false
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return object;
|
|
271
|
+
return Util.makeHidden(object, property, value);
|
|
332
272
|
}
|
|
333
273
|
|
|
334
274
|
/**
|
|
@@ -359,13 +299,13 @@ util.makeHidden = function(object, property, value) {
|
|
|
359
299
|
* </pre>
|
|
360
300
|
*
|
|
361
301
|
* @method makeImmutable
|
|
362
|
-
* @param object {
|
|
302
|
+
* @param object {Object} - The object to specify immutable properties for
|
|
363
303
|
* @param [property] {string | [string]} - The name of the property (or array of names) to make immutable.
|
|
364
304
|
* If not provided, all owned properties of the object are made immutable.
|
|
365
305
|
* @param [value] {* | [*]} - Property value (or array of values) to set
|
|
366
306
|
* the property to before making immutable. Only used when setting a single
|
|
367
307
|
* property. Retained for backward compatibility.
|
|
368
|
-
* @return object {
|
|
308
|
+
* @return object {Object} - The original object is returned - for chaining.
|
|
369
309
|
*/
|
|
370
310
|
util.makeImmutable = function(object, property, value) {
|
|
371
311
|
if (Buffer.isBuffer(object)) {
|
|
@@ -403,21 +343,21 @@ util.makeImmutable = function(object, property, value) {
|
|
|
403
343
|
});
|
|
404
344
|
} else if (Array.isArray(value)) {
|
|
405
345
|
// Ensure object items of this array are also immutable.
|
|
406
|
-
value.forEach((item, index) => { if (
|
|
346
|
+
value.forEach((item, index) => { if (Util.isObject(item) || Array.isArray(item)) util.makeImmutable(item) })
|
|
407
347
|
|
|
408
348
|
Object.defineProperty(object, propertyName, {
|
|
409
349
|
value: Object.freeze(value)
|
|
410
350
|
});
|
|
411
351
|
} else {
|
|
412
352
|
// Call recursively if an object.
|
|
413
|
-
if (
|
|
353
|
+
if (Util.isObject(value)) {
|
|
414
354
|
// Create a proxy, to capture user updates of configuration options, and throw an exception for awareness, as per:
|
|
415
355
|
// https://github.com/lorenwest/node-config/issues/514
|
|
416
356
|
value = new Proxy(util.makeImmutable(value), {
|
|
417
357
|
get(target, property, receiver) {
|
|
418
358
|
// Config's own defined prototype properties and methods (e.g., `get`, `has`, etc.)
|
|
419
359
|
const ownProps = [
|
|
420
|
-
...Object.getOwnPropertyNames(Config.prototype),
|
|
360
|
+
...Object.getOwnPropertyNames(Config.prototype), //TODO: This keeps us from moving this function to util.js where it belongs
|
|
421
361
|
...Object.getOwnPropertyNames(target),
|
|
422
362
|
]
|
|
423
363
|
|
|
@@ -445,11 +385,15 @@ util.makeImmutable = function(object, property, value) {
|
|
|
445
385
|
})
|
|
446
386
|
}
|
|
447
387
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
388
|
+
// Check if property already has writable: false and configurable: false
|
|
389
|
+
const currentDescriptor = Object.getOwnPropertyDescriptor(object, propertyName);
|
|
390
|
+
if (!currentDescriptor || currentDescriptor.writable !== false || currentDescriptor.configurable !== false) {
|
|
391
|
+
Object.defineProperty(object, propertyName, {
|
|
392
|
+
value: value,
|
|
393
|
+
writable : false,
|
|
394
|
+
configurable: false
|
|
395
|
+
});
|
|
396
|
+
}
|
|
453
397
|
|
|
454
398
|
// Ensure new properties can not be added, as per:
|
|
455
399
|
// https://github.com/lorenwest/node-config/issues/505
|
|
@@ -474,8 +418,7 @@ util.makeImmutable = function(object, property, value) {
|
|
|
474
418
|
* name, original, and parsed elements
|
|
475
419
|
*/
|
|
476
420
|
util.getConfigSources = function() {
|
|
477
|
-
|
|
478
|
-
return configSources.slice(0);
|
|
421
|
+
return FIRST_LOAD.getSources();
|
|
479
422
|
};
|
|
480
423
|
|
|
481
424
|
/**
|
|
@@ -487,17 +430,14 @@ util.getConfigSources = function() {
|
|
|
487
430
|
* </p>
|
|
488
431
|
*
|
|
489
432
|
* @method getOption
|
|
433
|
+
* @deprecated use Util.getOption
|
|
490
434
|
* @param options {Object | undefined} the options object
|
|
491
435
|
* @param optionName {string} the attribute name to look for
|
|
492
436
|
* @param defaultValue { any } the default in case the options object is empty, or the attribute does not exist.
|
|
493
437
|
* @return options[optionName] if defined, defaultValue if not.
|
|
494
438
|
*/
|
|
495
439
|
util.getOption = function(options, optionName, defaultValue) {
|
|
496
|
-
|
|
497
|
-
return options[optionName];
|
|
498
|
-
} else {
|
|
499
|
-
return defaultValue;
|
|
500
|
-
}
|
|
440
|
+
return Util.getOption(options, optionName, defaultValue);
|
|
501
441
|
};
|
|
502
442
|
|
|
503
443
|
|
|
@@ -521,7 +461,7 @@ util.getOption = function(options, optionName, defaultValue) {
|
|
|
521
461
|
* <p>
|
|
522
462
|
* EXT can be yml, yaml, coffee, iced, json, jsonc, cson or js signifying the file type.
|
|
523
463
|
* yaml (and yml) is in YAML format, coffee is a coffee-script, iced is iced-coffee-script,
|
|
524
|
-
* json is in JSON format, jsonc is in JSONC format, cson is in CSON format, properties is
|
|
464
|
+
* json is in JSON format, jsonc is in JSONC format, cson is in CSON format, properties is
|
|
525
465
|
* in .properties format (http://en.wikipedia.org/wiki/.properties), and js is a javascript
|
|
526
466
|
* executable file that is require()'d with module.exports being the config object.
|
|
527
467
|
* </p>
|
|
@@ -552,246 +492,98 @@ util.getOption = function(options, optionName, defaultValue) {
|
|
|
552
492
|
* </p>
|
|
553
493
|
*
|
|
554
494
|
* @protected
|
|
495
|
+
* @see Util.loadFileConfigs for discrete execution of (most of this functionality
|
|
555
496
|
* @method loadFileConfigs
|
|
556
497
|
* @param configDir { string | null } the path to the directory containing the configurations to load
|
|
557
|
-
* @param options {
|
|
558
|
-
* @return
|
|
498
|
+
* @param options { LoadOptions | undefined } parsing options
|
|
499
|
+
* @return {Object} The configuration object
|
|
559
500
|
*/
|
|
560
501
|
util.loadFileConfigs = function(configDir, options) {
|
|
502
|
+
let newLoad;
|
|
561
503
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
504
|
+
if (configDir) {
|
|
505
|
+
let opts = {...FIRST_LOAD.options, configDir, ...options};
|
|
506
|
+
newLoad = new Load(opts);
|
|
507
|
+
newLoad.scan();
|
|
565
508
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
for (const node_env_var_name of node_env_var_names) {
|
|
571
|
-
NODE_ENV = util.initParam(node_env_var_name);
|
|
572
|
-
if (!!NODE_ENV) {
|
|
573
|
-
NODE_ENV_VAR_NAME = node_env_var_name;
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
509
|
+
FIRST_LOAD.sourcesBug(newLoad.getSources());
|
|
510
|
+
} else {
|
|
511
|
+
newLoad = new Load({...FIRST_LOAD.options, ...options});
|
|
512
|
+
_init(newLoad);
|
|
577
513
|
|
|
578
|
-
|
|
579
|
-
if (!NODE_ENV) {
|
|
580
|
-
NODE_ENV = 'development';
|
|
514
|
+
FIRST_LOAD.sourcesBug(newLoad.getSources());
|
|
581
515
|
}
|
|
582
516
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
});
|
|
517
|
+
return newLoad.config;
|
|
518
|
+
}
|
|
586
519
|
|
|
587
|
-
|
|
588
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Scan with the default config dir (usually only at startup.
|
|
522
|
+
* This adds a bit more data from NODE_CONFIG that _load() skips
|
|
523
|
+
*
|
|
524
|
+
* @param load {Load}
|
|
525
|
+
* @private
|
|
526
|
+
*/
|
|
527
|
+
function _init(load) {
|
|
528
|
+
let options = load.options;
|
|
529
|
+
let additional = [];
|
|
589
530
|
|
|
590
|
-
|
|
591
|
-
|
|
531
|
+
// Override configurations from the $NODE_CONFIG environment variable
|
|
532
|
+
let envConfig = {};
|
|
592
533
|
|
|
593
|
-
|
|
594
|
-
CONFIG_SKIP_GITCRYPT = util.initParam('CONFIG_SKIP_GITCRYPT');
|
|
534
|
+
load.setEnv("CONFIG_DIR", options.configDir);
|
|
595
535
|
|
|
596
|
-
|
|
597
|
-
if (NODE_CONFIG_PARSER) {
|
|
536
|
+
if (process.env.NODE_CONFIG) {
|
|
598
537
|
try {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
Parser = require(parserModule);
|
|
603
|
-
}
|
|
604
|
-
catch (e) {
|
|
605
|
-
console.warn('Failed to load config parser from ' + NODE_CONFIG_PARSER);
|
|
606
|
-
console.log(e);
|
|
538
|
+
envConfig = JSON.parse(process.env.NODE_CONFIG);
|
|
539
|
+
} catch(e) {
|
|
540
|
+
console.error('The $NODE_CONFIG environment variable is malformed JSON');
|
|
607
541
|
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const HOST = util.initParam('HOST');
|
|
611
|
-
const HOSTNAME = util.initParam('HOSTNAME');
|
|
612
|
-
|
|
613
|
-
// Determine the host name from the OS module, $HOST, or $HOSTNAME
|
|
614
|
-
// Remove any . appendages, and default to null if not set
|
|
615
|
-
let hostName = HOST || HOSTNAME;
|
|
616
|
-
try {
|
|
617
|
-
if (!hostName) {
|
|
618
|
-
const OS = require('os');
|
|
619
|
-
hostName = OS.hostname();
|
|
620
|
-
}
|
|
621
|
-
} catch (e) {
|
|
622
|
-
hostName = '';
|
|
623
|
-
}
|
|
624
542
|
|
|
625
|
-
|
|
626
|
-
env.HOSTNAME = hostName;
|
|
627
|
-
|
|
628
|
-
// Read each file in turn
|
|
629
|
-
const baseNames = ['default'].concat(NODE_ENV);
|
|
630
|
-
|
|
631
|
-
// #236: Also add full hostname when they are different.
|
|
632
|
-
if (hostName) {
|
|
633
|
-
const firstDomain = hostName.split('.')[0];
|
|
634
|
-
|
|
635
|
-
NODE_ENV.forEach(function(env) {
|
|
636
|
-
// Backward compatibility
|
|
637
|
-
baseNames.push(firstDomain, firstDomain + '-' + env);
|
|
638
|
-
|
|
639
|
-
// Add full hostname when it is not the same
|
|
640
|
-
if (hostName !== firstDomain) {
|
|
641
|
-
baseNames.push(hostName, hostName + '-' + env);
|
|
642
|
-
}
|
|
643
|
-
});
|
|
543
|
+
additional.push({ name: "$NODE_CONFIG", config: envConfig });
|
|
644
544
|
}
|
|
645
545
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
baseNames.forEach(function(baseName) {
|
|
654
|
-
const fileNames = [baseName];
|
|
655
|
-
if (APP_INSTANCE) {
|
|
656
|
-
fileNames.push(baseName + '-' + APP_INSTANCE);
|
|
657
|
-
}
|
|
658
|
-
fileNames.forEach(function(fileName) {
|
|
659
|
-
extNames.forEach(function(extName) {
|
|
660
|
-
allowedFiles[fileName + '.' + extName] = resolutionIndex++;
|
|
661
|
-
});
|
|
662
|
-
})
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
const locatedFiles = util.locateMatchingFiles(dir, allowedFiles);
|
|
666
|
-
locatedFiles.forEach(function(fullFilename) {
|
|
667
|
-
const configObj = util.parseFile(fullFilename, options);
|
|
668
|
-
if (configObj) {
|
|
669
|
-
util.extendDeep(config, configObj);
|
|
670
|
-
}
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
// Override configurations from the $NODE_CONFIG environment variable
|
|
674
|
-
// NODE_CONFIG only applies to the base config
|
|
675
|
-
if (!configDir) {
|
|
676
|
-
let envConfig = {};
|
|
677
|
-
|
|
678
|
-
CONFIG_DIR = dir;
|
|
679
|
-
|
|
680
|
-
if (process.env.NODE_CONFIG) {
|
|
681
|
-
try {
|
|
682
|
-
envConfig = JSON.parse(process.env.NODE_CONFIG);
|
|
683
|
-
} catch(e) {
|
|
684
|
-
console.error('The $NODE_CONFIG environment variable is malformed JSON');
|
|
685
|
-
}
|
|
686
|
-
util.extendDeep(config, envConfig);
|
|
687
|
-
const skipConfigSources = util.getOption(options,'skipConfigSources', false);
|
|
688
|
-
if (!skipConfigSources){
|
|
689
|
-
configSources.push({
|
|
690
|
-
name: "$NODE_CONFIG",
|
|
691
|
-
parsed: envConfig,
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Override configurations from the --NODE_CONFIG command line
|
|
697
|
-
let cmdLineConfig = util.getCmdLineArg('NODE_CONFIG');
|
|
698
|
-
if (cmdLineConfig) {
|
|
699
|
-
try {
|
|
700
|
-
cmdLineConfig = JSON.parse(cmdLineConfig);
|
|
701
|
-
} catch(e) {
|
|
702
|
-
console.error('The --NODE_CONFIG={json} command line argument is malformed JSON');
|
|
703
|
-
}
|
|
704
|
-
util.extendDeep(config, cmdLineConfig);
|
|
705
|
-
const skipConfigSources = util.getOption(options,'skipConfigSources', false);
|
|
706
|
-
if (!skipConfigSources){
|
|
707
|
-
configSources.push({
|
|
708
|
-
name: "--NODE_CONFIG argument",
|
|
709
|
-
parsed: cmdLineConfig,
|
|
710
|
-
});
|
|
711
|
-
}
|
|
546
|
+
// Override configurations from the --NODE_CONFIG command line
|
|
547
|
+
let cmdLineConfig = load.getCmdLineArg('NODE_CONFIG');
|
|
548
|
+
if (cmdLineConfig) {
|
|
549
|
+
try {
|
|
550
|
+
cmdLineConfig = JSON.parse(cmdLineConfig);
|
|
551
|
+
} catch(e) {
|
|
552
|
+
console.error('The --NODE_CONFIG={json} command line argument is malformed JSON');
|
|
712
553
|
}
|
|
713
554
|
|
|
714
|
-
|
|
715
|
-
env['NODE_CONFIG'] = JSON.stringify(util.extendDeep(envConfig, cmdLineConfig, {}));
|
|
555
|
+
additional.push({ name: "--NODE_CONFIG argument", config: cmdLineConfig });
|
|
716
556
|
}
|
|
717
557
|
|
|
718
|
-
//
|
|
719
|
-
|
|
720
|
-
util.extendDeep(config, customEnvVars);
|
|
558
|
+
// Place the mixed NODE_CONFIG into the environment
|
|
559
|
+
load.setEnv('NODE_CONFIG', JSON.stringify(Util.extendDeep(envConfig, cmdLineConfig, {})));
|
|
721
560
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
// Return the configuration object
|
|
725
|
-
return config;
|
|
726
|
-
};
|
|
561
|
+
load.scan(additional);
|
|
562
|
+
}
|
|
727
563
|
|
|
728
564
|
/**
|
|
729
565
|
* Return a list of fullFilenames who exists in allowedFiles
|
|
730
566
|
* Ordered according to allowedFiles argument specifications
|
|
731
567
|
*
|
|
732
568
|
* @protected
|
|
569
|
+
* @deprecated use Util.locateMatchingFiles
|
|
733
570
|
* @method locateMatchingFiles
|
|
734
571
|
* @param configDirs {string} the config dir, or multiple dirs separated by a column (:)
|
|
735
|
-
* @param allowedFiles {
|
|
572
|
+
* @param allowedFiles {Object} an object. keys and supported filenames
|
|
736
573
|
* and values are the position in the resolution order
|
|
737
574
|
* @returns {string[]} fullFilenames - path + filename
|
|
738
575
|
*/
|
|
739
576
|
util.locateMatchingFiles = function(configDirs, allowedFiles) {
|
|
740
|
-
return
|
|
741
|
-
.filter(Boolean)
|
|
742
|
-
.reduce(function(files, configDir) {
|
|
743
|
-
configDir = _toAbsolutePath(configDir);
|
|
744
|
-
try {
|
|
745
|
-
FileSystem.readdirSync(configDir)
|
|
746
|
-
.filter(file => allowedFiles[file])
|
|
747
|
-
.forEach(function(file) {
|
|
748
|
-
files.push([allowedFiles[file], Path.join(configDir, file)]);
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
catch(e) {}
|
|
752
|
-
return files;
|
|
753
|
-
}, [])
|
|
754
|
-
.sort(function(a, b) { return a[0] - b[0]; })
|
|
755
|
-
.map(function(file) { return file[1]; });
|
|
577
|
+
return Util.locateMatchingFiles(configDirs, allowedFiles);
|
|
756
578
|
};
|
|
757
579
|
|
|
580
|
+
/**
|
|
581
|
+
* @deprecated use Util.resolveDeferredConfigs
|
|
582
|
+
* @param config
|
|
583
|
+
*/
|
|
758
584
|
// Using basic recursion pattern, find all the deferred values and resolve them.
|
|
759
585
|
util.resolveDeferredConfigs = function (config) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
function _iterate (prop) {
|
|
763
|
-
if (prop.constructor === String) {
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// We put the properties we are going to look it in an array to keep the order predictable
|
|
768
|
-
const propsToSort = Object.keys(prop).filter((property) => prop[property] != null);
|
|
769
|
-
|
|
770
|
-
// Second step is to iterate of the elements in a predictable (sorted) order
|
|
771
|
-
propsToSort.sort().forEach(function (property) {
|
|
772
|
-
if (prop[property].constructor === Object) {
|
|
773
|
-
_iterate(prop[property]);
|
|
774
|
-
} else if (prop[property].constructor === Array) {
|
|
775
|
-
for (let i = 0; i < prop[property].length; i++) {
|
|
776
|
-
if (prop[property][i] instanceof DeferredConfig) {
|
|
777
|
-
deferred.push(prop[property][i].prepare(config, prop[property], i));
|
|
778
|
-
}
|
|
779
|
-
else {
|
|
780
|
-
_iterate(prop[property][i]);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
} else {
|
|
784
|
-
if (prop[property] instanceof DeferredConfig) {
|
|
785
|
-
deferred.push(prop[property].prepare(config, prop, property));
|
|
786
|
-
}
|
|
787
|
-
// else: Nothing to do. Keep the property how it is.
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
_iterate(config);
|
|
793
|
-
|
|
794
|
-
deferred.forEach(function (defer) { defer.resolve(); });
|
|
586
|
+
return Util.resolveDeferredConfigs(config);
|
|
795
587
|
};
|
|
796
588
|
|
|
797
589
|
/**
|
|
@@ -805,7 +597,7 @@ util.resolveDeferredConfigs = function (config) {
|
|
|
805
597
|
* .js = File to run that has a module.exports containing the config object
|
|
806
598
|
* .coffee = File to run that has a module.exports with coffee-script containing the config object
|
|
807
599
|
* .iced = File to run that has a module.exports with iced-coffee-script containing the config object
|
|
808
|
-
* All other supported file types (yaml, toml, json,
|
|
600
|
+
* All other supported file types (yaml, toml, json, cson, hjson, json5, properties, xml)
|
|
809
601
|
* are parsed with util.parseString.
|
|
810
602
|
*
|
|
811
603
|
* If the file doesn't exist, a null will be returned. If the file can't be
|
|
@@ -815,73 +607,31 @@ util.resolveDeferredConfigs = function (config) {
|
|
|
815
607
|
* after synchronous module loading.
|
|
816
608
|
*
|
|
817
609
|
* @protected
|
|
610
|
+
* @deprecated
|
|
818
611
|
* @method parseFile
|
|
819
612
|
* @param fullFilename {string} The full file path and name
|
|
820
613
|
* @param options { object | undefined } parsing options. Current supported option: skipConfigSources: true|false
|
|
821
614
|
* @return configObject {object|null} The configuration object parsed from the file
|
|
822
615
|
*/
|
|
823
|
-
util.parseFile = function(fullFilename, options) {
|
|
824
|
-
|
|
825
|
-
let
|
|
826
|
-
let fileContent = null;
|
|
827
|
-
const stat = null;
|
|
828
|
-
|
|
829
|
-
// Note that all methods here are the Sync versions. This is appropriate during
|
|
830
|
-
// module loading (which is a synchronous operation), but not thereafter.
|
|
831
|
-
|
|
832
|
-
try {
|
|
833
|
-
// Try loading the file.
|
|
834
|
-
fileContent = FileSystem.readFileSync(fullFilename, 'utf-8');
|
|
835
|
-
fileContent = fileContent.replace(/^\uFEFF/, '');
|
|
836
|
-
}
|
|
837
|
-
catch (e2) {
|
|
838
|
-
if (e2.code !== 'ENOENT') {
|
|
839
|
-
throw new Error('Config file ' + fullFilename + ' cannot be read. Error code is: '+e2.code
|
|
840
|
-
+'. Error message is: '+e2.message);
|
|
841
|
-
}
|
|
842
|
-
return null; // file doesn't exists
|
|
843
|
-
}
|
|
616
|
+
util.parseFile = function(fullFilename, options = {}) {
|
|
617
|
+
let loadOpts = {...FIRST_LOAD.options}; //TODO: Support full LoadOptions?
|
|
618
|
+
let loadInfo = new Load(loadOpts);
|
|
844
619
|
|
|
845
|
-
|
|
846
|
-
try {
|
|
620
|
+
loadInfo.loadFile(fullFilename);
|
|
847
621
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (gitCryptTestRegex.test(fileContent)) {
|
|
851
|
-
console.error('WARNING: ' + fullFilename + ' is a git-crypt file and CONFIG_SKIP_GITCRYPT is set. skipping.');
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
configObject = Parser.parse(fullFilename, fileContent);
|
|
857
|
-
}
|
|
858
|
-
catch (e3) {
|
|
859
|
-
if (gitCryptTestRegex.test(fileContent)) {
|
|
860
|
-
console.error('ERROR: ' + fullFilename + ' is a git-crypt file and CONFIG_SKIP_GITCRYPT is not set.');
|
|
861
|
-
}
|
|
862
|
-
throw new Error("Cannot parse config file: '" + fullFilename + "': " + e3);
|
|
622
|
+
if (!options.skipConfigSources) {
|
|
623
|
+
FIRST_LOAD.sourcesBug(loadInfo.getSources());
|
|
863
624
|
}
|
|
864
625
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
if (typeof configObject === 'object' && !skipConfigSources) {
|
|
868
|
-
configSources.push({
|
|
869
|
-
name: fullFilename,
|
|
870
|
-
original: fileContent,
|
|
871
|
-
parsed: configObject,
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
return configObject;
|
|
876
|
-
};
|
|
626
|
+
return loadInfo.config;
|
|
627
|
+
}
|
|
877
628
|
|
|
878
629
|
/**
|
|
879
630
|
* Parse and return the specified string with the specified format.
|
|
880
631
|
*
|
|
881
632
|
* The format determines the parser to use.
|
|
882
633
|
*
|
|
883
|
-
* json =
|
|
884
|
-
* jsonc = Parsed with a JSON5 parser
|
|
634
|
+
* json = File is parsed using JSON.parse()
|
|
885
635
|
* yaml (or yml) = Parsed with a YAML parser
|
|
886
636
|
* toml = Parsed with a TOML parser
|
|
887
637
|
* cson = Parsed with a CSON parser
|
|
@@ -897,16 +647,14 @@ util.parseFile = function(fullFilename, options) {
|
|
|
897
647
|
* after synchronous module loading.
|
|
898
648
|
*
|
|
899
649
|
* @protected
|
|
650
|
+
* @deprecated
|
|
900
651
|
* @method parseString
|
|
901
652
|
* @param content {string} The full content
|
|
902
653
|
* @param format {string} The format to be parsed
|
|
903
654
|
* @return {configObject} The configuration object parsed from the string
|
|
904
655
|
*/
|
|
905
656
|
util.parseString = function (content, format) {
|
|
906
|
-
|
|
907
|
-
if (typeof parser === 'function') {
|
|
908
|
-
return parser(null, content);
|
|
909
|
-
}
|
|
657
|
+
return FIRST_LOAD.parseString(content, format);
|
|
910
658
|
};
|
|
911
659
|
|
|
912
660
|
/**
|
|
@@ -924,9 +672,9 @@ util.parseString = function (content, format) {
|
|
|
924
672
|
*
|
|
925
673
|
* @protected
|
|
926
674
|
* @method attachProtoDeep
|
|
927
|
-
* @param toObject
|
|
928
|
-
* @param depth
|
|
929
|
-
* @return
|
|
675
|
+
* @param toObject {Object}
|
|
676
|
+
* @param depth {number=20}
|
|
677
|
+
* @return {Object}
|
|
930
678
|
*/
|
|
931
679
|
util.attachProtoDeep = function(toObject, depth) {
|
|
932
680
|
if (toObject instanceof RawConfig) {
|
|
@@ -934,7 +682,6 @@ util.attachProtoDeep = function(toObject, depth) {
|
|
|
934
682
|
}
|
|
935
683
|
|
|
936
684
|
// Recursion detection
|
|
937
|
-
const t = this;
|
|
938
685
|
depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
|
|
939
686
|
if (depth < 0) {
|
|
940
687
|
return toObject;
|
|
@@ -944,13 +691,13 @@ util.attachProtoDeep = function(toObject, depth) {
|
|
|
944
691
|
// because adding to toObject.__proto__ exposes the function in toObject
|
|
945
692
|
for (const fnName in Config.prototype) {
|
|
946
693
|
if (!toObject[fnName]) {
|
|
947
|
-
|
|
694
|
+
Util.makeHidden(toObject, fnName, Config.prototype[fnName]);
|
|
948
695
|
}
|
|
949
696
|
}
|
|
950
697
|
|
|
951
698
|
// Add prototypes to sub-objects
|
|
952
699
|
for (const prop in toObject) {
|
|
953
|
-
if (
|
|
700
|
+
if (Util.isObject(toObject[prop])) {
|
|
954
701
|
util.attachProtoDeep(toObject[prop], depth - 1);
|
|
955
702
|
}
|
|
956
703
|
}
|
|
@@ -967,119 +714,29 @@ util.attachProtoDeep = function(toObject, depth) {
|
|
|
967
714
|
* with the returned object without affecting the input object.
|
|
968
715
|
*
|
|
969
716
|
* @protected
|
|
717
|
+
* @deprecated please see Util.cloneDeep
|
|
970
718
|
* @method cloneDeep
|
|
971
|
-
* @param parent {
|
|
972
|
-
* @param [depth=20] {
|
|
973
|
-
* @return {
|
|
974
|
-
*
|
|
975
|
-
* This method is copied from https://github.com/pvorb/node-clone/blob/17eea36140d61d97a9954c53417d0e04a00525d9/clone.js
|
|
976
|
-
*
|
|
977
|
-
* Copyright © 2011-2014 Paul Vorbach and contributors.
|
|
978
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
979
|
-
* of this software and associated documentation files (the “Software”), to deal
|
|
980
|
-
* in the Software without restriction, including without limitation the rights
|
|
981
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
982
|
-
* of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
983
|
-
* subject to the following conditions: The above copyright notice and this permission
|
|
984
|
-
* notice shall be included in all copies or substantial portions of the Software.
|
|
719
|
+
* @param parent {Object} The original object to copy from
|
|
720
|
+
* @param [depth=20] {number} Maximum depth (default 20)
|
|
721
|
+
* @return {Object} A new object with the elements copied from the copyFrom object
|
|
985
722
|
*/
|
|
986
723
|
util.cloneDeep = function cloneDeep(parent, depth, circular, prototype) {
|
|
987
|
-
|
|
988
|
-
// and children have the same index
|
|
989
|
-
const allParents = [];
|
|
990
|
-
const allChildren = [];
|
|
991
|
-
|
|
992
|
-
const useBuffer = typeof Buffer !== 'undefined';
|
|
993
|
-
|
|
994
|
-
if (typeof circular === 'undefined')
|
|
995
|
-
circular = true;
|
|
996
|
-
|
|
997
|
-
if (typeof depth === 'undefined')
|
|
998
|
-
depth = 20;
|
|
999
|
-
|
|
1000
|
-
// recurse this function so we don't reset allParents and allChildren
|
|
1001
|
-
function _clone(parent, depth) {
|
|
1002
|
-
// cloning null always returns null
|
|
1003
|
-
if (parent === null)
|
|
1004
|
-
return null;
|
|
1005
|
-
|
|
1006
|
-
if (depth === 0)
|
|
1007
|
-
return parent;
|
|
1008
|
-
|
|
1009
|
-
let child;
|
|
1010
|
-
if (typeof parent != 'object') {
|
|
1011
|
-
return parent;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
if (Array.isArray(parent)) {
|
|
1015
|
-
child = [];
|
|
1016
|
-
} else if (parent instanceof RegExp) {
|
|
1017
|
-
child = new RegExp(parent.source, util.getRegExpFlags(parent));
|
|
1018
|
-
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
|
|
1019
|
-
} else if (parent instanceof Date) {
|
|
1020
|
-
child = new Date(parent.getTime());
|
|
1021
|
-
} else if (useBuffer && Buffer.isBuffer(parent)) {
|
|
1022
|
-
child = Buffer.alloc(parent.length);
|
|
1023
|
-
parent.copy(child);
|
|
1024
|
-
return child;
|
|
1025
|
-
} else {
|
|
1026
|
-
if (typeof prototype === 'undefined') child = Object.create(Object.getPrototypeOf(parent));
|
|
1027
|
-
else child = Object.create(prototype);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
if (circular) {
|
|
1031
|
-
const index = allParents.indexOf(parent);
|
|
1032
|
-
|
|
1033
|
-
if (index != -1) {
|
|
1034
|
-
return allChildren[index];
|
|
1035
|
-
}
|
|
1036
|
-
allParents.push(parent);
|
|
1037
|
-
allChildren.push(child);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
for (const i in parent) {
|
|
1041
|
-
const propDescriptor = Object.getOwnPropertyDescriptor(parent,i);
|
|
1042
|
-
const hasGetter = ((typeof propDescriptor !== 'undefined') && (typeof propDescriptor.get !== 'undefined'));
|
|
1043
|
-
|
|
1044
|
-
if (hasGetter){
|
|
1045
|
-
Object.defineProperty(child,i,propDescriptor);
|
|
1046
|
-
} else if (util.isPromise(parent[i])) {
|
|
1047
|
-
child[i] = parent[i];
|
|
1048
|
-
} else {
|
|
1049
|
-
child[i] = _clone(parent[i], depth - 1);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
return child;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
return _clone(parent, depth);
|
|
724
|
+
return Util.cloneDeep(parent, depth, circular, prototype);
|
|
1057
725
|
};
|
|
1058
726
|
|
|
1059
727
|
/**
|
|
1060
728
|
* Set objects given a path as a string list
|
|
1061
729
|
*
|
|
1062
730
|
* @protected
|
|
731
|
+
* @deprecated see Util.setPath()
|
|
732
|
+
*
|
|
1063
733
|
* @method setPath
|
|
1064
|
-
* @param object {
|
|
734
|
+
* @param object {Object} - Object to set the property on
|
|
1065
735
|
* @param path {array[string]} - Array path to the property
|
|
1066
736
|
* @param value {*} - value to set, ignoring null
|
|
1067
737
|
*/
|
|
1068
738
|
util.setPath = function (object, path, value) {
|
|
1069
|
-
|
|
1070
|
-
if (value === null || path.length === 0) {
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
else if (path.length === 1) { // no more keys to make, so set the value
|
|
1074
|
-
object[path.shift()] = value;
|
|
1075
|
-
}
|
|
1076
|
-
else {
|
|
1077
|
-
nextKey = path.shift();
|
|
1078
|
-
if (!Object.hasOwnProperty.call(object, nextKey)) {
|
|
1079
|
-
object[nextKey] = {};
|
|
1080
|
-
}
|
|
1081
|
-
util.setPath(object[nextKey], path, value);
|
|
1082
|
-
}
|
|
739
|
+
Util.setPath(object, path, value);
|
|
1083
740
|
};
|
|
1084
741
|
|
|
1085
742
|
/**
|
|
@@ -1089,133 +746,53 @@ util.setPath = function (object, path, value) {
|
|
|
1089
746
|
* 3. And parent keys are created as necessary to retain the structure of substitutionMap.
|
|
1090
747
|
*
|
|
1091
748
|
* @protected
|
|
749
|
+
* @deprecated
|
|
750
|
+
*
|
|
1092
751
|
* @method substituteDeep
|
|
1093
|
-
* @param substitutionMap {
|
|
752
|
+
* @param substitutionMap {Object} - an object whose terminal (non-subobject) values are strings
|
|
1094
753
|
* @param variables {object[string:value]} - usually process.env, a flat object used to transform
|
|
1095
754
|
* terminal values in a copy of substitutionMap.
|
|
1096
|
-
* @returns {
|
|
755
|
+
* @returns {Object} - deep copy of substitutionMap with only those paths whose terminal values
|
|
1097
756
|
* corresponded to a key in `variables`
|
|
1098
757
|
*/
|
|
1099
758
|
util.substituteDeep = function (substitutionMap, variables) {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
function _substituteVars(map, vars, pathTo) {
|
|
1103
|
-
let parsedValue;
|
|
1104
|
-
for (const prop in map) {
|
|
1105
|
-
const value = map[prop];
|
|
1106
|
-
if (typeof(value) === 'string') { // We found a leaf variable name
|
|
1107
|
-
if (typeof vars[value] !== 'undefined' && vars[value] !== '') { // if the vars provide a value set the value in the result map
|
|
1108
|
-
util.setPath(result, pathTo.concat(prop), vars[value]);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
else if (util.isObject(value)) { // work on the subtree, giving it a clone of the pathTo
|
|
1112
|
-
if ('__name' in value && '__format' in value && typeof vars[value.__name] !== 'undefined' && vars[value.__name] !== '') {
|
|
1113
|
-
let parsedValue;
|
|
1114
|
-
try {
|
|
1115
|
-
parsedValue = util.parseString(vars[value.__name], value.__format);
|
|
1116
|
-
} catch(err) {
|
|
1117
|
-
err.message = '__format parser error in ' + value.__name + ': ' + err.message;
|
|
1118
|
-
throw err;
|
|
1119
|
-
}
|
|
1120
|
-
util.setPath(result, pathTo.concat(prop), parsedValue);
|
|
1121
|
-
} else {
|
|
1122
|
-
_substituteVars(value, vars, pathTo.concat(prop));
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
else {
|
|
1126
|
-
msg = "Illegal key type for substitution map at " + pathTo.join('.') + ': ' + typeof(value);
|
|
1127
|
-
throw Error(msg);
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
_substituteVars(substitutionMap, variables, []);
|
|
1133
|
-
return result;
|
|
1134
|
-
|
|
759
|
+
return FIRST_LOAD.substituteDeep(substitutionMap, variables);
|
|
1135
760
|
};
|
|
1136
761
|
|
|
1137
|
-
|
|
762
|
+
/**
|
|
763
|
+
* Map environment variables into the configuration if a mapping file,
|
|
1138
764
|
* `custom-environment-variables.EXT` exists.
|
|
1139
765
|
*
|
|
1140
766
|
* @protected
|
|
767
|
+
* @deprecated
|
|
1141
768
|
* @method getCustomEnvVars
|
|
1142
769
|
* @param configDir {string} - the passed configuration directory
|
|
1143
770
|
* @param extNames {Array[string]} - acceptable configuration file extension names.
|
|
1144
|
-
* @returns {
|
|
771
|
+
* @returns {Object} - mapped environment variables or {} if there are none
|
|
1145
772
|
*/
|
|
1146
773
|
util.getCustomEnvVars = function (configDir, extNames) {
|
|
1147
|
-
|
|
1148
|
-
let
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
const locatedFiles = util.locateMatchingFiles(configDir, allowedFiles);
|
|
1154
|
-
locatedFiles.forEach(function (fullFilename) {
|
|
1155
|
-
const configObj = util.parseFile(fullFilename);
|
|
1156
|
-
if (configObj) {
|
|
1157
|
-
const environmentSubstitutions = util.substituteDeep(configObj, process.env);
|
|
1158
|
-
util.extendDeep(result, environmentSubstitutions);
|
|
1159
|
-
}
|
|
1160
|
-
});
|
|
1161
|
-
return result;
|
|
774
|
+
let options = {...FIRST_LOAD.options, configDir};
|
|
775
|
+
let load = new Load(options);
|
|
776
|
+
|
|
777
|
+
load.loadCustomEnvVars(extNames);
|
|
778
|
+
|
|
779
|
+
return load.config;
|
|
1162
780
|
};
|
|
1163
781
|
|
|
1164
782
|
/**
|
|
1165
783
|
* Return true if two objects have equal contents.
|
|
1166
784
|
*
|
|
1167
785
|
* @protected
|
|
786
|
+
* @deprecated see Util.equalsDeep()
|
|
787
|
+
*
|
|
1168
788
|
* @method equalsDeep
|
|
1169
|
-
* @param object1 {
|
|
1170
|
-
* @param object2 {
|
|
789
|
+
* @param object1 {Object} The object to compare from
|
|
790
|
+
* @param object2 {Object} The object to compare with
|
|
1171
791
|
* @param depth {integer} An optional depth to prevent recursion. Default: 20.
|
|
1172
792
|
* @return {boolean} True if both objects have equivalent contents
|
|
1173
793
|
*/
|
|
1174
794
|
util.equalsDeep = function(object1, object2, depth) {
|
|
1175
|
-
|
|
1176
|
-
// Recursion detection
|
|
1177
|
-
const t = this;
|
|
1178
|
-
depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
|
|
1179
|
-
if (depth < 0) {
|
|
1180
|
-
return {};
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// Fast comparisons
|
|
1184
|
-
if (!object1 || !object2) {
|
|
1185
|
-
return false;
|
|
1186
|
-
}
|
|
1187
|
-
if (object1 === object2) {
|
|
1188
|
-
return true;
|
|
1189
|
-
}
|
|
1190
|
-
if (typeof(object1) != 'object' || typeof(object2) != 'object') {
|
|
1191
|
-
return false;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// They must have the same keys. If their length isn't the same
|
|
1195
|
-
// then they're not equal. If the keys aren't the same, the value
|
|
1196
|
-
// comparisons will fail.
|
|
1197
|
-
if (Object.keys(object1).length != Object.keys(object2).length) {
|
|
1198
|
-
return false;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
// Compare the values
|
|
1202
|
-
for (const prop in object1) {
|
|
1203
|
-
|
|
1204
|
-
// Call recursively if an object or array
|
|
1205
|
-
if (object1[prop] && typeof(object1[prop]) === 'object') {
|
|
1206
|
-
if (!util.equalsDeep(object1[prop], object2[prop], depth - 1)) {
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
if (object1[prop] !== object2[prop]) {
|
|
1212
|
-
return false;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// Test passed.
|
|
1218
|
-
return true;
|
|
795
|
+
return Util.equalsDeep(object1, object2, depth);
|
|
1219
796
|
};
|
|
1220
797
|
|
|
1221
798
|
/**
|
|
@@ -1230,18 +807,19 @@ util.equalsDeep = function(object1, object2, depth) {
|
|
|
1230
807
|
* the changes made to object1 which resulted in object2.
|
|
1231
808
|
* </p>
|
|
1232
809
|
*
|
|
810
|
+
* <b>This function has a number of issues and you may be better off with lodash</b>
|
|
811
|
+
*
|
|
1233
812
|
* @protected
|
|
813
|
+
* @deprecated please investigate lodash or similar tools for object traversal
|
|
1234
814
|
* @method diffDeep
|
|
1235
|
-
* @param object1 {
|
|
1236
|
-
* @param object2 {
|
|
815
|
+
* @param object1 {Object} The base object to compare to
|
|
816
|
+
* @param object2 {Object} The object to compare with
|
|
1237
817
|
* @param depth {integer} An optional depth to prevent recursion. Default: 20.
|
|
1238
|
-
* @return {
|
|
818
|
+
* @return {Object} A differential object, which if extended onto object1 would
|
|
1239
819
|
* result in object2.
|
|
1240
820
|
*/
|
|
1241
821
|
util.diffDeep = function(object1, object2, depth) {
|
|
1242
|
-
|
|
1243
822
|
// Recursion detection
|
|
1244
|
-
const t = this;
|
|
1245
823
|
const diff = {};
|
|
1246
824
|
depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
|
|
1247
825
|
if (depth < 0) {
|
|
@@ -1253,13 +831,13 @@ util.diffDeep = function(object1, object2, depth) {
|
|
|
1253
831
|
for (const parm in object2) {
|
|
1254
832
|
const value1 = object1[parm];
|
|
1255
833
|
const value2 = object2[parm];
|
|
1256
|
-
if (value1 && value2 &&
|
|
1257
|
-
if (!(
|
|
834
|
+
if (value1 && value2 && Util.isObject(value2)) {
|
|
835
|
+
if (!(Util.equalsDeep(value1, value2))) {
|
|
1258
836
|
diff[parm] = util.diffDeep(value1, value2, depth - 1);
|
|
1259
837
|
}
|
|
1260
838
|
}
|
|
1261
839
|
else if (Array.isArray(value1) && Array.isArray(value2)) {
|
|
1262
|
-
if(!
|
|
840
|
+
if(!Util.equalsDeep(value1, value2)) {
|
|
1263
841
|
diff[parm] = value2;
|
|
1264
842
|
}
|
|
1265
843
|
}
|
|
@@ -1270,7 +848,6 @@ util.diffDeep = function(object1, object2, depth) {
|
|
|
1270
848
|
|
|
1271
849
|
// Return the diff object
|
|
1272
850
|
return diff;
|
|
1273
|
-
|
|
1274
851
|
};
|
|
1275
852
|
|
|
1276
853
|
/**
|
|
@@ -1280,67 +857,15 @@ util.diffDeep = function(object1, object2, depth) {
|
|
|
1280
857
|
* replacing individual elements instead.
|
|
1281
858
|
*
|
|
1282
859
|
* @protected
|
|
860
|
+
* @deprecated please use Util.extendDeep()
|
|
1283
861
|
* @method extendDeep
|
|
1284
|
-
* @param mergeInto {
|
|
1285
|
-
* @param mergeFrom... {object
|
|
862
|
+
* @param mergeInto {Object} The object to merge into
|
|
863
|
+
* @param mergeFrom... {object[]} - Any number of objects to merge from
|
|
1286
864
|
* @param depth {integer} An optional depth to prevent recursion. Default: 20.
|
|
1287
|
-
* @return {
|
|
865
|
+
* @return {Object} The altered mergeInto object is returned
|
|
1288
866
|
*/
|
|
1289
867
|
util.extendDeep = function(mergeInto, ...vargs) {
|
|
1290
|
-
|
|
1291
|
-
// Initialize
|
|
1292
|
-
let depth = vargs.pop();
|
|
1293
|
-
if (typeof(depth) != 'number') {
|
|
1294
|
-
vargs.push(depth);
|
|
1295
|
-
depth = DEFAULT_CLONE_DEPTH;
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
// Recursion detection
|
|
1299
|
-
if (depth < 0) {
|
|
1300
|
-
return mergeInto;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
// Cycle through each object to extend
|
|
1304
|
-
vargs.forEach(function(mergeFrom) {
|
|
1305
|
-
|
|
1306
|
-
// Cycle through each element of the object to merge from
|
|
1307
|
-
for (const prop in mergeFrom) {
|
|
1308
|
-
|
|
1309
|
-
// save original value in deferred elements
|
|
1310
|
-
const fromIsDeferredFunc = mergeFrom[prop] instanceof DeferredConfig;
|
|
1311
|
-
const isDeferredFunc = mergeInto[prop] instanceof DeferredConfig;
|
|
1312
|
-
|
|
1313
|
-
if (fromIsDeferredFunc && Object.hasOwnProperty.call(mergeInto, prop)) {
|
|
1314
|
-
mergeFrom[prop]._original = isDeferredFunc ? mergeInto[prop]._original : mergeInto[prop];
|
|
1315
|
-
}
|
|
1316
|
-
// Extend recursively if both elements are objects and target is not really a deferred function
|
|
1317
|
-
if (mergeFrom[prop] instanceof Date) {
|
|
1318
|
-
mergeInto[prop] = mergeFrom[prop];
|
|
1319
|
-
} if (mergeFrom[prop] instanceof RegExp) {
|
|
1320
|
-
mergeInto[prop] = mergeFrom[prop];
|
|
1321
|
-
} else if (util.isObject(mergeInto[prop]) && util.isObject(mergeFrom[prop]) && !isDeferredFunc) {
|
|
1322
|
-
util.extendDeep(mergeInto[prop], mergeFrom[prop], depth - 1);
|
|
1323
|
-
}
|
|
1324
|
-
else if (util.isPromise(mergeFrom[prop])) {
|
|
1325
|
-
mergeInto[prop] = mergeFrom[prop];
|
|
1326
|
-
}
|
|
1327
|
-
// Copy recursively if the mergeFrom element is an object (or array or fn)
|
|
1328
|
-
else if (mergeFrom[prop] && typeof mergeFrom[prop] === 'object') {
|
|
1329
|
-
mergeInto[prop] = util.cloneDeep(mergeFrom[prop], depth -1);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// Copy property descriptor otherwise, preserving accessors
|
|
1333
|
-
else if (Object.getOwnPropertyDescriptor(Object(mergeFrom), prop)){
|
|
1334
|
-
Object.defineProperty(mergeInto, prop, Object.getOwnPropertyDescriptor(Object(mergeFrom), prop));
|
|
1335
|
-
} else {
|
|
1336
|
-
mergeInto[prop] = mergeFrom[prop];
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
});
|
|
1340
|
-
|
|
1341
|
-
// Chain
|
|
1342
|
-
return mergeInto;
|
|
1343
|
-
|
|
868
|
+
return Util.extendDeep(mergeInto, ...vargs);
|
|
1344
869
|
};
|
|
1345
870
|
|
|
1346
871
|
/**
|
|
@@ -1349,24 +874,26 @@ util.extendDeep = function(mergeInto, ...vargs) {
|
|
|
1349
874
|
* The argument is an object if it's a JS object, but not an array.
|
|
1350
875
|
*
|
|
1351
876
|
* @protected
|
|
877
|
+
* @deprecated please use Util.isObject()
|
|
1352
878
|
* @method isObject
|
|
1353
879
|
* @param obj {*} An argument of any type.
|
|
1354
880
|
* @return {boolean} TRUE if the arg is an object, FALSE if not
|
|
1355
881
|
*/
|
|
1356
882
|
util.isObject = function(obj) {
|
|
1357
|
-
return
|
|
883
|
+
return Util.isObject(obj);
|
|
1358
884
|
};
|
|
1359
885
|
|
|
1360
886
|
/**
|
|
1361
887
|
* Is the specified argument a javascript promise?
|
|
1362
888
|
*
|
|
1363
889
|
* @protected
|
|
890
|
+
* @deprecated please use Util.isPromise()
|
|
1364
891
|
* @method isPromise
|
|
1365
892
|
* @param obj {*} An argument of any type.
|
|
1366
893
|
* @returns {boolean}
|
|
1367
894
|
*/
|
|
1368
895
|
util.isPromise = function(obj) {
|
|
1369
|
-
return
|
|
896
|
+
return Util.isPromise(obj);
|
|
1370
897
|
};
|
|
1371
898
|
|
|
1372
899
|
/**
|
|
@@ -1379,17 +906,13 @@ util.isPromise = function(obj) {
|
|
|
1379
906
|
* </p>
|
|
1380
907
|
*
|
|
1381
908
|
* @method initParam
|
|
909
|
+
* @deprecated
|
|
1382
910
|
* @param paramName {String} Name of the parameter
|
|
1383
|
-
* @param [defaultValue] {
|
|
1384
|
-
* @return {
|
|
911
|
+
* @param [defaultValue] {*} Default value of the parameter
|
|
912
|
+
* @return {*} The found value, or default value
|
|
1385
913
|
*/
|
|
1386
914
|
util.initParam = function (paramName, defaultValue) {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
// Record and return the value
|
|
1390
|
-
const value = util.getCmdLineArg(paramName) || process.env[paramName] || defaultValue;
|
|
1391
|
-
env[paramName] = value;
|
|
1392
|
-
return value;
|
|
915
|
+
return FIRST_LOAD.initParam(paramName, defaultValue);
|
|
1393
916
|
}
|
|
1394
917
|
|
|
1395
918
|
/**
|
|
@@ -1404,22 +927,15 @@ util.initParam = function (paramName, defaultValue) {
|
|
|
1404
927
|
* </p>
|
|
1405
928
|
*
|
|
1406
929
|
* @method getCmdLineArg
|
|
930
|
+
* @deprecated
|
|
1407
931
|
* @param searchFor {String} The argument name to search for
|
|
1408
932
|
* @return {*} false if the argument was not found, the argument value if found
|
|
1409
933
|
*/
|
|
1410
934
|
util.getCmdLineArg = function (searchFor) {
|
|
1411
|
-
|
|
1412
|
-
const argName = '--' + searchFor + '=';
|
|
1413
|
-
|
|
1414
|
-
for (let argvIt = 0; argvIt < cmdLineArgs.length; argvIt++) {
|
|
1415
|
-
if (cmdLineArgs[argvIt].indexOf(argName) === 0) {
|
|
1416
|
-
return cmdLineArgs[argvIt].substr(argName.length);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
return false;
|
|
935
|
+
return FIRST_LOAD.getCmdLineArg(searchFor);
|
|
1421
936
|
}
|
|
1422
937
|
|
|
938
|
+
|
|
1423
939
|
/**
|
|
1424
940
|
* <p>Get a Config Environment Variable Value</p>
|
|
1425
941
|
*
|
|
@@ -1429,11 +945,12 @@ util.getCmdLineArg = function (searchFor) {
|
|
|
1429
945
|
* </p>
|
|
1430
946
|
*
|
|
1431
947
|
* @method getEnv
|
|
948
|
+
* @deprecated
|
|
1432
949
|
* @param varName {String} The environment variable name
|
|
1433
950
|
* @return {String} The value of the environment variable
|
|
1434
951
|
*/
|
|
1435
952
|
util.getEnv = function (varName) {
|
|
1436
|
-
return
|
|
953
|
+
return FIRST_LOAD.getEnv(varName);
|
|
1437
954
|
}
|
|
1438
955
|
|
|
1439
956
|
|
|
@@ -1441,15 +958,13 @@ util.getEnv = function (varName) {
|
|
|
1441
958
|
/**
|
|
1442
959
|
* Returns a string of flags for regular expression `re`.
|
|
1443
960
|
*
|
|
961
|
+
* @protected
|
|
962
|
+
* @deprecated This is an internal implementation detail
|
|
1444
963
|
* @param {RegExp} re Regular expression
|
|
1445
964
|
* @returns {string} Flags
|
|
1446
965
|
*/
|
|
1447
966
|
util.getRegExpFlags = function (re) {
|
|
1448
|
-
|
|
1449
|
-
re.global && (flags += 'g');
|
|
1450
|
-
re.ignoreCase && (flags += 'i');
|
|
1451
|
-
re.multiline && (flags += 'm');
|
|
1452
|
-
return flags;
|
|
967
|
+
return Util.getRegExpFlags(re);
|
|
1453
968
|
};
|
|
1454
969
|
|
|
1455
970
|
/**
|
|
@@ -1459,12 +974,12 @@ util.getRegExpFlags = function (re) {
|
|
|
1459
974
|
* @returns {Object} The cloned config or part of the config
|
|
1460
975
|
*/
|
|
1461
976
|
util.toObject = function(config) {
|
|
1462
|
-
return
|
|
977
|
+
return Util.toObject(config || this);
|
|
1463
978
|
};
|
|
1464
979
|
|
|
1465
980
|
// Run strictness checks on NODE_ENV and NODE_APP_INSTANCE and throw an error if there's a problem.
|
|
1466
981
|
util.runStrictnessChecks = function (config) {
|
|
1467
|
-
if (
|
|
982
|
+
if (FIRST_LOAD.initParam('SUPPRESS_STRICTNESS_CHECK')) {
|
|
1468
983
|
return;
|
|
1469
984
|
}
|
|
1470
985
|
|
|
@@ -1473,27 +988,32 @@ util.runStrictnessChecks = function (config) {
|
|
|
1473
988
|
return Path.basename(src.name);
|
|
1474
989
|
});
|
|
1475
990
|
|
|
1476
|
-
|
|
991
|
+
FIRST_LOAD.options.nodeEnv.forEach(function(env) {
|
|
1477
992
|
// Throw an exception if there's no explicit config file for NODE_ENV
|
|
1478
993
|
const anyFilesMatchEnv = sourceFilenames.some(function (filename) {
|
|
1479
994
|
return filename.match(env);
|
|
1480
995
|
});
|
|
1481
996
|
// development is special-cased because it's the default value
|
|
1482
997
|
if (env && (env !== 'development') && !anyFilesMatchEnv) {
|
|
1483
|
-
_warnOrThrow(
|
|
998
|
+
_warnOrThrow(`${FIRST_LOAD.getEnv("nodeEnv")} value of '${env}' did not match any deployment config file names.`);
|
|
1484
999
|
}
|
|
1485
1000
|
// Throw if NODE_ENV matches' default' or 'local'
|
|
1486
1001
|
if ((env === 'default') || (env === 'local')) {
|
|
1487
|
-
_warnOrThrow(
|
|
1002
|
+
_warnOrThrow(`${FIRST_LOAD.getEnv("nodeEnv")} value of '${env}' is ambiguous.`);
|
|
1488
1003
|
}
|
|
1489
1004
|
});
|
|
1490
1005
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1006
|
+
let appInstance = FIRST_LOAD.options.appInstance;
|
|
1007
|
+
|
|
1008
|
+
if (appInstance) {
|
|
1009
|
+
// Throw an exception if there's no explicit config file for NODE_APP_INSTANCE
|
|
1010
|
+
const anyFilesMatchInstance = sourceFilenames.some(function (filename) {
|
|
1011
|
+
return filename.match(appInstance);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
if (!anyFilesMatchInstance) {
|
|
1015
|
+
_warnOrThrow(`NODE_APP_INSTANCE value of '${appInstance}' did not match any instance config file names.`);
|
|
1016
|
+
}
|
|
1497
1017
|
}
|
|
1498
1018
|
|
|
1499
1019
|
function _warnOrThrow (msg) {
|
|
@@ -1511,24 +1031,18 @@ util.runStrictnessChecks = function (config) {
|
|
|
1511
1031
|
}
|
|
1512
1032
|
};
|
|
1513
1033
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
if (configDir.indexOf('.') === 0) {
|
|
1517
|
-
return Path.join(process.cwd(), configDir);
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
return configDir;
|
|
1521
|
-
}
|
|
1034
|
+
/** @type {Load} */
|
|
1035
|
+
const FIRST_LOAD = Load.fromEnvironment();
|
|
1522
1036
|
|
|
1523
1037
|
// Instantiate and export the configuration
|
|
1524
1038
|
const config = module.exports = new Config();
|
|
1525
1039
|
|
|
1526
1040
|
// copy method to util for backwards compatibility
|
|
1527
|
-
util.stripYamlComments =
|
|
1041
|
+
util.stripYamlComments = FIRST_LOAD.parser.stripYamlComments;
|
|
1528
1042
|
|
|
1529
1043
|
// Produce warnings if the configuration is empty
|
|
1530
|
-
const showWarnings = !(
|
|
1044
|
+
const showWarnings = !(FIRST_LOAD.initParam('SUPPRESS_NO_CONFIG_WARNING'));
|
|
1531
1045
|
if (showWarnings && Object.keys(config).length === 0) {
|
|
1532
|
-
console.error('WARNING: No configurations found in configuration directory:' +
|
|
1046
|
+
console.error('WARNING: No configurations found in configuration directory:' + FIRST_LOAD.options.configDir);
|
|
1533
1047
|
console.error('WARNING: To disable this warning set SUPPRESS_NO_CONFIG_WARNING in the environment.');
|
|
1534
1048
|
}
|