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/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
- let Parser = require('../parser');
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 {object} - The top level configuration object
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
- util.extendDeep(t, util.loadFileConfigs());
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 (!util.initParam('ALLOW_CONFIG_MUTATIONS', false)) {
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 = getImpl(t, property);
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 isPresent {boolean} - True if the property is defined, false if not defined.
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 getImpl(t, property) !== "undefined";
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 {object} - The default module configuration.
242
- * @return moduleConfig {object} - The module level configuration object.
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 moduleConfig = util.cloneDeep(defaultProperties);
213
+ const path = moduleName.split('.');
214
+ const moduleConfig = FIRST_LOAD.setModuleDefaults(moduleName, defaultProperties);
215
+ let existing = Util.getPath(t, path);
249
216
 
250
- // Set module defaults into the first sources element
251
- if (configSources.length === 0 || configSources[0].name !== 'Module Defaults') {
252
- configSources.splice(0, 0, {
253
- name: 'Module Defaults',
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 (!util.initParam('ALLOW_CONFIG_MUTATIONS', false)) {
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(getImpl(t, moduleName));
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 {object} - The object to make a hidden property into.
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 {object} - The original object is returned - for chaining.
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 {object} - The object to specify immutable properties for
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 {object} - The original object is returned - for chaining.
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 (util.isObject(item) || Array.isArray(item)) util.makeImmutable(item) })
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 (util.isObject(value)) {
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
- Object.defineProperty(object, propertyName, {
449
- value: value,
450
- writable : false,
451
- configurable: false
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
- const t = this;
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
- if (options !== undefined && typeof options[optionName] !== 'undefined'){
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 { object | undefined } parsing options. Current supported option: skipConfigSources: true|false
558
- * @return config {Object} The configuration object
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
- // Initialize
563
- const t = this;
564
- const config = {};
504
+ if (configDir) {
505
+ let opts = {...FIRST_LOAD.options, configDir, ...options};
506
+ newLoad = new Load(opts);
507
+ newLoad.scan();
565
508
 
566
- // Specify variables that can be used to define the environment
567
- const node_env_var_names = ['NODE_CONFIG_ENV', 'NODE_ENV'];
568
-
569
- // Loop through the variables to try and set environment
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
- // If we haven't successfully set the environment using the variables, we'll default it
579
- if (!NODE_ENV) {
580
- NODE_ENV = 'development';
514
+ FIRST_LOAD.sourcesBug(newLoad.getSources());
581
515
  }
582
516
 
583
- node_env_var_names.forEach(node_env_var_name => {
584
- env[node_env_var_name] = NODE_ENV;
585
- });
517
+ return newLoad.config;
518
+ }
586
519
 
587
- // Split files name, for loading multiple files.
588
- NODE_ENV = NODE_ENV.split(',');
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
- let dir = configDir || util.initParam('NODE_CONFIG_DIR', Path.join( process.cwd(), 'config') );
591
- dir = _toAbsolutePath(dir);
531
+ // Override configurations from the $NODE_CONFIG environment variable
532
+ let envConfig = {};
592
533
 
593
- APP_INSTANCE = util.initParam('NODE_APP_INSTANCE');
594
- CONFIG_SKIP_GITCRYPT = util.initParam('CONFIG_SKIP_GITCRYPT');
534
+ load.setEnv("CONFIG_DIR", options.configDir);
595
535
 
596
- NODE_CONFIG_PARSER = util.initParam('NODE_CONFIG_PARSER');
597
- if (NODE_CONFIG_PARSER) {
536
+ if (process.env.NODE_CONFIG) {
598
537
  try {
599
- const parserModule = Path.isAbsolute(NODE_CONFIG_PARSER)
600
- ? NODE_CONFIG_PARSER
601
- : Path.join(dir, NODE_CONFIG_PARSER);
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
- // Store the hostname that won.
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
- NODE_ENV.forEach(function(env) {
647
- baseNames.push('local', 'local-' + env);
648
- });
649
-
650
- const allowedFiles = {};
651
- let resolutionIndex = 1;
652
- const extNames = Parser.getFilesOrder();
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
- // Place the mixed NODE_CONFIG into the environment
715
- env['NODE_CONFIG'] = JSON.stringify(util.extendDeep(envConfig, cmdLineConfig, {}));
555
+ additional.push({ name: "--NODE_CONFIG argument", config: cmdLineConfig });
716
556
  }
717
557
 
718
- // Override with environment variables if there is a custom-environment-variables.EXT mapping file
719
- const customEnvVars = util.getCustomEnvVars(dir, extNames);
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
- util.resolveDeferredConfigs(config);
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 {object} an object. keys and supported filenames
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 configDirs.split(Path.delimiter)
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
- const deferred = [];
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, jsonc, cson, hjson, json5, properties, xml)
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
- const t = this; // Initialize
825
- let configObject = null;
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
- // Parse the file based on extension
846
- try {
620
+ loadInfo.loadFile(fullFilename);
847
621
 
848
- // skip if it's a gitcrypt file and CONFIG_SKIP_GITCRYPT is true
849
- if (CONFIG_SKIP_GITCRYPT) {
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
- // Keep track of this configuration sources, including empty ones, unless the skipConfigSources flag is set to true in the options
866
- const skipConfigSources = util.getOption(options,'skipConfigSources', false);
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 = Parsed with a JSON5 parser
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
- const parser = Parser.getParser(format);
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 toObject
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
- util.makeHidden(toObject, fnName, Config.prototype[fnName]);
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 (util.isObject(toObject[prop])) {
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 {object} The original object to copy from
972
- * @param [depth=20] {Integer} Maximum depth (default 20)
973
- * @return {object} A new object with the elements copied from the copyFrom object
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
- // maintain two arrays for circular references, where corresponding parents
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 {object} - Object to set the property on
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
- let nextKey = null;
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 {object} - an object whose terminal (non-subobject) values are strings
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 {object} - deep copy of substitutionMap with only those paths whose terminal values
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
- const result = {};
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
- /* Map environment variables into the configuration if a mapping file,
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 {object} - mapped environment variables or {} if there are none
771
+ * @returns {Object} - mapped environment variables or {} if there are none
1145
772
  */
1146
773
  util.getCustomEnvVars = function (configDir, extNames) {
1147
- const result = {};
1148
- let resolutionIndex = 1;
1149
- const allowedFiles = {};
1150
- extNames.forEach(function (extName) {
1151
- allowedFiles['custom-environment-variables' + '.' + extName] = resolutionIndex++;
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 {object} The object to compare from
1170
- * @param object2 {object} The object to compare with
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 {object} The base object to compare to
1236
- * @param object2 {object} The object to compare with
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 {object} A differential object, which if extended onto object1 would
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 && util.isObject(value2)) {
1257
- if (!(util.equalsDeep(value1, value2))) {
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(!util.equalsDeep(value1, value2)) {
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 {object} The object to merge into
1285
- * @param mergeFrom... {object...} - Any number of objects to merge from
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 {object} The altered mergeInto object is returned
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 (obj !== null) && (typeof obj === 'object') && !(Array.isArray(obj));
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 Object.prototype.toString.call(obj) === '[object Promise]';
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] {Any} Default value of the parameter
1384
- * @return {Any} The found value, or default value
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
- const t = this;
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
- const cmdLineArgs = process.argv.slice(2, process.argv.length);
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 env[varName];
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
- let flags = '';
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 JSON.parse(JSON.stringify(config || this));
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 (util.initParam('SUPPRESS_STRICTNESS_CHECK')) {
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
- NODE_ENV.forEach(function(env) {
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(NODE_ENV_VAR_NAME+" value of '"+env+"' did not match any deployment config file names.");
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(NODE_ENV_VAR_NAME+" value of '"+env+"' is ambiguous.");
1002
+ _warnOrThrow(`${FIRST_LOAD.getEnv("nodeEnv")} value of '${env}' is ambiguous.`);
1488
1003
  }
1489
1004
  });
1490
1005
 
1491
- // Throw an exception if there's no explicit config file for NODE_APP_INSTANCE
1492
- const anyFilesMatchInstance = sourceFilenames.some(function (filename) {
1493
- return filename.match(APP_INSTANCE);
1494
- });
1495
- if (APP_INSTANCE && !anyFilesMatchInstance) {
1496
- _warnOrThrow("NODE_APP_INSTANCE value of '"+APP_INSTANCE+"' did not match any instance config file names.");
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
- // Helper functions shared accross object members
1515
- function _toAbsolutePath (configDir) {
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 = Parser.stripYamlComments;
1041
+ util.stripYamlComments = FIRST_LOAD.parser.stripYamlComments;
1528
1042
 
1529
1043
  // Produce warnings if the configuration is empty
1530
- const showWarnings = !(util.initParam('SUPPRESS_NO_CONFIG_WARNING'));
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:' +CONFIG_DIR);
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
  }