config 3.0.1 → 3.2.2

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/History.md CHANGED
@@ -1,3 +1,25 @@
1
+ 3.2.2 / 2019-07-20
2
+ ==================
3
+
4
+ * Fixed delimiter bug in configDirs to match O/S delimiter - @iMoses
5
+
6
+ 3.2.1 / 2019-07-18
7
+ ==================
8
+
9
+ * Fixed TypeError: obj.toString is not a function - @leosuncin
10
+
11
+ 3.2.0 / 2019-07-11
12
+ ==================
13
+
14
+ * Asynchronous configs - @iMoses
15
+ * Multiple config directories - @iMoses
16
+ * Improved parser support - @iMoses
17
+
18
+ 3.1.0 / 2019-04-07
19
+ ==================
20
+
21
+ * Support of module.exports syntax for TS config files @keenondrums
22
+
1
23
  3.0.1 / 2018-12-16
2
24
  ==================
3
25
 
package/README.md CHANGED
@@ -80,13 +80,13 @@ $ vi config/default.json
80
80
  **Use configs in your code:**
81
81
 
82
82
  ```js
83
- var config = require('config');
83
+ const config = require('config');
84
84
  //...
85
- var dbConfig = config.get('Customer.dbConfig');
85
+ const dbConfig = config.get('Customer.dbConfig');
86
86
  db.connect(dbConfig, ...);
87
87
 
88
88
  if (config.has('optionalFeature.detail')) {
89
- var detail = config.get('optionalFeature.detail');
89
+ const detail = config.get('optionalFeature.detail');
90
90
  //...
91
91
  }
92
92
  ```
@@ -141,13 +141,13 @@ Contributors
141
141
  <td><img src=https://avatars0.githubusercontent.com/u/66902?v=4><a href="https://github.com/leachiM2k">leachiM2k</a></td>
142
142
  <td><img src=https://avatars1.githubusercontent.com/u/791137?v=4><a href="https://github.com/josx">josx</a></td>
143
143
  </tr><tr><td><img src=https://avatars2.githubusercontent.com/u/133277?v=4><a href="https://github.com/enyo">enyo</a></td>
144
+ <td><img src=https://avatars1.githubusercontent.com/u/4307697?v=4><a href="https://github.com/leosuncin">leosuncin</a></td>
144
145
  <td><img src=https://avatars3.githubusercontent.com/u/1077378?v=4><a href="https://github.com/arthanzel">arthanzel</a></td>
145
146
  <td><img src=https://avatars2.githubusercontent.com/u/1656140?v=4><a href="https://github.com/eheikes">eheikes</a></td>
146
- <td><img src=https://avatars0.githubusercontent.com/u/355800?v=4><a href="https://github.com/diversario">diversario</a></td>
147
147
  <td><img src=https://avatars3.githubusercontent.com/u/138707?v=4><a href="https://github.com/th507">th507</a></td>
148
148
  <td><img src=https://avatars2.githubusercontent.com/u/506460?v=4><a href="https://github.com/Osterjour">Osterjour</a></td>
149
149
  </tr><tr><td><img src=https://avatars0.githubusercontent.com/u/842998?v=4><a href="https://github.com/nsabovic">nsabovic</a></td>
150
- <td><img src=https://avatars0.githubusercontent.com/u/5138570?v=4><a href="https://github.com/ScionOfBytes">ScionOfBytes</a></td>
150
+ <td><img src=https://avatars0.githubusercontent.com/u/5138570?v=4><a href="https://github.com/BadgerBadgerBadgerBadger">BadgerBadgerBadgerBadger</a></td>
151
151
  <td><img src=https://avatars2.githubusercontent.com/u/2529835?v=4><a href="https://github.com/simon-scherzinger">simon-scherzinger</a></td>
152
152
  <td><img src=https://avatars1.githubusercontent.com/u/175627?v=4><a href="https://github.com/axelhzf">axelhzf</a></td>
153
153
  <td><img src=https://avatars3.githubusercontent.com/u/7782055?v=4><a href="https://github.com/benkroeger">benkroeger</a></td>
package/async.js ADDED
@@ -0,0 +1,70 @@
1
+ var asyncSymbol = Symbol('asyncSymbol');
2
+ var deferConfig = require('./defer').deferConfig;
3
+
4
+ /**
5
+ * @param promiseOrFunc the promise will determine a property's value once resolved
6
+ * can also be a function to defer which resolves to a promise
7
+ * @returns {Promise} a marked promise to be resolve later using `resolveAsyncConfigs`
8
+ */
9
+ function asyncConfig(promiseOrFunc) {
10
+ if (typeof promiseOrFunc === 'function') { // also acts as deferConfig
11
+ return deferConfig(function (config, original) {
12
+ var release;
13
+ function registerRelease(resolve) { release = resolve; }
14
+ function callFunc() { return promiseOrFunc.call(config, config, original); }
15
+ var promise = asyncConfig(new Promise(registerRelease).then(callFunc));
16
+ promise.release = release;
17
+ return promise;
18
+ });
19
+ }
20
+ var promise = promiseOrFunc;
21
+ promise.async = asyncSymbol;
22
+ promise.prepare = function(config, prop, property) {
23
+ if (promise.release) {
24
+ promise.release();
25
+ }
26
+ return function() {
27
+ return promise.then(function(value) {
28
+ Object.defineProperty(prop, property, {value: value});
29
+ });
30
+ };
31
+ };
32
+ return promise;
33
+ }
34
+
35
+ /**
36
+ * Do not use `config.get` before executing this method, it will freeze the config object
37
+ * @param config the main config object, returned from require('config')
38
+ * @returns {Promise<config>} once all promises are resolved, return the original config object
39
+ */
40
+ function resolveAsyncConfigs(config) {
41
+ var promises = [];
42
+ var resolvers = [];
43
+ (function iterate(prop) {
44
+ var propsToSort = [];
45
+ for (var property in prop) {
46
+ if (prop.hasOwnProperty(property) && prop[property] != null) {
47
+ propsToSort.push(property);
48
+ }
49
+ }
50
+ propsToSort.sort().forEach(function(property) {
51
+ if (prop[property].constructor === Object) {
52
+ iterate(prop[property]);
53
+ }
54
+ else if (prop[property].constructor === Array) {
55
+ prop[property].forEach(iterate);
56
+ }
57
+ else if (prop[property] && prop[property].async === asyncSymbol) {
58
+ resolvers.push(prop[property].prepare(config, prop, property));
59
+ promises.push(prop[property]);
60
+ }
61
+ });
62
+ })(config);
63
+ return Promise.all(promises).then(function() {
64
+ resolvers.forEach(function(resolve) { resolve(); });
65
+ return config;
66
+ });
67
+ }
68
+
69
+ module.exports.asyncConfig = asyncConfig;
70
+ module.exports.resolveAsyncConfigs = resolveAsyncConfigs;
package/defer.js CHANGED
@@ -1,12 +1,21 @@
1
1
  // Create a deferredConfig prototype so that we can check for it when reviewing the configs later.
