UNPKG

45.8 kBJavaScriptView Raw
1// config.js (c) 2010-2020 Loren West and other contributors
2// May be freely distributed under the MIT license.
3// For further details and documentation:
4// http://lorenwest.github.com/node-config
5
6// Dependencies
7var deferConfig = require('../defer').deferConfig,
8 DeferredConfig = require('../defer').DeferredConfig,
9 RawConfig = require('../raw').RawConfig,
10 Parser = require('../parser'),
11 Utils = require('util'),
12 Path = require('path'),
13 FileSystem = require('fs');
14
15// Static members
16var DEFAULT_CLONE_DEPTH = 20,
17 NODE_CONFIG, CONFIG_DIR, RUNTIME_JSON_FILENAME, NODE_ENV, APP_INSTANCE,
18 HOST, HOSTNAME, ALLOW_CONFIG_MUTATIONS, CONFIG_SKIP_GITCRYPT,
19 NODE_CONFIG_PARSER,
20 env = {},
21 privateUtil = {},
22 deprecationWarnings = {},
23 configSources = [], // Configuration sources - array of {name, original, parsed}
24 checkMutability = true, // Check for mutability/immutability on first get
25 gitCryptTestRegex = /^.GITCRYPT/; // regular expression to test for gitcrypt files.
26
27/**
28 * <p>Application Configurations</p>
29 *
30 * <p>
31 * The config module exports a singleton object representing all
32 * configurations for this application deployment.
33 * </p>
34 *
35 * <p>
36 * Application configurations are stored in files within the config directory
37 * of your application. The default configuration file is loaded, followed
38 * by files specific to the deployment type (development, testing, staging,
39 * production, etc.).
40 * </p>
41 *
42 * <p>
43 * For example, with the following config/default.yaml file:
44 * </p>
45 *
46 * <pre>
47 * ...
48 * customer:
49 * &nbsp;&nbsp;initialCredit: 500
50 * &nbsp;&nbsp;db:
51 * &nbsp;&nbsp;&nbsp;&nbsp;name: customer
52 * &nbsp;&nbsp;&nbsp;&nbsp;port: 5984
53 * ...
54 * </pre>
55 *
56 * <p>
57 * The following code loads the customer section into the CONFIG variable:
58 * <p>
59 *
60 * <pre>
61 * var CONFIG = require('config').customer;
62 * ...
63 * newCustomer.creditLimit = CONFIG.initialCredit;
64 * database.open(CONFIG.db.name, CONFIG.db.port);
65 * ...
66 * </pre>
67 *
68 * @module config
69 * @class Config
70 */
71
72/**
73 * <p>Get the configuration object.</p>
74 *
75 * <p>
76 * The configuration object is a shared singleton object within the application,
77 * attained by calling require('config').
78 * </p>
79 *
80 * <p>
81 * Usually you'll specify a CONFIG variable at the top of your .js file
82 * for file/module scope. If you want the root of the object, you can do this:
83 * </p>
84 * <pre>
85 * var CONFIG = require('config');
86 * </pre>
87 *
88 * <p>
89 * Sometimes you only care about a specific sub-object within the CONFIG
90 * object. In that case you could do this at the top of your file:
91 * </p>
92 * <pre>
93 * var CONFIG = require('config').customer;
94 * or
95 * var CUSTOMER_CONFIG = require('config').customer;
96 * </pre>
97 *
98 * <script type="text/javascript">
99 * document.getElementById("showProtected").style.display = "block";
100 * </script>
101 *
102 * @method constructor
103 * @return CONFIG {object} - The top level configuration object
104 */
105var Config = function() {
106 var t = this;
107
108 // Bind all utility functions to this
109 for (var fnName in util) {
110 if (typeof util[fnName] === 'function') {
111 util[fnName] = util[fnName].bind(t);
112 }
113 }
114
115 // Merge configurations into this
116 util.extendDeep(t, util.loadFileConfigs());
117 util.attachProtoDeep(t);
118
119 // Perform strictness checks and possibly throw an exception.
120 util.runStrictnessChecks(t);
121};
122
123/**
124 * Utilities are under the util namespace vs. at the top level
125 */
126var util = Config.prototype.util = {};
127
128/**
129 * Underlying get mechanism
130 *
131 * @private
132 * @method getImpl
133 * @param object {object} - Object to get the property for
134 * @param property {string|string[]} - The property name to get (as an array or '.' delimited string)
135 * @return value {*} - Property value, including undefined if not defined.
136 */
137var getImpl= function(object, property) {
138 var t = this,
139 elems = Array.isArray(property) ? property : property.split('.'),
140 name = elems[0],
141 value = object[name];
142 if (elems.length <= 1) {
143 return value;
144 }
145 // Note that typeof null === 'object'
146 if (value === null || typeof value !== 'object') {
147 return undefined;
148 }
149 return getImpl(value, elems.slice(1));
150};
151
152/**
153 * <p>Get a configuration value</p>
154 *
155 * <p>
156 * This will return the specified property value, throwing an exception if the
157 * configuration isn't defined. It is used to assure configurations are defined
158 * before being used, and to prevent typos.
159 * </p>
160 *
161 * @method get
162 * @param property {string} - The configuration property to get. Can include '.' sub-properties.
163 * @return value {*} - The property value
164 */
165Config.prototype.get = function(property) {
166 if(property === null || property === undefined){
167 throw new Error("Calling config.get with null or undefined argument");
168 }
169
170 // Make configurations immutable after first get (unless disabled)
171 if (checkMutability) {
172 if (!util.initParam('ALLOW_CONFIG_MUTATIONS', false)) {
173 util.makeImmutable(config);
174 }
175 checkMutability = false;
176 }
177 var t = this,
178 value = getImpl(t, property);
179
180 // Produce an exception if the property doesn't exist
181 if (value === undefined) {
182 throw new Error('Configuration property "' + property + '" is not defined');
183 }
184
185 // Return the value
186 return value;
187};
188
189/**
190 * Test that a configuration parameter exists
191 *
192 * <pre>
193 * var config = require('config');
194 * if (config.has('customer.dbName')) {
195 * console.log('Customer database name: ' + config.customer.dbName);
196 * }
197 * </pre>
198 *
199 * @method has
200 * @param property {string} - The configuration property to test. Can include '.' sub-properties.
201 * @return isPresent {boolean} - True if the property is defined, false if not defined.
202 */
203Config.prototype.has = function(property) {
204 // While get() throws an exception for undefined input, has() is designed to test validity, so false is appropriate
205 if(property === null || property === undefined){
206 return false;
207 }
208 var t = this;
209 return (getImpl(t, property) !== undefined);
210};
211
212/**
213 * <p>
214 * Set default configurations for a node.js module.
215 * </p>
216 *
217 * <p>
218 * This allows module developers to attach their configurations onto the
219 * default configuration object so they can be configured by the consumers
220 * of the module.
221 * </p>
222 *
223 * <p>Using the function within your module:</p>
224 * <pre>
225 * var CONFIG = require("config");
226 * CONFIG.util.setModuleDefaults("MyModule", {
227 * &nbsp;&nbsp;templateName: "t-50",
228 * &nbsp;&nbsp;colorScheme: "green"
229 * });
230 * <br>
231 * // Template name may be overridden by application config files
232 * console.log("Template: " + CONFIG.MyModule.templateName);
233 * </pre>
234 *
235 * <p>
236 * The above example results in a "MyModule" element of the configuration
237 * object, containing an object with the specified default values.
238 * </p>
239 *
240 * @method setModuleDefaults
241 * @param moduleName {string} - Name of your module.
242 * @param defaultProperties {object} - The default module configuration.
243 * @return moduleConfig {object} - The module level configuration object.
244 */
245util.setModuleDefaults = function (moduleName, defaultProperties) {
246
247 // Copy the properties into a new object
248 var t = this,
249 moduleConfig = util.cloneDeep(defaultProperties);
250
251 // Set module defaults into the first sources element
252 if (configSources.length === 0 || configSources[0].name !== 'Module Defaults') {
253 configSources.splice(0, 0, {
254 name: 'Module Defaults',
255 parsed: {}
256 });
257 }
258 util.setPath(configSources[0].parsed, moduleName.split('.'), {});
259 util.extendDeep(getImpl(configSources[0].parsed, moduleName), defaultProperties);
260
261 // Create a top level config for this module if it doesn't exist
262 util.setPath(t, moduleName.split('.'), getImpl(t, moduleName) || {});
263
264 // Extend local configurations into the module config
265 util.extendDeep(moduleConfig, getImpl(t, moduleName));
266
267 // Merge the extended configs without replacing the original
268 util.extendDeep(getImpl(t, moduleName), moduleConfig);
269
270 // reset the mutability check for "config.get" method.
271 // we are not making t[moduleName] immutable immediately,
272 // since there might be more modifications before the first config.get
273 if (!util.initParam('ALLOW_CONFIG_MUTATIONS', false)) {
274 checkMutability = true;
275 }
276
277 // Attach handlers & watchers onto the module config object
278 return util.attachProtoDeep(getImpl(t, moduleName));
279};
280
281/**
282 * <p>Make a configuration property hidden so it doesn't appear when enumerating
283 * elements of the object.</p>
284 *
285 * <p>
286 * The property still exists and can be read from and written to, but it won't
287 * show up in for ... in loops, Object.keys(), or JSON.stringify() type methods.
288 * </p>
289 *
290 * <p>
291 * If the property already exists, it will be made hidden. Otherwise it will
292 * be created as a hidden property with the specified value.
293 * </p>
294 *
295 * <p><i>
296 * This method was built for hiding configuration values, but it can be applied
297 * to <u>any</u> javascript object.
298 * </i></p>
299 *
300 * <p>Example:</p>
301 * <pre>
302 * var CONFIG = require('config');
303 * ...
304 *
305 * // Hide the Amazon S3 credentials
306 * CONFIG.util.makeHidden(CONFIG.amazonS3, 'access_id');
307 * CONFIG.util.makeHidden(CONFIG.amazonS3, 'secret_key');
308 * </pre>
309 *
310 * @method makeHidden
311 * @param object {object} - The object to make a hidden property into.
312 * @param property {string} - The name of the property to make hidden.
313 * @param value {*} - (optional) Set the property value to this (otherwise leave alone)
314 * @return object {object} - The original object is returned - for chaining.
315 */
316util.makeHidden = function(object, property, value) {
317
318 // If the new value isn't specified, just mark the property as hidden
319 if (typeof value === 'undefined') {
320 Object.defineProperty(object, property, {
321 enumerable : false
322 });
323 }
324 // Otherwise set the value and mark it as hidden
325 else {
326 Object.defineProperty(object, property, {
327 value : value,
328 enumerable : false
329 });
330 }
331
332 return object;
333}
334
335/**
336 * <p>Make a javascript object property immutable (assuring it cannot be changed
337 * from the current value).</p>
338 * <p>
339 * If the specified property is an object, all attributes of that object are
340 * made immutable, including properties of contained objects, recursively.
341 * If a property name isn't supplied, all properties of the object are made
342 * immutable.
343 * </p>
344 * <p>
345 *
346 * </p>
347 * <p>
348 * New properties can be added to the object and those properties will not be
349 * immutable unless this method is called on those new properties.
350 * </p>
351 * <p>
352 * This operation cannot be undone.
353 * </p>
354 *
355 * <p>Example:</p>
356 * <pre>
357 * var config = require('config');
358 * var myObject = {hello:'world'};
359 * config.util.makeImmutable(myObject);
360 * </pre>
361 *
362 * @method makeImmutable
363 * @param object {object} - The object to specify immutable properties for
364 * @param [property] {string | [string]} - The name of the property (or array of names) to make immutable.
365 * If not provided, all owned properties of the object are made immutable.
366 * @param [value] {* | [*]} - Property value (or array of values) to set
367 * the property to before making immutable. Only used when setting a single
368 * property. Retained for backward compatibility.
369 * @return object {object} - The original object is returned - for chaining.
370 */
371util.makeImmutable = function(object, property, value) {
372 if (Buffer.isBuffer(object)) {
373 return object;
374 }
375 var properties = null;
376
377 // Backwards compatibility mode where property/value can be specified
378 if (typeof property === 'string') {
379 return Object.defineProperty(object, property, {
380 value : (typeof value === 'undefined') ? object[property] : value,
381 writable : false,
382 configurable: false
383 });
384 }
385
386 // Get the list of properties to work with
387 if (Array.isArray(property)) {
388 properties = property;
389 }
390 else {
391 properties = Object.keys(object);
392 }
393
394 // Process each property
395 for (var i = 0; i < properties.length; i++) {
396 var propertyName = properties[i],
397 value = object[propertyName];
398
399 if (value instanceof RawConfig) {
400 Object.defineProperty(object, propertyName, {
401 value: value.resolve(),
402 writable: false,
403 configurable: false
404 });
405 } else if (Array.isArray(value)) {
406 // Ensure object items of this array are also immutable.
407 value.forEach((item, index) => { if (util.isObject(item) || Array.isArray(item)) util.makeImmutable(item) })
408
409 Object.defineProperty(object, propertyName, {
410 value: Object.freeze(value)
411 });
412 } else {
413 Object.defineProperty(object, propertyName, {
414 value: value,
415 writable : false,
416 configurable: false
417 });
418
419 // Ensure new properties can not be added.
420 Object.preventExtensions(object)
421
422 // Call recursively if an object.
423 if (util.isObject(value)) {
424 util.makeImmutable(value);
425 }
426 }
427 }
428
429 return object;
430};
431
432/**
433 * Return the sources for the configurations
434 *
435 * <p>
436 * All sources for configurations are stored in an array of objects containing
437 * the source name (usually the filename), the original source (as a string),
438 * and the parsed source as an object.
439 * </p>
440 *
441 * @method getConfigSources
442 * @return configSources {Array[Object]} - An array of objects containing
443 * name, original, and parsed elements
444 */
445util.getConfigSources = function() {
446 var t = this;
447 return configSources.slice(0);
448};
449
450/**
451 * Load the individual file configurations.
452 *
453 * <p>
454 * This method builds a map of filename to the configuration object defined
455 * by the file. The search order is:
456 * </p>
457 *
458 * <pre>
459 * default.EXT
460 * (deployment).EXT
461 * (hostname).EXT
462 * (hostname)-(deployment).EXT
463 * local.EXT
464 * local-(deployment).EXT
465 * runtime.json
466 * </pre>
467 *
468 * <p>
469 * EXT can be yml, yaml, coffee, iced, json, cson or js signifying the file type.
470 * yaml (and yml) is in YAML format, coffee is a coffee-script, iced is iced-coffee-script,
471 * json is in JSON format, cson is in CSON format, properties is in .properties format
472 * (http://en.wikipedia.org/wiki/.properties), and js is a javascript executable file that is
473 * require()'d with module.exports being the config object.
474 * </p>
475 *
476 * <p>
477 * hostname is the $HOST environment variable (or --HOST command line parameter)
478 * if set, otherwise the $HOSTNAME environment variable (or --HOSTNAME command
479 * line parameter) if set, otherwise the hostname found from
480 * require('os').hostname().
481 * </p>
482 *
483 * <p>
484 * Once a hostname is found, everything from the first period ('.') onwards
485 * is removed. For example, abc.example.com becomes abc
486 * </p>
487 *
488 * <p>
489 * (deployment) is the deployment type, found in the $NODE_ENV environment
490 * variable (which can be overriden by using $NODE_CONFIG_ENV
491 * environment variable). Defaults to 'development'.
492 * </p>
493 *
494 * <p>
495 * The runtime.json file contains configuration changes made at runtime either
496 * manually, or by the application setting a configuration value.
497 * </p>
498 *
499 * <p>
500 * If the $NODE_APP_INSTANCE environment variable (or --NODE_APP_INSTANCE
501 * command line parameter) is set, then files with this appendage will be loaded.
502 * See the Multiple Application Instances section of the main documentaion page
503 * for more information.
504 * </p>
505 *
506 * @protected
507 * @method loadFileConfigs
508 * @return config {Object} The configuration object
509 */
510util.loadFileConfigs = function(configDir) {
511
512 // Initialize
513 var t = this,
514 config = {};
515
516 // Initialize parameters from command line, environment, or default
517 NODE_ENV = util.initParam('NODE_ENV', 'development');
518
519 // Override, NODE_ENV if NODE_CONFIG_ENV is specified.
520 NODE_ENV = util.initParam('NODE_CONFIG_ENV', NODE_ENV);
521
522 // Split files name, for loading multiple files.
523 NODE_ENV = NODE_ENV.split(',');
524
525 CONFIG_DIR = configDir || util.initParam('NODE_CONFIG_DIR', Path.join( process.cwd(), 'config') );
526 if (CONFIG_DIR.indexOf('.') === 0) {
527 CONFIG_DIR = Path.join(process.cwd() , CONFIG_DIR);
528 }
529
530 APP_INSTANCE = util.initParam('NODE_APP_INSTANCE');
531 HOST = util.initParam('HOST');
532 HOSTNAME = util.initParam('HOSTNAME');
533 CONFIG_SKIP_GITCRYPT = util.initParam('CONFIG_SKIP_GITCRYPT');
534
535 // This is for backward compatibility
536 RUNTIME_JSON_FILENAME = util.initParam('NODE_CONFIG_RUNTIME_JSON', Path.join(CONFIG_DIR , 'runtime.json') );
537
538 NODE_CONFIG_PARSER = util.initParam('NODE_CONFIG_PARSER');
539 if (NODE_CONFIG_PARSER) {
540 try {
541 var parserModule = Path.isAbsolute(NODE_CONFIG_PARSER)
542 ? NODE_CONFIG_PARSER
543 : Path.join(CONFIG_DIR, NODE_CONFIG_PARSER);
544 Parser = require(parserModule);
545 }
546 catch (e) {
547 console.warn('Failed to load config parser from ' + NODE_CONFIG_PARSER);
548 console.log(e);
549 }
550 }
551
552 // Determine the host name from the OS module, $HOST, or $HOSTNAME
553 // Remove any . appendages, and default to null if not set
554 try {
555 var hostName = HOST || HOSTNAME;
556
557 if (!hostName) {
558 var OS = require('os');
559 hostName = OS.hostname();
560 }
561 } catch (e) {
562 hostName = '';
563 }
564
565 // Store the hostname that won.
566 env.HOSTNAME = hostName;
567
568 // Read each file in turn
569 var baseNames = ['default'].concat(NODE_ENV);
570
571 // #236: Also add full hostname when they are different.
572 if (hostName) {
573 var firstDomain = hostName.split('.')[0];
574
575 NODE_ENV.forEach(function(env) {
576 // Backward compatibility
577 baseNames.push(firstDomain, firstDomain + '-' + env);
578
579 // Add full hostname when it is not the same
580 if (hostName !== firstDomain) {
581 baseNames.push(hostName, hostName + '-' + env);
582 }
583 });
584 }
585
586 NODE_ENV.forEach(function(env) {
587 baseNames.push('local', 'local-' + env);
588 });
589
590 var allowedFiles = {};
591 var resolutionIndex = 1;
592 var extNames = Parser.getFilesOrder();
593 baseNames.forEach(function(baseName) {
594 extNames.forEach(function(extName) {
595 allowedFiles[baseName + '.' + extName] = resolutionIndex++;
596 if (APP_INSTANCE) {
597 allowedFiles[baseName + '-' + APP_INSTANCE + '.' + extName] = resolutionIndex++;
598 }
599 });
600 });
601
602 var locatedFiles = util.locateMatchingFiles(CONFIG_DIR, allowedFiles);
603 locatedFiles.forEach(function(fullFilename) {
604 var configObj = util.parseFile(fullFilename);
605 if (configObj) {
606 util.extendDeep(config, configObj);
607 }
608 });
609
610 // Override configurations from the $NODE_CONFIG environment variable
611 // NODE_CONFIG only applies to the base config
612 if (!configDir) {
613 var envConfig = {};
614 if (process.env.NODE_CONFIG) {
615 try {
616 envConfig = JSON.parse(process.env.NODE_CONFIG);
617 } catch(e) {
618 console.error('The $NODE_CONFIG environment variable is malformed JSON');
619 }
620 util.extendDeep(config, envConfig);
621 configSources.push({
622 name: "$NODE_CONFIG",
623 parsed: envConfig,
624 });
625 }
626
627 // Override configurations from the --NODE_CONFIG command line
628 var cmdLineConfig = util.getCmdLineArg('NODE_CONFIG');
629 if (cmdLineConfig) {
630 try {
631 cmdLineConfig = JSON.parse(cmdLineConfig);
632 } catch(e) {
633 console.error('The --NODE_CONFIG={json} command line argument is malformed JSON');
634 }
635 util.extendDeep(config, cmdLineConfig);
636 configSources.push({
637 name: "--NODE_CONFIG argument",
638 parsed: cmdLineConfig,
639 });
640 }
641
642 // Place the mixed NODE_CONFIG into the environment
643 env['NODE_CONFIG'] = JSON.stringify(util.extendDeep(envConfig, cmdLineConfig, {}));
644 }
645
646 // Override with environment variables if there is a custom-environment-variables.EXT mapping file
647 var customEnvVars = util.getCustomEnvVars(CONFIG_DIR, extNames);
648 util.extendDeep(config, customEnvVars);
649
650 // Extend the original config with the contents of runtime.json (backwards compatibility)
651 var runtimeJson = util.parseFile(RUNTIME_JSON_FILENAME) || {};
652 util.extendDeep(config, runtimeJson);
653
654 util.resolveDeferredConfigs(config);
655
656 // Return the configuration object
657 return config;
658};
659
660/**
661 * Return a list of fullFilenames who exists in allowedFiles
662 * Ordered according to allowedFiles argument specifications
663 *
664 * @protected
665 * @method locateMatchingFiles
666 * @param configDirs {string} the config dir, or multiple dirs separated by a column (:)
667 * @param allowedFiles {object} an object. keys and supported filenames
668 * and values are the position in the resolution order
669 * @returns {string[]} fullFilenames - path + filename
670 */
671util.locateMatchingFiles = function(configDirs, allowedFiles) {
672 return configDirs.split(Path.delimiter)
673 .reduce(function(files, configDir) {
674 if (configDir) {
675 try {
676 FileSystem.readdirSync(configDir).forEach(function(file) {
677 if (allowedFiles[file]) {
678 files.push([allowedFiles[file], Path.join(configDir, file)]);
679 }
680 });
681 }
682 catch(e) {}
683 return files;
684 }
685 }, [])
686 .sort(function(a, b) { return a[0] - b[0]; })
687 .map(function(file) { return file[1]; });
688};
689
690// Using basic recursion pattern, find all the deferred values and resolve them.
691util.resolveDeferredConfigs = function (config) {
692 var deferred = [];
693
694 function _iterate (prop) {
695
696 // We put the properties we are going to look it in an array to keep the order predictable
697 var propsToSort = [];
698
699 // First step is to put the properties of interest in an array
700 for (var property in prop) {
701 if (prop.hasOwnProperty(property) && prop[property] != null) {
702 propsToSort.push(property);
703 }
704 }
705
706 // Second step is to iterate of the elements in a predictable (sorted) order
707 propsToSort.sort().forEach(function (property) {
708 if (prop[property].constructor === Object) {
709 _iterate(prop[property]);
710 } else if (prop[property].constructor === Array) {
711 for (var i = 0; i < prop[property].length; i++) {
712 if (prop[property][i] instanceof DeferredConfig) {
713 deferred.push(prop[property][i].prepare(config, prop[property], i));
714 }
715 else {
716 _iterate(prop[property][i]);
717 }
718 }
719 } else {
720 if (prop[property] instanceof DeferredConfig) {
721 deferred.push(prop[property].prepare(config, prop, property));
722 }
723 // else: Nothing to do. Keep the property how it is.
724 }
725 });
726 }
727
728 _iterate(config);
729
730 deferred.forEach(function (defer) { defer.resolve(); });
731};
732
733/**
734 * Parse and return the specified configuration file.
735 *
736 * If the file exists in the application config directory, it will
737 * parse and return it as a JavaScript object.
738 *
739 * The file extension determines the parser to use.
740 *
741 * .js = File to run that has a module.exports containing the config object
742 * .coffee = File to run that has a module.exports with coffee-script containing the config object
743 * .iced = File to run that has a module.exports with iced-coffee-script containing the config object
744 * All other supported file types (yaml, toml, json, cson, hjson, json5, properties, xml)
745 * are parsed with util.parseString.
746 *
747 * If the file doesn't exist, a null will be returned. If the file can't be
748 * parsed, an exception will be thrown.
749 *
750 * This method performs synchronous file operations, and should not be called
751 * after synchronous module loading.
752 *
753 * @protected
754 * @method parseFile
755 * @param fullFilename {string} The full file path and name
756 * @return configObject {object|null} The configuration object parsed from the file
757 */
758util.parseFile = function(fullFilename) {
759 var t = this, // Initialize
760 configObject = null,
761 fileContent = null,
762 stat = null;
763
764 // Note that all methods here are the Sync versions. This is appropriate during
765 // module loading (which is a synchronous operation), but not thereafter.
766
767 try {
768 // Try loading the file.
769 fileContent = FileSystem.readFileSync(fullFilename, 'utf-8');
770 fileContent = fileContent.replace(/^\uFEFF/, '');
771 }
772 catch (e2) {
773 if (e2.code !== 'ENOENT') {
774 throw new Error('Config file ' + fullFilename + ' cannot be read. Error code is: '+e2.code
775 +'. Error message is: '+e2.message);
776 }
777 return null; // file doesn't exists
778 }
779
780 // Parse the file based on extension
781 try {
782
783 // skip if it's a gitcrypt file and CONFIG_SKIP_GITCRYPT is true
784 if (CONFIG_SKIP_GITCRYPT) {
785 if (gitCryptTestRegex.test(fileContent)) {
786 console.error('WARNING: ' + fullFilename + ' is a git-crypt file and CONFIG_SKIP_GITCRYPT is set. skipping.');
787 return null;
788 }
789 }
790
791 configObject = Parser.parse(fullFilename, fileContent);
792 }
793 catch (e3) {
794 if (gitCryptTestRegex.test(fileContent)) {
795 console.error('ERROR: ' + fullFilename + ' is a git-crypt file and CONFIG_SKIP_GITCRYPT is not set.');
796 }
797 throw new Error("Cannot parse config file: '" + fullFilename + "': " + e3);
798 }
799
800 // Keep track of this configuration sources, including empty ones
801 if (typeof configObject === 'object') {
802 configSources.push({
803 name: fullFilename,
804 original: fileContent,
805 parsed: configObject,
806 });
807 }
808
809 return configObject;
810};
811
812/**
813 * Parse and return the specied string with the specified format.
814 *
815 * The format determines the parser to use.
816 *
817 * json = File is parsed using JSON.parse()
818 * yaml (or yml) = Parsed with a YAML parser
819 * toml = Parsed with a TOML parser
820 * cson = Parsed with a CSON parser
821 * hjson = Parsed with a HJSON parser
822 * json5 = Parsed with a JSON5 parser
823 * properties = Parsed with the 'properties' node package
824 * xml = Parsed with a XML parser
825 *
826 * If the file doesn't exist, a null will be returned. If the file can't be
827 * parsed, an exception will be thrown.
828 *
829 * This method performs synchronous file operations, and should not be called
830 * after synchronous module loading.
831 *
832 * @protected
833 * @method parseString
834 * @param content {string} The full content
835 * @param format {string} The format to be parsed
836 * @return {configObject} The configuration object parsed from the string
837 */
838util.parseString = function (content, format) {
839 var parser = Parser.getParser(format);
840 if (typeof parser === 'function') {
841 return parser(null, content);
842 }
843};
844
845/**
846 * Attach the Config class prototype to all config objects recursively.
847 *
848 * <p>
849 * This allows you to do anything with CONFIG sub-objects as you can do with
850 * the top-level CONFIG object. It's so you can do this:
851 * </p>
852 *
853 * <pre>
854 * var CUST_CONFIG = require('config').Customer;
855 * CUST_CONFIG.get(...)
856 * </pre>
857 *
858 * @protected
859 * @method attachProtoDeep
860 * @param toObject
861 * @param depth
862 * @return toObject
863 */
864util.attachProtoDeep = function(toObject, depth) {
865 if (toObject instanceof RawConfig) {
866 return toObject;
867 }
868
869 // Recursion detection
870 var t = this;
871 depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
872 if (depth < 0) {
873 return toObject;
874 }
875
876 // Adding Config.prototype methods directly to toObject as hidden properties
877 // because adding to toObject.__proto__ exposes the function in toObject
878 for (var fnName in Config.prototype) {
879 if (!toObject[fnName]) {
880 util.makeHidden(toObject, fnName, Config.prototype[fnName]);
881 }
882 }
883
884 // Add prototypes to sub-objects
885 for (var prop in toObject) {
886 if (util.isObject(toObject[prop])) {
887 util.attachProtoDeep(toObject[prop], depth - 1);
888 }
889 }
890
891 // Return the original object
892 return toObject;
893};
894
895/**
896 * Return a deep copy of the specified object.
897 *
898 * This returns a new object with all elements copied from the specified
899 * object. Deep copies are made of objects and arrays so you can do anything
900 * with the returned object without affecting the input object.
901 *
902 * @protected
903 * @method cloneDeep
904 * @param parent {object} The original object to copy from
905 * @param [depth=20] {Integer} Maximum depth (default 20)
906 * @return {object} A new object with the elements copied from the copyFrom object
907 *
908 * This method is copied from https://github.com/pvorb/node-clone/blob/17eea36140d61d97a9954c53417d0e04a00525d9/clone.js
909 *
910 * Copyright © 2011-2014 Paul Vorbach and contributors.
911 * Permission is hereby granted, free of charge, to any person obtaining a copy
912 * of this software and associated documentation files (the “Software”), to deal
913 * in the Software without restriction, including without limitation the rights
914 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
915 * of the Software, and to permit persons to whom the Software is furnished to do so,
916 * subject to the following conditions: The above copyright notice and this permission
917 * notice shall be included in all copies or substantial portions of the Software.
918 */
919util.cloneDeep = function cloneDeep(parent, depth, circular, prototype) {
920 // maintain two arrays for circular references, where corresponding parents
921 // and children have the same index
922 var allParents = [];
923 var allChildren = [];
924
925 var useBuffer = typeof Buffer != 'undefined';
926
927 if (typeof circular === 'undefined')
928 circular = true;
929
930 if (typeof depth === 'undefined')
931 depth = 20;
932
933 // recurse this function so we don't reset allParents and allChildren
934 function _clone(parent, depth) {
935 // cloning null always returns null
936 if (parent === null)
937 return null;
938
939 if (depth === 0)
940 return parent;
941
942 var child;
943 if (typeof parent != 'object') {
944 return parent;
945 }
946
947 if (Utils.isArray(parent)) {
948 child = [];
949 } else if (Utils.isRegExp(parent)) {
950 child = new RegExp(parent.source, util.getRegExpFlags(parent));
951 if (parent.lastIndex) child.lastIndex = parent.lastIndex;
952 } else if (Utils.isDate(parent)) {
953 child = new Date(parent.getTime());
954 } else if (useBuffer && Buffer.isBuffer(parent)) {
955 child = new Buffer(parent.length);
956 parent.copy(child);
957 return child;
958 } else {
959 if (typeof prototype === 'undefined') child = Object.create(Object.getPrototypeOf(parent));
960 else child = Object.create(prototype);
961 }
962
963 if (circular) {
964 var index = allParents.indexOf(parent);
965
966 if (index != -1) {
967 return allChildren[index];
968 }
969 allParents.push(parent);
970 allChildren.push(child);
971 }
972
973 for (var i in parent) {
974 var propDescriptor = Object.getOwnPropertyDescriptor(parent,i);
975 var hasGetter = ((propDescriptor !== undefined) && (propDescriptor.get !== undefined));
976
977 if (hasGetter){
978 Object.defineProperty(child,i,propDescriptor);
979 } else if (util.isPromise(parent[i])) {
980 child[i] = parent[i];
981 } else {
982 child[i] = _clone(parent[i], depth - 1);
983 }
984 }
985
986 return child;
987 }
988
989 return _clone(parent, depth);
990};
991
992/**
993 * Set objects given a path as a string list
994 *
995 * @protected
996 * @method setPath
997 * @param object {object} - Object to set the property on
998 * @param path {array[string]} - Array path to the property
999 * @param value {*} - value to set, ignoring null
1000 */
1001util.setPath = function (object, path, value) {
1002 var nextKey = null;
1003 if (value === null || path.length === 0) {
1004 return;
1005 }
1006 else if (path.length === 1) { // no more keys to make, so set the value
1007 object[path.shift()] = value;
1008 }
1009 else {
1010 nextKey = path.shift();
1011 if (!object.hasOwnProperty(nextKey)) {
1012 object[nextKey] = {};
1013 }
1014 util.setPath(object[nextKey], path, value);
1015 }
1016};
1017
1018/**
1019 * Create a new object patterned after substitutionMap, where:
1020 * 1. Terminal string values in substitutionMap are used as keys
1021 * 2. To look up values in a key-value store, variables
1022 * 3. And parent keys are created as necessary to retain the structure of substitutionMap.
1023 *
1024 * @protected
1025 * @method substituteDeep
1026 * @param substitionMap {object} - an object whose terminal (non-subobject) values are strings
1027 * @param variables {object[string:value]} - usually process.env, a flat object used to transform
1028 * terminal values in a copy of substititionMap.
1029 * @returns {object} - deep copy of substitutionMap with only those paths whose terminal values
1030 * corresponded to a key in `variables`
1031 */
1032util.substituteDeep = function (substitutionMap, variables) {
1033 var result = {};
1034
1035 function _substituteVars(map, vars, pathTo) {
1036 for (var prop in map) {
1037 var value = map[prop];
1038 if (typeof(value) === 'string') { // We found a leaf variable name
1039 if (vars[value] !== undefined) { // if the vars provide a value set the value in the result map
1040 util.setPath(result, pathTo.concat(prop), vars[value]);
1041 }
1042 }
1043 else if (util.isObject(value)) { // work on the subtree, giving it a clone of the pathTo
1044 if ('__name' in value && '__format' in value && vars[value.__name] !== undefined) {
1045 try {
1046 var parsedValue = util.parseString(vars[value.__name], value.__format);
1047 } catch(err) {
1048 err.message = '__format parser error in ' + value.__name + ': ' + err.message;
1049 throw err;
1050 }
1051 util.setPath(result, pathTo.concat(prop), parsedValue);
1052 } else {
1053 _substituteVars(value, vars, pathTo.concat(prop));
1054 }
1055 }
1056 else {
1057 msg = "Illegal key type for substitution map at " + pathTo.join('.') + ': ' + typeof(value);
1058 throw Error(msg);
1059 }
1060 }
1061 }
1062
1063 _substituteVars(substitutionMap, variables, []);
1064 return result;
1065
1066};
1067
1068/* Map environment variables into the configuration if a mapping file,
1069 * `custom-environment-variables.EXT` exists.
1070 *
1071 * @protected
1072 * @method getCustomEnvVars
1073 * @param CONFIG_DIR {string} - the passsed configuration directory
1074 * @param extNames {Array[string]} - acceptable configuration file extension names.
1075 * @returns {object} - mapped environment variables or {} if there are none
1076 */
1077util.getCustomEnvVars = function (CONFIG_DIR, extNames) {
1078 var result = {};
1079 var resolutionIndex = 1;
1080 var allowedFiles = {};
1081 extNames.forEach(function (extName) {
1082 allowedFiles['custom-environment-variables' + '.' + extName] = resolutionIndex++;
1083 });
1084 var locatedFiles = util.locateMatchingFiles(CONFIG_DIR, allowedFiles);
1085 locatedFiles.forEach(function (fullFilename) {
1086 var configObj = util.parseFile(fullFilename);
1087 if (configObj) {
1088 var environmentSubstitutions = util.substituteDeep(configObj, process.env);
1089 util.extendDeep(result, environmentSubstitutions);
1090 }
1091 });
1092 return result;
1093};
1094
1095/**
1096 * Return true if two objects have equal contents.
1097 *
1098 * @protected
1099 * @method equalsDeep
1100 * @param object1 {object} The object to compare from
1101 * @param object2 {object} The object to compare with
1102 * @param depth {integer} An optional depth to prevent recursion. Default: 20.
1103 * @return {boolean} True if both objects have equivalent contents
1104 */
1105util.equalsDeep = function(object1, object2, depth) {
1106
1107 // Recursion detection
1108 var t = this;
1109 depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
1110 if (depth < 0) {
1111 return {};
1112 }
1113
1114 // Fast comparisons
1115 if (!object1 || !object2) {
1116 return false;
1117 }
1118 if (object1 === object2) {
1119 return true;
1120 }
1121 if (typeof(object1) != 'object' || typeof(object2) != 'object') {
1122 return false;
1123 }
1124
1125 // They must have the same keys. If their length isn't the same
1126 // then they're not equal. If the keys aren't the same, the value
1127 // comparisons will fail.
1128 if (Object.keys(object1).length != Object.keys(object2).length) {
1129 return false;
1130 }
1131
1132 // Compare the values
1133 for (var prop in object1) {
1134
1135 // Call recursively if an object or array
1136 if (object1[prop] && typeof(object1[prop]) === 'object') {
1137 if (!util.equalsDeep(object1[prop], object2[prop], depth - 1)) {
1138 return false;
1139 }
1140 }
1141 else {
1142 if (object1[prop] !== object2[prop]) {
1143 return false;
1144 }
1145 }
1146 }
1147
1148 // Test passed.
1149 return true;
1150};
1151
1152/**
1153 * Returns an object containing all elements that differ between two objects.
1154 * <p>
1155 * This method was designed to be used to create the runtime.json file
1156 * contents, but can be used to get the diffs between any two Javascript objects.
1157 * </p>
1158 * <p>
1159 * It works best when object2 originated by deep copying object1, then
1160 * changes were made to object2, and you want an object that would give you
1161 * the changes made to object1 which resulted in object2.
1162 * </p>
1163 *
1164 * @protected
1165 * @method diffDeep
1166 * @param object1 {object} The base object to compare to
1167 * @param object2 {object} The object to compare with
1168 * @param depth {integer} An optional depth to prevent recursion. Default: 20.
1169 * @return {object} A differential object, which if extended onto object1 would
1170 * result in object2.
1171 */
1172util.diffDeep = function(object1, object2, depth) {
1173
1174 // Recursion detection
1175 var t = this, diff = {};
1176 depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
1177 if (depth < 0) {
1178 return {};
1179 }
1180
1181 // Process each element from object2, adding any element that's different
1182 // from object 1.
1183 for (var parm in object2) {
1184 var value1 = object1[parm];
1185 var value2 = object2[parm];
1186 if (value1 && value2 && util.isObject(value2)) {
1187 if (!(util.equalsDeep(value1, value2))) {
1188 diff[parm] = util.diffDeep(value1, value2, depth - 1);
1189 }
1190 }
1191 else if (Array.isArray(value1) && Array.isArray(value2)) {
1192 if(!util.equalsDeep(value1, value2)) {
1193 diff[parm] = value2;
1194 }
1195 }
1196 else if (value1 !== value2){
1197 diff[parm] = value2;
1198 }
1199 }
1200
1201 // Return the diff object
1202 return diff;
1203
1204};
1205
1206/**
1207 * Extend an object, and any object it contains.
1208 *
1209 * This does not replace deep objects, but dives into them
1210 * replacing individual elements instead.
1211 *
1212 * @protected
1213 * @method extendDeep
1214 * @param mergeInto {object} The object to merge into
1215 * @param mergeFrom... {object...} - Any number of objects to merge from
1216 * @param depth {integer} An optional depth to prevent recursion. Default: 20.
1217 * @return {object} The altered mergeInto object is returned
1218 */
1219util.extendDeep = function(mergeInto) {
1220
1221 // Initialize
1222 var t = this;
1223 var vargs = Array.prototype.slice.call(arguments, 1);
1224 var depth = vargs.pop();
1225 if (typeof(depth) != 'number') {
1226 vargs.push(depth);
1227 depth = DEFAULT_CLONE_DEPTH;
1228 }
1229
1230 // Recursion detection
1231 if (depth < 0) {
1232 return mergeInto;
1233 }
1234
1235 // Cycle through each object to extend
1236 vargs.forEach(function(mergeFrom) {
1237
1238 // Cycle through each element of the object to merge from
1239 for (var prop in mergeFrom) {
1240
1241 // save original value in deferred elements
1242 var fromIsDeferredFunc = mergeFrom[prop] instanceof DeferredConfig;
1243 var isDeferredFunc = mergeInto[prop] instanceof DeferredConfig;
1244
1245 if (fromIsDeferredFunc && mergeInto.hasOwnProperty(prop)) {
1246 mergeFrom[prop]._original = isDeferredFunc ? mergeInto[prop]._original : mergeInto[prop];
1247 }
1248 // Extend recursively if both elements are objects and target is not really a deferred function
1249 if (mergeFrom[prop] instanceof Date) {
1250 mergeInto[prop] = mergeFrom[prop];
1251 } if (mergeFrom[prop] instanceof RegExp) {
1252 mergeInto[prop] = mergeFrom[prop];
1253 } else if (util.isObject(mergeInto[prop]) && util.isObject(mergeFrom[prop]) && !isDeferredFunc) {
1254 util.extendDeep(mergeInto[prop], mergeFrom[prop], depth - 1);
1255 }
1256 else if (util.isPromise(mergeFrom[prop])) {
1257 mergeInto[prop] = mergeFrom[prop];
1258 }
1259 // Copy recursively if the mergeFrom element is an object (or array or fn)
1260 else if (mergeFrom[prop] && typeof mergeFrom[prop] === 'object') {
1261 mergeInto[prop] = util.cloneDeep(mergeFrom[prop], depth -1);
1262 }
1263
1264 // Copy property descriptor otherwise, preserving accessors
1265 else if (Object.getOwnPropertyDescriptor(Object(mergeFrom), prop)){
1266 Object.defineProperty(mergeInto, prop, Object.getOwnPropertyDescriptor(Object(mergeFrom), prop));
1267 } else {
1268 mergeInto[prop] = mergeFrom[prop];
1269 }
1270 }
1271 });
1272
1273 // Chain
1274 return mergeInto;
1275
1276};
1277
1278/**
1279 * Is the specified argument a regular javascript object?
1280 *
1281 * The argument is an object if it's a JS object, but not an array.
1282 *
1283 * @protected
1284 * @method isObject
1285 * @param obj {*} An argument of any type.
1286 * @return {boolean} TRUE if the arg is an object, FALSE if not
1287 */
1288util.isObject = function(obj) {
1289 return (obj !== null) && (typeof obj === 'object') && !(Array.isArray(obj));
1290};
1291
1292/**
1293 * Is the specified argument a javascript promise?
1294 *
1295 * @protected
1296 * @method isPromise
1297 * @param obj {*} An argument of any type.
1298 * @returns {boolean}
1299 */
1300util.isPromise = function(obj) {
1301 return Object.prototype.toString.call(obj) === '[object Promise]';
1302};
1303
1304/**
1305 * <p>Initialize a parameter from the command line or process environment</p>
1306 *
1307 * <p>
1308 * This method looks for the parameter from the command line in the format
1309 * --PARAMETER=VALUE, then from the process environment, then from the
1310 * default specified as an argument.
1311 * </p>
1312 *
1313 * @method initParam
1314 * @param paramName {String} Name of the parameter
1315 * @param [defaultValue] {Any} Default value of the parameter
1316 * @return {Any} The found value, or default value
1317 */
1318util.initParam = function (paramName, defaultValue) {
1319 var t = this;
1320
1321 // Record and return the value
1322 var value = util.getCmdLineArg(paramName) || process.env[paramName] || defaultValue;
1323 env[paramName] = value;
1324 return value;
1325}
1326
1327/**
1328 * <p>Get Command Line Arguments</p>
1329 *
1330 * <p>
1331 * This method allows you to retrieve the value of the specified command line argument.
1332 * </p>
1333 *
1334 * <p>
1335 * The argument is case sensitive, and must be of the form '--ARG_NAME=value'
1336 * </p>
1337 *
1338 * @method getCmdLineArg
1339 * @param searchFor {String} The argument name to search for
1340 * @return {*} false if the argument was not found, the argument value if found
1341 */
1342util.getCmdLineArg = function (searchFor) {
1343 var cmdLineArgs = process.argv.slice(2, process.argv.length),
1344 argName = '--' + searchFor + '=';
1345
1346 for (var argvIt = 0; argvIt < cmdLineArgs.length; argvIt++) {
1347 if (cmdLineArgs[argvIt].indexOf(argName) === 0) {
1348 return cmdLineArgs[argvIt].substr(argName.length);
1349 }
1350 }
1351
1352 return false;
1353}
1354
1355/**
1356 * <p>Get a Config Environment Variable Value</p>
1357 *
1358 * <p>
1359 * This method returns the value of the specified config environment variable,
1360 * including any defaults or overrides.
1361 * </p>
1362 *
1363 * @method getEnv
1364 * @param varName {String} The environment variable name
1365 * @return {String} The value of the environment variable
1366 */
1367util.getEnv = function (varName) {
1368 return env[varName];
1369}
1370
1371
1372
1373/**
1374 * Returns a string of flags for regular expression `re`.
1375 *
1376 * @param {RegExp} re Regular expression
1377 * @returns {string} Flags
1378 */
1379util.getRegExpFlags = function (re) {
1380 var flags = '';
1381 re.global && (flags += 'g');
1382 re.ignoreCase && (flags += 'i');
1383 re.multiline && (flags += 'm');
1384 return flags;
1385};
1386
1387/**
1388 * Returns a new deep copy of the current config object, or any part of the config if provided.
1389 *
1390 * @param {Object} config The part of the config to copy and serialize. Omit this argument to return the entire config.
1391 * @returns {Object} The cloned config or part of the config
1392 */
1393util.toObject = function(config) {
1394 return JSON.parse(JSON.stringify(config || this));
1395};
1396
1397// Run strictness checks on NODE_ENV and NODE_APP_INSTANCE and throw an error if there's a problem.
1398util.runStrictnessChecks = function (config) {
1399 var sources = config.util.getConfigSources();
1400
1401 var sourceFilenames = sources.map(function (src) {
1402 return Path.basename(src.name);
1403 });
1404
1405 NODE_ENV.forEach(function(env) {
1406 // Throw an exception if there's no explicit config file for NODE_ENV
1407 var anyFilesMatchEnv = sourceFilenames.some(function (filename) {
1408 return filename.match(env);
1409 });
1410 // development is special-cased because it's the default value
1411 if (env && (env !== 'development') && !anyFilesMatchEnv) {
1412 _warnOrThrow("NODE_ENV value of '"+env+"' did not match any deployment config file names.");
1413 }
1414 // Throw if NODE_ENV matches' default' or 'local'
1415 if ((env === 'default') || (env === 'local')) {
1416 _warnOrThrow("NODE_ENV value of '"+env+"' is ambiguous.");
1417 }
1418 });
1419
1420 // Throw an exception if there's no explict config file for NODE_APP_INSTANCE
1421 var anyFilesMatchInstance = sourceFilenames.some(function (filename) {
1422 return filename.match(APP_INSTANCE);
1423 });
1424 if (APP_INSTANCE && !anyFilesMatchInstance) {
1425 _warnOrThrow("NODE_APP_INSTANCE value of '"+APP_INSTANCE+"' did not match any instance config file names.");
1426 }
1427
1428 function _warnOrThrow (msg) {
1429 var beStrict = process.env.NODE_CONFIG_STRICT_MODE;
1430 var prefix = beStrict ? 'FATAL: ' : 'WARNING: ';
1431 var seeURL = 'See https://github.com/lorenwest/node-config/wiki/Strict-Mode';
1432
1433 console.error(prefix+msg);
1434 console.error(prefix+seeURL);
1435
1436 // Accept 1 and true as truthy values. When set via process.env, Node.js casts them to strings.
1437 if (["true", "1"].indexOf(beStrict) >= 0) {
1438 throw new Error(prefix+msg+' '+seeURL);
1439 }
1440 }
1441};
1442
1443// Instantiate and export the configuration
1444var config = module.exports = new Config();
1445
1446// copy methods to util for backwards compatibility
1447util.stripComments = Parser.stripComments;
1448util.stripYamlComments = Parser.stripYamlComments;
1449
1450// Produce warnings if the configuration is empty
1451var showWarnings = !(util.initParam('SUPPRESS_NO_CONFIG_WARNING'));
1452if (showWarnings && Object.keys(config).length === 0) {
1453 console.error('WARNING: No configurations found in configuration directory:' +CONFIG_DIR);
1454 console.error('WARNING: To disable this warning set SUPPRESS_NO_CONFIG_WARNING in the environment.');
1455}