2
- function DeferredConfig () {
3
- }
4
- DeferredConfig.prototype.resolve = function (config, original) {};
2
+ function DeferredConfig() {}
3
+ DeferredConfig.prototype.prepare = function() {};
4
+ DeferredConfig.prototype.resolve = function() {};
5
5
 
6
6
  // Accept a function that we'll use to resolve this value later and return a 'deferred' configuration value to resolve it later.
7
- function deferConfig (func) {
7
+ function deferConfig(func) {
8
8
  var obj = Object.create(DeferredConfig.prototype);
9
- obj.resolve = func;
9
+ obj.prepare = function(config, prop, property) {
10
+ var original = prop[property]._original;
11
+ obj.resolve = function() {
12
+ var value = func.call(config, config, original);
13
+ Object.defineProperty(prop, property, {value: value});
14
+ return value;
15
+ };
16
+ Object.defineProperty(prop, property, {get: function() { return obj.resolve(); }});
17
+ return obj;
18
+ };
10
19
  return obj;
11
20
  }
12
21
 
package/lib/config.js CHANGED
@@ -4,19 +4,10 @@
4
4
  // http://lorenwest.github.com/node-config
5
5
 
6
6
  // Dependencies
7
- var Yaml = null, // External libraries are lazy-loaded
8
- VisionmediaYaml = null, // only if these file types exist.
9
- Coffee = null,
10
- Iced = null,
11
- CSON = null,
12
- PPARSER = null,
13
- JSON5 = null,
14
- TOML = null,
15
- HJSON = null,
16
- XML = null,
17
- deferConfig = require('../defer').deferConfig,
7
+ var deferConfig = require('../defer').deferConfig,
18
8
  DeferredConfig = require('../defer').DeferredConfig,
19
9
  RawConfig = require('../raw').RawConfig,
10
+ Parser = require('../parser'),
20
11
  Utils = require('util'),
21
12
  Path = require('path'),
22
13
  FileSystem = require('fs');
@@ -25,6 +16,7 @@ var Yaml = null, // External libraries are lazy-loaded
25
16
  var DEFAULT_CLONE_DEPTH = 20,
26
17
  NODE_CONFIG, CONFIG_DIR, RUNTIME_JSON_FILENAME, NODE_ENV, APP_INSTANCE,
27
18
  HOST, HOSTNAME, ALLOW_CONFIG_MUTATIONS, CONFIG_SKIP_GITCRYPT,
19
+ NODE_CONFIG_PARSER,
28
20
  env = {},
29
21
  privateUtil = {},
30
22
  deprecationWarnings = {},
@@ -32,20 +24,6 @@ var DEFAULT_CLONE_DEPTH = 20,
32
24
  checkMutability = true, // Check for mutability/immutability on first get
33
25
  gitCryptTestRegex = /^.GITCRYPT/; // regular expression to test for gitcrypt files.
34
26
 
35
- // Define soft dependencies so transpilers don't include everything
36
- var COFFEE_2_DEP = "coffeescript",
37
- COFFEE_DEP = "coffee-script",
38
- ICED_DEP = "iced-coffee-script",
39
- JS_YAML_DEP = "js-yaml",
40
- YAML_DEP = "yaml",
41
- JSON5_DEP = "json5",
42
- HJSON_DEP = "hjson",
43
- TOML_DEP = "toml",
44
- CSON_DEP = "cson",
45
- PPARSER_DEP = "properties",
46
- XML_DEP = "x2js",
47
- TS_DEP = "ts-node";
48
-
49
27
  /**
50
28
  * <p>Application Configurations</p>
51
29
  *
@@ -129,7 +107,9 @@ var Config = function() {
129
107
 
130
108
  // Bind all utility functions to this
131
109
  for (var fnName in util) {
132
- util[fnName] = util[fnName].bind(t);
110
+ if (typeof util[fnName] === 'function') {
111
+ util[fnName] = util[fnName].bind(t);
112
+ }
133
113
  }
134
114
 
135
115
  // Merge configurations into this
@@ -151,7 +131,7 @@ var util = Config.prototype.util = {};
151
131
  * @private
152
132
  * @method getImpl
153
133
  * @param object {object} - Object to get the property for
154
- * @param property {string | array[string]} - The property name to get (as an array or '.' delimited string)
134
+ * @param property {string|string[]} - The property name to get (as an array or '.' delimited string)
155
135
  * @return value {*} - Property value, including undefined if not defined.
156
136
  */
157
137
  var getImpl= function(object, property) {
@@ -169,7 +149,6 @@ var getImpl= function(object, property) {
169
149
  return getImpl(value, elems.slice(1));
170
150
  };
171
151
 
172
-
173
152
  /**
174
153
  * <p>Get a configuration value</p>
175
154
  *
@@ -553,6 +532,20 @@ util.loadFileConfigs = function(configDir) {
553
532
  // This is for backward compatibility
554
533
  RUNTIME_JSON_FILENAME = util.initParam('NODE_CONFIG_RUNTIME_JSON', Path.join(CONFIG_DIR , 'runtime.json') );
555
534
 
535
+ NODE_CONFIG_PARSER = util.initParam('NODE_CONFIG_PARSER');
536
+ if (NODE_CONFIG_PARSER) {
537
+ try {
538
+ var parserModule = Path.isAbsolute(NODE_CONFIG_PARSER)
539
+ ? NODE_CONFIG_PARSER
540
+ : Path.join(CONFIG_DIR, NODE_CONFIG_PARSER);
541
+ Parser = require(parserModule);
542
+ }
543
+ catch (e) {
544
+ console.warn('Failed to load config parser from ' + NODE_CONFIG_PARSER);
545
+ console.log(e);
546
+ }
547
+ }
548
+
556
549
  // Determine the host name from the OS module, $HOST, or $HOSTNAME
557
550
  // Remove any . appendages, and default to null if not set
558
551
  try {
@@ -573,7 +566,7 @@ util.loadFileConfigs = function(configDir) {
573
566
  var baseNames = ['default'].concat(NODE_ENV);
574
567
 
575
568
  // #236: Also add full hostname when they are different.
576
- if ( hostName ) {
569
+ if (hostName) {
577
570
  var firstDomain = hostName.split('.')[0];
578
571
 
579
572
  NODE_ENV.forEach(function(env) {
@@ -581,7 +574,7 @@ util.loadFileConfigs = function(configDir) {
581
574
  baseNames.push(firstDomain, firstDomain + '-' + env);
582
575
 
583
576
  // Add full hostname when it is not the same
584
- if ( hostName != firstDomain ) {
577
+ if (hostName !== firstDomain) {
585
578
  baseNames.push(hostName, hostName + '-' + env);
586
579
  }
587
580
  });
@@ -591,28 +584,26 @@ util.loadFileConfigs = function(configDir) {
591
584
  baseNames.push('local', 'local-' + env);
592
585
  });
593
586
 
594
- var extNames = ['js', 'ts', 'json', 'json5', 'hjson', 'toml', 'coffee', 'iced', 'yaml', 'yml', 'cson', 'properties', 'xml'];
587
+ var allowedFiles = {};
588
+ var resolutionIndex = 1;
589
+ var extNames = Parser.getFilesOrder();
595
590
  baseNames.forEach(function(baseName) {
596
591
  extNames.forEach(function(extName) {
597
-
598
- // Try merging the config object into this object
599
- var fullFilename = Path.join(CONFIG_DIR , baseName + '.' + extName);
600
- var configObj = util.parseFile(fullFilename);
601
- if (configObj) {
602
- util.extendDeep(config, configObj);
603
- }
604
-
605
- // See if the application instance file is available
592
+ allowedFiles[baseName + '.' + extName] = resolutionIndex++;
606
593
  if (APP_INSTANCE) {
607
- fullFilename = Path.join(CONFIG_DIR, baseName + '-' + APP_INSTANCE + '.' + extName);
608
- configObj = util.parseFile(fullFilename);
609
- if (configObj) {
610
- util.extendDeep(config, configObj);
611
- }
594
+ allowedFiles[baseName + '-' + APP_INSTANCE + '.' + extName] = resolutionIndex++;
612
595
  }
613
596
  });
614
597
  });
615
598
 
599
+ var locatedFiles = util.locateMatchingFiles(CONFIG_DIR, allowedFiles);
600
+ locatedFiles.forEach(function(fullFilename) {
601
+ var configObj = util.parseFile(fullFilename);
602
+ if (configObj) {
603
+ util.extendDeep(config, configObj);
604
+ }
605
+ });
606
+
616
607
  // Override configurations from the $NODE_CONFIG environment variable
617
608
  // NODE_CONFIG only applies to the base config
618
609
  if (!configDir) {
@@ -663,10 +654,39 @@ util.loadFileConfigs = function(configDir) {
663
654
  return config;
664
655
  };
665
656
 
657
+ /**
658
+ * Return a list of fullFilenames who exists in allowedFiles
659
+ * Ordered according to allowedFiles argument specifications
660
+ *
661
+ * @protected
662
+ * @method locateMatchingFiles
663
+ * @param configDirs {string} the config dir, or multiple dirs separated by a column (:)
664
+ * @param allowedFiles {object} an object. keys and supported filenames
665
+ * and values are the position in the resolution order
666
+ * @returns {string[]} fullFilenames - path + filename
667
+ */
668
+ util.locateMatchingFiles = function(configDirs, allowedFiles) {
669
+ return configDirs.split(Path.delimiter)
670
+ .reduce(function(files, configDir) {
671
+ if (configDir) {
672
+ try {
673
+ FileSystem.readdirSync(configDir).forEach(function(file) {
674
+ if (allowedFiles[file]) {
675
+ files.push([allowedFiles[file], Path.join(configDir, file)]);
676
+ }
677
+ });
678
+ }
679
+ catch(e) {}
680
+ return files;
681
+ }
682
+ }, [])
683
+ .sort(function(a, b) { return a[0] - b[0]; })
684
+ .map(function(file) { return file[1]; });
685
+ };
686
+
666
687
  // Using basic recursion pattern, find all the deferred values and resolve them.
667
688
  util.resolveDeferredConfigs = function (config) {
668
- var completeConfig = config;
669
-
689
+ var deferred = [];
670
690
 
671
691
  function _iterate (prop) {
672
692
 
@@ -682,25 +702,30 @@ util.resolveDeferredConfigs = function (config) {
682
702
 
683
703
  // Second step is to iterate of the elements in a predictable (sorted) order
684
704
  propsToSort.sort().forEach(function (property) {
685
- if (prop[property].constructor == Object) {
705
+ if (prop[property].constructor === Object) {
686
706
  _iterate(prop[property]);
687
- } else if (prop[property].constructor == Array) {
707
+ } else if (prop[property].constructor === Array) {
688
708
  for (var i = 0; i < prop[property].length; i++) {
689
- _iterate(prop[property][i]);
709
+ if (prop[property][i] instanceof DeferredConfig) {
710
+ deferred.push(prop[property][i].prepare(config, prop[property], i));
711
+ }
712
+ else {
713
+ _iterate(prop[property][i]);
714
+ }
690
715
  }
691
716
  } else {
692
- if (prop[property] instanceof DeferredConfig ) {
693
- prop[property]= prop[property].resolve.call(completeConfig,completeConfig, prop[property]._original);
694
- }
695
- else {
696
- // Nothing to do. Keep the property how it is.
717
+ if (prop[property] instanceof DeferredConfig) {
718
+ deferred.push(prop[property].prepare(config, prop, property));
697
719
  }
720
+ // else: Nothing to do. Keep the property how it is.
698
721
  }
699
722
  });
700
723
  }
701
724
 
702
- _iterate(config);
703
- }
725
+ _iterate(config);
726
+
727
+ deferred.forEach(function (defer) { defer.resolve(); });
728
+ };
704
729
 
705
730
  /**
706
731
  * Parse and return the specified configuration file.
@@ -725,36 +750,27 @@ util.resolveDeferredConfigs = function (config) {
725
750
  * @protected
726
751
  * @method parseFile
727
752
  * @param fullFilename {string} The full file path and name
728
- * @return {configObject} The configuration object parsed from the file
753
+ * @return configObject {object|null} The configuration object parsed from the file
729
754
  */
730
755
  util.parseFile = function(fullFilename) {
731
-
732
- // Initialize
733
- var t = this,
734
- extension = fullFilename.substr(fullFilename.lastIndexOf('.') + 1),
756
+ var t = this, // Initialize
735
757
  configObject = null,
736
758
  fileContent = null,
737
759
  stat = null;
738
760
 
739
- // Return null if the file doesn't exist.
740
761
  // Note that all methods here are the Sync versions. This is appropriate during
741
762
  // module loading (which is a synchronous operation), but not thereafter.
742
- try {
743
- stat = FileSystem.statSync(fullFilename);
744
- if (!stat || stat.size < 1) {
745
- return null;
746
- }
747
- } catch (e1) {
748
- return null
749
- }
750
763
 
751
- // Try loading the file.
752
764
  try {
753
- fileContent = FileSystem.readFileSync(fullFilename, 'UTF-8');
765
+ // Try loading the file.
766
+ fileContent = FileSystem.readFileSync(fullFilename, 'utf-8');
754
767
  fileContent = fileContent.replace(/^\uFEFF/, '');
755
768
  }
756
769
  catch (e2) {
757
- throw new Error('Config file ' + fullFilename + ' cannot be read');
770
+ if (e2.code !== 'ENOENT') {
771
+ throw new Error('Config file ' + fullFilename + ' cannot be read');
772
+ }
773
+ return null; // file doesn't exists
758
774
  }
759
775
 
760
776
  // Parse the file based on extension
@@ -768,68 +784,7 @@ util.parseFile = function(fullFilename) {
768
784
  }
769
785
  }
770
786
 
771
- if (extension === 'js') {
772
- // Use the built-in parser for .js files
773
- configObject = require(fullFilename);
774
- }
775
- else if (extension === 'ts') {
776
- if (!require.extensions['.ts']) {
777
- require(TS_DEP).register({
778
- lazy: true,
779
- compilerOptions: {
780
- allowJs: true,
781
- }
782
- });
783
- }
784
-
785
- // Because of ES6 modules usage, `default` is treated as named export (like any other)
786
- // Therefore config is a value of `default` key.
787
- configObject = require(fullFilename).default;
788
- }
789
- else if (extension === 'coffee') {
790
- // .coffee files can be loaded with either coffee-script or iced-coffee-script.
791
- // Prefer iced-coffee-script, if it exists.
792
- // Lazy load the appropriate extension
793
- if (!Coffee) {
794
- Coffee = {};
795
-
796
- // The following enables iced-coffee-script on .coffee files, if iced-coffee-script is available.
797
- // This is commented as per a decision on a pull request.
798
- //try {
799
- // Coffee = require("iced-coffee-script");
800
- //}
801
- //catch (e) {
802
- // Coffee = require("coffee-script");
803
- //}
804
-
805
- try {
806
- // Try to load coffeescript
807
- Coffee = require(COFFEE_2_DEP);
808
- }
809
- catch (e) {
810
- // If it doesn't exist, try to load it using the deprecated module name
811
- Coffee = require(COFFEE_DEP);
812
- }
813
-
814
- // coffee-script >= 1.7.0 requires explicit registration for require() to work
815
- if (Coffee.register) {
816
- Coffee.register();
817
- }
818
- }
819
- // Use the built-in parser for .coffee files with coffee-script
820
- configObject = require(fullFilename);
821
- }
822
- else if (extension === 'iced') {
823
- Iced = require(ICED_DEP);
824
-
825
- // coffee-script >= 1.7.0 requires explicit registration for require() to work
826
- if (Iced.register) {
827
- Iced.register();
828
- }
829
- }
830
- else {
831
- configObject = util.parseString(fileContent, extension);
832
- }
787
+ configObject = Parser.parse(fullFilename, fileContent);
833
788
  }
834
789
  catch (e3) {
835
790
  if (gitCryptTestRegex.test(fileContent)) {
@@ -877,112 +832,10 @@ util.parseFile = function(fullFilename) {
877
832
  * @return {configObject} The configuration object parsed from the string
878
833
  */
879
834
  util.parseString = function (content, format) {
880
- // Initialize
881
- var configObject = null;
882
-
883
- // Parse the file based on extension
884
- if (format === 'yaml' || format === 'yml') {
885
- if (!Yaml && !VisionmediaYaml) {
886
- // Lazy loading
887
- try {
888
- // Try to load the better js-yaml module
889
- Yaml = require(JS_YAML_DEP);
890
- }
891
- catch (e) {
892
- try {
893
- // If it doesn't exist, load the fallback visionmedia yaml module.
894
- VisionmediaYaml = require(YAML_DEP);
895
- }
896
- catch (e) { }
897
- }
898
- }
899
-
900
- if (Yaml) {
901
- configObject = Yaml.load(content);
902
- }
903
- else if (VisionmediaYaml) {
904
- // The yaml library doesn't like strings that have newlines but don't
905
- // end in a newline: https://github.com/visionmedia/js-yaml/issues/issue/13
906
- content += '\n';
907
- configObject = VisionmediaYaml.eval(util.stripYamlComments(content));
908
- }
909
- else {
910
- console.error("No YAML parser loaded. Suggest adding js-yaml dependency to your package.json file.")
911
- }
912
- }
913
- else if (format === 'json') {
914
- try {
915
- configObject = JSON.parse(content);
916
- }
917
- catch (e) {
918
- // All JS Style comments will begin with /, so all JSON parse errors that
919
- // encountered a syntax error will complain about this character.
920
- if (e.name !== 'SyntaxError' || e.message.indexOf('Unexpected token /') !== 0) {
921
- throw e;
922
- }
923
-
924
- if (!JSON5) {
925
- JSON5 = require(JSON5_DEP);
926
- }
927
-
928
- configObject = JSON5.parse(content);
929
- }
930
- }
931
- else if (format === 'json5') {
932
-
933
- if (!JSON5) {
934
- JSON5 = require(JSON5_DEP);
935
- }
936
-
937
- configObject = JSON5.parse(content);
938
-
939
- } else if (format === 'hjson') {
940
-
941
- if (!HJSON) {
942
- HJSON = require(HJSON_DEP);
943
- }
944
-
945
- configObject = HJSON.parse(content);
946
-
947
- } else if (format === 'toml') {
948
-
949
- if(!TOML) {
950
- TOML = require(TOML_DEP);
951
- }
952
-
953
- configObject = TOML.parse(content);
954
- }
955
- else if (format === 'cson') {
956
- if (!CSON) {
957
- CSON = require(CSON_DEP);
958
- }
959
- // Allow comments in CSON files
960
- if (typeof CSON.parseSync === 'function') {
961
- configObject = CSON.parseSync(util.stripComments(content));
962
- } else {
963
- configObject = CSON.parse(util.stripComments(content));
964
- }
835
+ var parser = Parser.getParser(format);
836
+ if (typeof parser === 'function') {
837
+ return parser(null, content);
965
838
  }
966
- else if (format === 'properties') {
967
- if (!PPARSER) {
968
- PPARSER = require(PPARSER_DEP);
969
- }
970
- configObject = PPARSER.parse(content, { namespaces: true, variables: true, sections: true });
971
- } else if (format === 'xml') {
972
-
973
- if (!XML) {
974
- XML = require(XML_DEP);
975
- }
976
-
977
- var x2js = new XML();
978
- configObject = x2js.xml2js(content);
979
- var rootKeys = Object.keys(configObject);
980
- if(rootKeys.length == 1) {
981
- configObject = configObject[rootKeys[0]];
982
- }
983
- }
984
-
985
- return configObject;
986
839
  };
987
840
 
988
841
  /**
@@ -1119,6 +972,8 @@ util.cloneDeep = function cloneDeep(parent, depth, circular, prototype) {
1119
972
 
1120
973
  if (hasGetter){
1121
974
  Object.defineProperty(child,i,propDescriptor);
975
+ } else if (util.isPromise(parent[i])) {
976
+ child[i] = parent[i];
1122
977
  } else {
1123
978
  child[i] = _clone(parent[i], depth - 1);
1124
979
  }
@@ -1384,7 +1239,9 @@ util.extendDeep = function(mergeInto) {
1384
1239
  } else if (util.isObject(mergeInto[prop]) && util.isObject(mergeFrom[prop]) && !isDeferredFunc) {
1385
1240
  util.extendDeep(mergeInto[prop], mergeFrom[prop], depth - 1);
1386
1241
  }
1387
-
1242
+ else if (util.isPromise(mergeFrom[prop])) {
1243
+ mergeInto[prop] = mergeFrom[prop];
1244
+ }
1388
1245
  // Copy recursively if the mergeFrom element is an object (or array or fn)
1389
1246
  else if (mergeFrom[prop] && typeof mergeFrom[prop] === 'object') {
1390
1247
  mergeInto[prop] = util.cloneDeep(mergeFrom[prop], depth -1);
@@ -1404,88 +1261,6 @@ util.extendDeep = function(mergeInto) {
1404
1261
 
1405
1262
  };
1406
1263
 
1407
- /**
1408
- * Strip YAML comments from the string
1409
- *
1410
- * The 2.0 yaml parser doesn't allow comment-only or blank lines. Strip them.
1411
- *
1412
- * @protected
1413
- * @method stripYamlComments
1414
- * @param fileString {string} The string to strip comments from
1415
- * @return {string} The string with comments stripped.
1416
- */
1417
- util.stripYamlComments = function(fileStr) {
1418
- // First replace removes comment-only lines
1419
- // Second replace removes blank lines
1420
- return fileStr.replace(/^\s*#.*/mg,'').replace(/^\s*[\n|\r]+/mg,'');
1421
- }
1422
-
1423
- /**
1424
- * Strip all Javascript type comments from the string.
1425
- *
1426
- * The string is usually a file loaded from the O/S, containing
1427
- * newlines and javascript type comments.
1428
- *
1429
- * Thanks to James Padolsey, and all who contributed to this implementation.
1430
- * http://james.padolsey.com/javascript/javascript-comment-removal-revisted/
1431
- *
1432
- * @protected
1433
- * @method stripComments
1434
- * @param fileString {string} The string to strip comments from
1435
- * @param stringRegex {RegExp} Optional regular expression to match strings that
1436
- * make up the config file
1437
- * @return {string} The string with comments stripped.
1438
- */
1439
- util.stripComments = function(fileStr, stringRegex) {
1440
- stringRegex = stringRegex || /(['"])(\\\1|.)+?\1/g;
1441
-
1442
- var uid = '_' + +new Date(),
1443
- primitives = [],
1444
- primIndex = 0;
1445
-
1446
- return (
1447
- fileStr
1448
-
1449
- /* Remove strings */
1450
- .replace(stringRegex, function(match){
1451
- primitives[primIndex] = match;
1452
- return (uid + '') + primIndex++;
1453
- })
1454
-
1455
- /* Remove Regexes */
1456
- .replace(/([^\/])(\/(?!\*|\/)(\\\/|.)+?\/[gim]{0,3})/g, function(match, $1, $2){
1457
- primitives[primIndex] = $2;
1458
- return $1 + (uid + '') + primIndex++;
1459
- })
1460
-
1461
- /*
1462
- - Remove single-line comments that contain would-be multi-line delimiters
1463
- E.g. // Comment /* <--
1464
- - Remove multi-line comments that contain would be single-line delimiters
1465
- E.g. /* // <--
1466
- */
1467
- .replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '')
1468
-
1469
- /*
1470
- Remove single and multi-line comments,
1471
- no consideration of inner-contents
1472
- */
1473
- .replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '')
1474
-
1475
- /*
1476
- Remove multi-line comments that have a replaced ending (string/regex)
1477
- Greedy, so no inner strings/regexes will stop it.
1478
- */
1479
- .replace(RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '')
1480
-
1481
- /* Bring back strings & regexes */
1482
- .replace(RegExp(uid + '(\\d+)', 'g'), function(match, n){
1483
- return primitives[n];
1484
- })
1485
- );
1486
-
1487
- };
1488
-
1489
1264
  /**
1490
1265
  * Is the specified argument a regular javascript object?
1491
1266
  *
@@ -1493,13 +1268,25 @@ util.stripComments = function(fileStr, stringRegex) {
1493
1268
  *
1494
1269
  * @protected
1495
1270
  * @method isObject
1496
- * @param arg {*} An argument of any type.
1271
+ * @param obj {*} An argument of any type.
1497
1272
  * @return {boolean} TRUE if the arg is an object, FALSE if not
1498
1273
  */
1499
1274
  util.isObject = function(obj) {
1500
1275
  return (obj !== null) && (typeof obj === 'object') && !(Array.isArray(obj));
1501
1276
  };
1502
1277
 
1278
+ /**
1279
+ * Is the specified argument a javascript promise?
1280
+ *
1281
+ * @protected
1282
+ * @method isPromise
1283
+ * @param obj {*} An argument of any type.
1284
+ * @returns {boolean}
1285
+ */
1286
+ util.isPromise = function(obj) {
1287
+ return Object.prototype.toString.call(obj) === '[object Promise]';
1288
+ };
1289
+
1503
1290
  /**
1504
1291
  * <p>Initialize a parameter from the command line or process environment</p>
1505
1292
  *
@@ -1637,11 +1424,15 @@ util.runStrictnessChecks = function (config) {
1637
1424
  throw new Error(prefix+msg+' '+seeURL);
1638
1425
  }
1639
1426
  }
1640
- }
1427
+ };
1641
1428
 
1642
1429
  // Instantiate and export the configuration
1643
1430
  var config = module.exports = new Config();
1644
1431
 
1432
+ // copy methods to util for backwards compatibility
1433
+ util.stripComments = Parser.stripComments;
1434
+ util.stripYamlComments = Parser.stripYamlComments;
1435
+
1645
1436
  // Produce warnings if the configuration is empty
1646
1437
  var showWarnings = !(util.initParam('SUPPRESS_NO_CONFIG_WARNING'));
1647
1438
  if (showWarnings && Object.keys(config).length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "config",
3
- "version": "3.0.1",
3
+ "version": "3.2.2",
4
4
  "main": "./lib/config.js",
5
5
  "description": "Configuration control for production node deployments",
6
6
  "author": "Loren West <open_source@lorenwest.com>",
package/parser.js ADDED
@@ -0,0 +1,331 @@
1
+ // External libraries are lazy-loaded only if these file types exist.
2
+ var Yaml = null,
3
+ VisionmediaYaml = null,
4
+ Coffee = null,
5
+ Iced = null,
6
+ CSON = null,
7
+ PPARSER = null,
8
+ JSON5 = null,
9
+ TOML = null,
10
+ HJSON = null,
11
+ XML = null;
12
+
13
+ // Define soft dependencies so transpilers don't include everything
14
+ var COFFEE_2_DEP = 'coffeescript',
15
+ COFFEE_DEP = 'coffee-script',
16
+ ICED_DEP = 'iced-coffee-script',
17
+ JS_YAML_DEP = 'js-yaml',
18
+ YAML_DEP = 'yaml',
19
+ JSON5_DEP = 'json5',
20
+ HJSON_DEP = 'hjson',
21
+ TOML_DEP = 'toml',
22
+ CSON_DEP = 'cson',
23
+ PPARSER_DEP = 'properties',
24
+ XML_DEP = 'x2js',
25
+ TS_DEP = 'ts-node';
26
+
27
+ var Parser = module.exports;
28
+
29
+ Parser.parse = function(filename, content) {
30
+ var parserName = filename.substr(filename.lastIndexOf('.') +1); // file extension
31
+ if (typeof definitions[parserName] === 'function') {
32
+ return definitions[parserName](filename, content);
33
+ }
34
+ // TODO: decide what to do in case of a missing parser
35
+ };
36
+
37
+ Parser.xmlParser = function(filename, content) {
38
+ if (!XML) {
39
+ XML = require(XML_DEP);
40
+ }
41
+ var x2js = new XML();
42
+ var configObject = x2js.xml2js(content);
43
+ var rootKeys = Object.keys(configObject);
44
+ if(rootKeys.length === 1) {
45
+ return configObject[rootKeys[0]];
46
+ }
47
+ return configObject;
48
+ };
49
+
50
+ Parser.jsParser = function(filename, content) {
51
+ return require(filename);
52
+ };
53
+
54
+ Parser.tsParser = function(filename, content) {
55
+ if (!require.extensions['.ts']) {
56
+ require(TS_DEP).register({
57
+ lazy: true,
58
+ compilerOptions: {
59
+ allowJs: true,
60
+ }
61
+ });
62
+ }
63
+
64
+ // Imports config if it is exported via module.exports = ...
65
+ // See https://github.com/lorenwest/node-config/issues/524
66
+ var configObject = require(filename);
67
+
68
+ // Because of ES6 modules usage, `default` is treated as named export (like any other)
69
+ // Therefore config is a value of `default` key.
70
+ if (configObject.default) {
71
+ return configObject.default
72
+ }
73
+ return configObject;
74
+ };
75
+
76
+ Parser.coffeeParser = function(filename, content) {
77
+ // .coffee files can be loaded with either coffee-script or iced-coffee-script.
78
+ // Prefer iced-coffee-script, if it exists.
79
+ // Lazy load the appropriate extension
80
+ if (!Coffee) {
81
+ Coffee = {};
82
+
83
+ // The following enables iced-coffee-script on .coffee files, if iced-coffee-script is available.
84
+ // This is commented as per a decision on a pull request.
85
+ //try {
86
+ // Coffee = require('iced-coffee-script');
87
+ //}
88
+ //catch (e) {
89
+ // Coffee = require('coffee-script');
90
+ //}
91
+ try {
92
+ // Try to load coffeescript
93
+ Coffee = require(COFFEE_2_DEP);
94
+ }
95
+ catch (e) {
96
+ // If it doesn't exist, try to load it using the deprecated module name
97
+ Coffee = require(COFFEE_DEP);
98
+ }
99
+ // coffee-script >= 1.7.0 requires explicit registration for require() to work
100
+ if (Coffee.register) {
101
+ Coffee.register();
102
+ }
103
+ }
104
+ // Use the built-in parser for .coffee files with coffee-script
105
+ return require(filename);
106
+ };
107
+
108
+ Parser.icedParser = function(filename, content) {
109
+ Iced = require(ICED_DEP);
110
+
111
+ // coffee-script >= 1.7.0 requires explicit registration for require() to work
112
+ if (Iced.register) {
113
+ Iced.register();
114
+ }
115
+ };
116
+
117
+ Parser.yamlParser = function(filename, content) {
118
+ if (!Yaml && !VisionmediaYaml) {
119
+ // Lazy loading
120
+ try {
121
+ // Try to load the better js-yaml module
122
+ Yaml = require(JS_YAML_DEP);
123
+ }
124
+ catch (e) {
125
+ try {
126
+ // If it doesn't exist, load the fallback visionmedia yaml module.
127
+ VisionmediaYaml = require(YAML_DEP);
128
+ }
129
+ catch (e) { }
130
+ }
131
+ }
132
+ if (Yaml) {
133
+ return Yaml.load(content);
134
+ }
135
+ else if (VisionmediaYaml) {
136
+ // The yaml library doesn't like strings that have newlines but don't
137
+ // end in a newline: https://github.com/visionmedia/js-yaml/issues/issue/13
138
+ content += '\n';
139
+ return VisionmediaYaml.eval(Parser.stripYamlComments(content));
140
+ }
141
+ else {
142
+ console.error('No YAML parser loaded. Suggest adding js-yaml dependency to your package.json file.')
143
+ }
144
+ };
145
+
146
+ Parser.jsonParser = function(filename, content) {
147
+ try {
148
+ return JSON.parse(content);
149
+ }
150
+ catch (e) {
151
+ // All JS Style comments will begin with /, so all JSON parse errors that
152
+ // encountered a syntax error will complain about this character.
153
+ if (e.name !== 'SyntaxError' || e.message.indexOf('Unexpected token /') !== 0) {
154
+ throw e;
155
+ }
156
+ if (!JSON5) {
157
+ JSON5 = require(JSON5_DEP);
158
+ }
159
+ return JSON5.parse(content);
160
+ }
161
+ };
162
+
163
+ Parser.json5Parser = function(filename, content) {
164
+ if (!JSON5) {
165
+ JSON5 = require(JSON5_DEP);
166
+ }
167
+ return JSON5.parse(content);
168
+ };
169
+
170
+ Parser.hjsonParser = function(filename, content) {
171
+ if (!HJSON) {
172
+ HJSON = require(HJSON_DEP);
173
+ }
174
+ return HJSON.parse(content);
175
+ };
176
+
177
+ Parser.tomlParser = function(filename, content) {
178
+ if(!TOML) {
179
+ TOML = require(TOML_DEP);
180
+ }
181
+ return TOML.parse(content);
182
+ };
183
+
184
+ Parser.csonParser = function(filename, content) {
185
+ if (!CSON) {
186
+ CSON = require(CSON_DEP);
187
+ }
188
+ // Allow comments in CSON files
189
+ if (typeof CSON.parseSync === 'function') {
190
+ return CSON.parseSync(Parser.stripComments(content));
191
+ }
192
+ return CSON.parse(Parser.stripComments(content));
193
+ };
194
+
195
+ Parser.propertiesParser = function(filename, content) {
196
+ if (!PPARSER) {
197
+ PPARSER = require(PPARSER_DEP);
198
+ }
199
+ return PPARSER.parse(content, { namespaces: true, variables: true, sections: true });
200
+ };
201
+
202
+ /**
203
+ * Strip all Javascript type comments from the string.
204
+ *
205
+ * The string is usually a file loaded from the O/S, containing
206
+ * newlines and javascript type comments.
207
+ *
208
+ * Thanks to James Padolsey, and all who contributed to this implementation.
209
+ * http://james.padolsey.com/javascript/javascript-comment-removal-revisted/
210
+ *
211
+ * @protected
212
+ * @method stripComments
213
+ * @param fileStr {string} The string to strip comments from
214
+ * @param stringRegex {RegExp} Optional regular expression to match strings that
215
+ * make up the config file
216
+ * @return {string} The string with comments stripped.
217
+ */
218
+ Parser.stripComments = function(fileStr, stringRegex) {
219
+ stringRegex = stringRegex || /(['"])(\\\1|.)+?\1/g;
220
+
221
+ var uid = '_' + +new Date(),
222
+ primitives = [],
223
+ primIndex = 0;
224
+
225
+ return (
226
+ fileStr
227
+
228
+ /* Remove strings */
229
+ .replace(stringRegex, function(match){
230
+ primitives[primIndex] = match;
231
+ return (uid + '') + primIndex++;
232
+ })
233
+
234
+ /* Remove Regexes */
235
+ .replace(/([^\/])(\/(?!\*|\/)(\\\/|.)+?\/[gim]{0,3})/g, function(match, $1, $2){
236
+ primitives[primIndex] = $2;
237
+ return $1 + (uid + '') + primIndex++;
238
+ })
239
+
240
+ /*
241
+ - Remove single-line comments that contain would-be multi-line delimiters
242
+ E.g. // Comment /* <--
243
+ - Remove multi-line comments that contain would be single-line delimiters
244
+ E.g. /* // <--
245
+ */
246
+ .replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '')
247
+
248
+ /*
249
+ Remove single and multi-line comments,
250
+ no consideration of inner-contents
251
+ */
252
+ .replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '')
253
+
254
+ /*
255
+ Remove multi-line comments that have a replaced ending (string/regex)
256
+ Greedy, so no inner strings/regexes will stop it.
257
+ */
258
+ .replace(RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '')
259
+
260
+ /* Bring back strings & regexes */
261
+ .replace(RegExp(uid + '(\\d+)', 'g'), function(match, n){
262
+ return primitives[n];
263
+ })
264
+ );
265
+
266
+ };
267
+
268
+ /**
269
+ * Strip YAML comments from the string
270
+ *
271
+ * The 2.0 yaml parser doesn't allow comment-only or blank lines. Strip them.
272
+ *
273
+ * @protected
274
+ * @method stripYamlComments
275
+ * @param fileStr {string} The string to strip comments from
276
+ * @return {string} The string with comments stripped.
277
+ */
278
+ Parser.stripYamlComments = function(fileStr) {
279
+ // First replace removes comment-only lines
280
+ // Second replace removes blank lines
281
+ return fileStr.replace(/^\s*#.*/mg,'').replace(/^\s*[\n|\r]+/mg,'');
282
+ };
283
+
284
+ var order = ['js', 'ts', 'json', 'json5', 'hjson', 'toml', 'coffee', 'iced', 'yaml', 'yml', 'cson', 'properties', 'xml'];
285
+ var definitions = {
286
+ coffee: Parser.coffeeParser,
287
+ cson: Parser.csonParser,
288
+ hjson: Parser.hjsonParser,
289
+ iced: Parser.icedParser,
290
+ js: Parser.jsParser,
291
+ json: Parser.jsonParser,
292
+ json5: Parser.json5Parser,
293
+ properties: Parser.propertiesParser,
294
+ toml: Parser.tomlParser,
295
+ ts: Parser.tsParser,
296
+ xml: Parser.xmlParser,
297
+ yaml: Parser.yamlParser,
298
+ yml: Parser.yamlParser,
299
+ };
300
+
301
+ Parser.getParser = function(name) {
302
+ return definitions[name];
303
+ };
304
+
305
+ Parser.setParser = function(name, parser) {
306
+ definitions[name] = parser;
307
+ if (order.indexOf(name) === -1) {
308
+ order.push(name);
309
+ }
310
+ };
311
+
312
+ Parser.getFilesOrder = function(name) {
313
+ if (name) {
314
+ return order.indexOf(name);
315
+ }
316
+ return order;
317
+ };
318
+
319
+ Parser.setFilesOrder = function(name, newIndex) {
320
+ if (Array.isArray(name)) {
321
+ return order = name;
322
+ }
323
+ if (typeof newIndex === 'number') {
324
+ var index = order.indexOf(name);
325
+ order.splice(newIndex, 0, name);
326
+ if (index > -1) {
327
+ order.splice(index >= newIndex ? index +1 : index, 1);
328
+ }
329
+ }
330
+ return order;
331
+ };