// config.js (c) 2010-2015 Loren West and other contributors // May be freely distributed under the MIT license. // For further details and documentation: // http://lorenwest.github.com/node-config // Dependencies var Yaml = null, // External libraries are lazy-loaded VisionmediaYaml = null, // only if these file types exist. Coffee = null, Iced = null, CSON = null, PPARSER = null, JSON5 = null, TOML = null, HJSON = null, XML = null, deferConfig = require('../defer').deferConfig, DeferredConfig = require('../defer').DeferredConfig, RawConfig = require('../raw').RawConfig, Utils = require('util'), Path = require('path'), FileSystem = require('fs'); // Static members var DEFAULT_CLONE_DEPTH = 20, NODE_CONFIG, CONFIG_DIR, RUNTIME_JSON_FILENAME, NODE_ENV, APP_INSTANCE, HOST, HOSTNAME, ALLOW_CONFIG_MUTATIONS, CONFIG_SKIP_GITCRYPT, env = {}, privateUtil = {}, deprecationWarnings = {}, configSources = [], // Configuration sources - array of {name, original, parsed} checkMutability = true, // Check for mutability/immutability on first get gitCryptTestRegex = /^.GITCRYPT/; // regular expression to test for gitcrypt files. // Define soft dependencies so transpilers don't include everything var COFFEE_2_DEP = "coffeescript", COFFEE_DEP = "coffee-script", ICED_DEP = "iced-coffee-script", JS_YAML_DEP = "js-yaml", YAML_DEP = "yaml", JSON5_DEP = "json5", HJSON_DEP = "hjson", TOML_DEP = "toml", CSON_DEP = "cson", PPARSER_DEP = "properties", XML_DEP = "x2js", TS_DEP = "ts-node"; /** *

Application Configurations

* *

* The config module exports a singleton object representing all * configurations for this application deployment. *

* *

* Application configurations are stored in files within the config directory * of your application. The default configuration file is loaded, followed * by files specific to the deployment type (development, testing, staging, * production, etc.). *

* *

* For example, with the following config/default.yaml file: *

* *
 *   ...
 *   customer:
 *       initialCredit: 500
 *       db:
 *           name: customer
 *           port: 5984
 *   ...
 * 
* *

* The following code loads the customer section into the CONFIG variable: *

* *

 *   var CONFIG = require('config').customer;
 *   ...
 *   newCustomer.creditLimit = CONFIG.initialCredit;
 *   database.open(CONFIG.db.name, CONFIG.db.port);
 *   ...
 * 
* * @module config * @class Config */ /** *

Get the configuration object.

* *

* The configuration object is a shared singleton object within the application, * attained by calling require('config'). *

* *

* Usually you'll specify a CONFIG variable at the top of your .js file * for file/module scope. If you want the root of the object, you can do this: *

*
 * var CONFIG = require('config');
 * 
* *

* Sometimes you only care about a specific sub-object within the CONFIG * object. In that case you could do this at the top of your file: *

*
 * var CONFIG = require('config').customer;
 * or
 * var CUSTOMER_CONFIG = require('config').customer;
 * 
* * * * @method constructor * @return CONFIG {object} - The top level configuration object */ var Config = function() { var t = this; // Bind all utility functions to this for (var fnName in util) { util[fnName] = util[fnName].bind(t); } // Merge configurations into this util.extendDeep(t, util.loadFileConfigs()); util.attachProtoDeep(t); // Perform strictness checks and possibly throw an exception. util.runStrictnessChecks(t); }; /** * Utilities are under the util namespace vs. at the top level */ var util = Config.prototype.util = {}; /** * Underlying get mechanism * * @private * @method getImpl * @param object {object} - Object to get the property for * @param property {string | array[string]} - The property name to get (as an array or '.' delimited string) * @return value {*} - Property value, including undefined if not defined. */ var getImpl= function(object, property) { var t = this, elems = Array.isArray(property) ? property : property.split('.'), name = elems[0], value = object[name]; if (elems.length <= 1) { return value; } // Note that typeof null === 'object' if (value === null || typeof value !== 'object') { return undefined; } return getImpl(value, elems.slice(1)); }; /** *

Get a configuration value

* *

* This will return the specified property value, throwing an exception if the * configuration isn't defined. It is used to assure configurations are defined * before being used, and to prevent typos. *

* * @method get * @param property {string} - The configuration property to get. Can include '.' sub-properties. * @return value {*} - The property value */ Config.prototype.get = function(property) { if(property === null || property === undefined){ throw new Error("Calling config.get with null or undefined argument"); } // Make configurations immutable after first get (unless disabled) if (checkMutability) { if (!util.initParam('ALLOW_CONFIG_MUTATIONS', false)) { util.makeImmutable(config); } checkMutability = false; } var t = this, value = getImpl(t, property); // Produce an exception if the property doesn't exist if (value === undefined) { throw new Error('Configuration property "' + property + '" is not defined'); } // Return the value return value; }; /** * Test that a configuration parameter exists * *
 *    var config = require('config');
 *    if (config.has('customer.dbName')) {
 *      console.log('Customer database name: ' + config.customer.dbName);
 *    }
 * 
* * @method has * @param property {string} - The configuration property to test. Can include '.' sub-properties. * @return isPresent {boolean} - True if the property is defined, false if not defined. */ Config.prototype.has = function(property) { // While get() throws an exception for undefined input, has() is designed to test validity, so false is appropriate if(property === null || property === undefined){ return false; } var t = this; return (getImpl(t, property) !== undefined); }; /** *

Monitor a javascript property for runtime changes.

* *

* This method was built for an earlier version of node-config that allowed * configuration value mutations. Since version 1.0.0, node-config no longer * allows configuration mutations, and is no longer used in node-config. *

* *

* It is deprecated, and will be removed in the next semver incompatible release 2.0.0. *

* * @method watch * @deprecated * @param object {object} - The object to watch. * @param property {string} - The property name to watch. Watch all object properties if null. * @param handler {function(object, propertyName, priorValue, newValue)} - Handler called when a property change is detected. * The handler is run along with other handlers registered for notification. * If your handler changes the value of the property, that change is applied after all handlers have finished processing the current change. * Then all handlers (including this one) will be called again with the newly changed value. * @param depth {integer} (optional) - If watching all object properties or if the specified property is an object, this specifies the depth of the object graph to watch for changes. Default 6. * @return object {object} - The original object is returned - for chaining. */ util.watch = function(object, property, handler, depth) { // Initialize var t = this, o = object; var allProperties = property ? [property] : Object.keys(o); // Deprecation warning if (!deprecationWarnings.watch) { console.error('WARNING: config.' + fnName + '() is deprecated, and will not be supported in release 2.0.'); console.error('WARNING: See https://github.com/lorenwest/node-config/wiki/Future-Compatibility#upcoming-incompatibilities'); deprecationWarnings.watch = true; } // Depth detection depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth); if (depth < 0) { return; } // Create hidden properties on the object if (!o.__watchers) util.makeHidden(o, '__watchers', {}); if (!o.__propertyValues) util.makeHidden(o, '__propertyValues', {}); // Attach watchers to all requested properties allProperties.forEach(function(prop){ // Setup the property for watching (first time only) if (typeof(o.__propertyValues[prop]) === 'undefined') { // Don't error re-defining the property if immutable var descriptor = Object.getOwnPropertyDescriptor(o, prop); if (descriptor && descriptor.writable === false) return; // Copy the value to the hidden field, and add the property to watchers o.__propertyValues[prop] = [o[prop]]; o.__watchers[prop] = []; // Attach the property watcher Object.defineProperty(o, prop, { enumerable : true, get : function(){ // If more than 1 item is in the values array, // then we're currently processing watchers. if (o.__propertyValues[prop].length === 1) // Current value return o.__propertyValues[prop][0]; else // [0] is prior value, [1] is new value being processed return o.__propertyValues[prop][1]; }, set : function(newValue) { // Return early if no change var origValue = o[prop]; if (util.equalsDeep(origValue, newValue)) return; // Remember the new value, and return if we're in another setter o.__propertyValues[prop].push(newValue); if (o.__propertyValues[prop].length > 2) return; // Call all watchers for each change requested var numIterations = 0; while (o.__propertyValues[prop].length > 1) { // Detect recursion if (++numIterations > 20) { o.__propertyValues[prop] = [origValue]; throw new Error('Recursion detected while setting [' + prop + ']'); } // Call each watcher for the current values var oldValue = o.__propertyValues[prop][0]; newValue = o.__propertyValues[prop][1]; o.__watchers[prop].forEach(function(watcher) { try { watcher(o, prop, oldValue, newValue); } catch (e) { // Log an error and continue with subsequent watchers console.error("Exception in object watcher for " + prop, e); } }); // Done processing this value o.__propertyValues[prop].splice(0,1); } } }); } // Done setting up the property for watching (first time) // Add the watcher to the property o.__watchers[prop].push(handler); // Recurs if this is an object... if (o[prop] && typeof(o[prop]) === 'object') { util.watch(o[prop], null, handler, depth - 1); } }); // Done processing each property // Return the original object - for chaining return o; }; /** *

* Set default configurations for a node.js module. *

* *

* This allows module developers to attach their configurations onto the * default configuration object so they can be configured by the consumers * of the module. *

* *

Using the function within your module:

*
 *   var CONFIG = require("config");
 *   CONFIG.util.setModuleDefaults("MyModule", {
 *     templateName: "t-50",
 *     colorScheme: "green"
 *   });
 * 
* // Template name may be overridden by application config files * console.log("Template: " + CONFIG.MyModule.templateName); *
* *

* The above example results in a "MyModule" element of the configuration * object, containing an object with the specified default values. *

* * @method setModuleDefaults * @param moduleName {string} - Name of your module. * @param defaultProperties {object} - The default module configuration. * @return moduleConfig {object} - The module level configuration object. */ util.setModuleDefaults = function (moduleName, defaultProperties) { // Copy the properties into a new object var t = this, moduleConfig = util.cloneDeep(defaultProperties); // Set module defaults into the first sources element if (configSources.length === 0 || configSources[0].name !== 'Module Defaults') { configSources.splice(0, 0, { name: 'Module Defaults', parsed: {} }); } configSources[0].parsed[moduleName] = {}; util.extendDeep(configSources[0].parsed[moduleName], defaultProperties); // Create a top level config for this module if it doesn't exist t[moduleName] = t[moduleName] || {}; // Extend local configurations into the module config util.extendDeep(moduleConfig, t[moduleName]); // Merge the extended configs without replacing the original util.extendDeep(t[moduleName], moduleConfig); // reset the mutability check for "config.get" method. // we are not making t[moduleName] immutable immediately, // since there might be more modifications before the first config.get if (!util.initParam('ALLOW_CONFIG_MUTATIONS', false)) { checkMutability = true; } // Attach handlers & watchers onto the module config object return util.attachProtoDeep(t[moduleName]); }; /** *

Make a configuration property hidden so it doesn't appear when enumerating * elements of the object.

* *

* The property still exists and can be read from and written to, but it won't * show up in for ... in loops, Object.keys(), or JSON.stringify() type methods. *

* *

* If the property already exists, it will be made hidden. Otherwise it will * be created as a hidden property with the specified value. *

* *

* This method was built for hiding configuration values, but it can be applied * to any javascript object. *

* *

Example:

*
 *   var CONFIG = require('config');
 *   ...
 *
 *   // Hide the Amazon S3 credentials
 *   CONFIG.util.makeHidden(CONFIG.amazonS3, 'access_id');
 *   CONFIG.util.makeHidden(CONFIG.amazonS3, 'secret_key');
 * 
* * @method makeHidden * @param object {object} - The object to make a hidden property into. * @param property {string} - The name of the property to make hidden. * @param value {*} - (optional) Set the property value to this (otherwise leave alone) * @return object {object} - The original object is returned - for chaining. */ util.makeHidden = function(object, property, value) { // If the new value isn't specified, just mark the property as hidden if (typeof value === 'undefined') { Object.defineProperty(object, property, { enumerable : false }); } // Otherwise set the value and mark it as hidden else { Object.defineProperty(object, property, { value : value, enumerable : false }); } return object; } /** *

Make a javascript object property immutable (assuring it cannot be changed * from the current value).

*

* If the specified property is an object, all attributes of that object are * made immutable, including properties of contained objects, recursively. * If a property name isn't supplied, all properties of the object are made * immutable. *

*

* *

*

* New properties can be added to the object and those properties will not be * immutable unless this method is called on those new properties. *

*

* This operation cannot be undone. *

* *

Example:

*
 *   var config = require('config');
 *   var myObject = {hello:'world'};
 *   config.util.makeImmutable(myObject);
 * 
* * @method makeImmutable * @param object {object} - The object to specify immutable properties for * @param [property] {string | [string]} - The name of the property (or array of names) to make immutable. * If not provided, all owned properties of the object are made immutable. * @param [value] {* | [*]} - Property value (or array of values) to set * the property to before making immutable. Only used when setting a single * property. Retained for backward compatibility. * @return object {object} - The original object is returned - for chaining. */ util.makeImmutable = function(object, property, value) { var properties = null; // Backwards compatibility mode where property/value can be specified if (typeof property === 'string') { return Object.defineProperty(object, property, { value : (typeof value === 'undefined') ? object[property] : value, writable : false, configurable: false }); } // Get the list of properties to work with if (Array.isArray(property)) { properties = property; } else { properties = Object.keys(object); } // Process each property for (var i = 0; i < properties.length; i++) { var propertyName = properties[i], value = object[propertyName]; if (value instanceof RawConfig) { Object.defineProperty(object, propertyName, { value: value.resolve(), writable: false, configurable: false }); } else { Object.defineProperty(object, propertyName, { value: value, writable : false, configurable: false }); // Call recursively if an object. if (util.isObject(value)) { util.makeImmutable(value); } } } return object; }; /** * Return the sources for the configurations * *

* All sources for configurations are stored in an array of objects containing * the source name (usually the filename), the original source (as a string), * and the parsed source as an object. *

* * @method getConfigSources * @return configSources {Array[Object]} - An array of objects containing * name, original, and parsed elements */ util.getConfigSources = function() { var t = this; return configSources.slice(0); }; /** * Load the individual file configurations. * *

* This method builds a map of filename to the configuration object defined * by the file. The search order is: *

* *
 *   default.EXT
 *   (deployment).EXT
 *   (hostname).EXT
 *   (hostname)-(deployment).EXT
 *   local.EXT
 *   local-(deployment).EXT
 *   runtime.json
 * 
* *

* EXT can be yml, yaml, coffee, iced, json, cson or js signifying the file type. * yaml (and yml) is in YAML format, coffee is a coffee-script, iced is iced-coffee-script, * json is in JSON format, cson is in CSON format, properties is in .properties format * (http://en.wikipedia.org/wiki/.properties), and js is a javascript executable file that is * require()'d with module.exports being the config object. *

* *

* hostname is the $HOST environment variable (or --HOST command line parameter) * if set, otherwise the $HOSTNAME environment variable (or --HOSTNAME command * line parameter) if set, otherwise the hostname found from * require('os').hostname(). *

* *

* Once a hostname is found, everything from the first period ('.') onwards * is removed. For example, abc.example.com becomes abc *

* *

* (deployment) is the deployment type, found in the $NODE_ENV environment * variable (which can be overriden by using $NODE_CONFIG_ENV * environment variable). Defaults to 'development'. *

* *

* The runtime.json file contains configuration changes made at runtime either * manually, or by the application setting a configuration value. *

* *

* If the $NODE_APP_INSTANCE environment variable (or --NODE_APP_INSTANCE * command line parameter) is set, then files with this appendage will be loaded. * See the Multiple Application Instances section of the main documentaion page * for more information. *

* * @protected * @method loadFileConfigs * @return config {Object} The configuration object */ util.loadFileConfigs = function(configDir) { // Initialize var t = this, config = {}; // Initialize parameters from command line, environment, or default NODE_ENV = util.initParam('NODE_ENV', 'development'); // Override, NODE_ENV if NODE_CONFIG_ENV is specified. NODE_ENV = util.initParam('NODE_CONFIG_ENV', NODE_ENV); // Split files name, for loading multiple files. NODE_ENV = NODE_ENV.split(','); CONFIG_DIR = configDir || util.initParam('NODE_CONFIG_DIR', Path.join( process.cwd(), 'config') ); if (CONFIG_DIR.indexOf('.') === 0) { CONFIG_DIR = Path.join(process.cwd() , CONFIG_DIR); } APP_INSTANCE = util.initParam('NODE_APP_INSTANCE'); HOST = util.initParam('HOST'); HOSTNAME = util.initParam('HOSTNAME'); CONFIG_SKIP_GITCRYPT = util.initParam('CONFIG_SKIP_GITCRYPT'); // This is for backward compatibility RUNTIME_JSON_FILENAME = util.initParam('NODE_CONFIG_RUNTIME_JSON', Path.join(CONFIG_DIR , 'runtime.json') ); // Determine the host name from the OS module, $HOST, or $HOSTNAME // Remove any . appendages, and default to null if not set try { var hostName = HOST || HOSTNAME; if (!hostName) { var OS = require('os'); hostName = OS.hostname(); } } catch (e) { hostName = ''; } // Store the hostname that won. env.HOSTNAME = hostName; // Read each file in turn var baseNames = ['default'].concat(NODE_ENV); // #236: Also add full hostname when they are different. if ( hostName ) { var firstDomain = hostName.split('.')[0]; NODE_ENV.forEach(function(env) { // Backward compatibility baseNames.push(firstDomain, firstDomain + '-' + env); // Add full hostname when it is not the same if ( hostName != firstDomain ) { baseNames.push(hostName, hostName + '-' + env); } }); } NODE_ENV.forEach(function(env) { baseNames.push('local', 'local-' + env); }); var extNames = ['js', 'ts', 'json', 'json5', 'hjson', 'toml', 'coffee', 'iced', 'yaml', 'yml', 'cson', 'properties', 'xml']; baseNames.forEach(function(baseName) { extNames.forEach(function(extName) { // Try merging the config object into this object var fullFilename = Path.join(CONFIG_DIR , baseName + '.' + extName); var configObj = util.parseFile(fullFilename); if (configObj) { util.extendDeep(config, configObj); } // See if the application instance file is available if (APP_INSTANCE) { fullFilename = Path.join(CONFIG_DIR, baseName + '-' + APP_INSTANCE + '.' + extName); configObj = util.parseFile(fullFilename); if (configObj) { util.extendDeep(config, configObj); } } }); }); // Override configurations from the $NODE_CONFIG environment variable // NODE_CONFIG only applies to the base config if (!configDir) { var envConfig = {}; if (process.env.NODE_CONFIG) { try { envConfig = JSON.parse(process.env.NODE_CONFIG); } catch(e) { console.error('The $NODE_CONFIG environment variable is malformed JSON'); } util.extendDeep(config, envConfig); configSources.push({ name: "$NODE_CONFIG", parsed: envConfig, }); } // Override configurations from the --NODE_CONFIG command line var cmdLineConfig = util.getCmdLineArg('NODE_CONFIG'); if (cmdLineConfig) { try { cmdLineConfig = JSON.parse(cmdLineConfig); } catch(e) { console.error('The --NODE_CONFIG={json} command line argument is malformed JSON'); } util.extendDeep(config, cmdLineConfig); configSources.push({ name: "--NODE_CONFIG argument", parsed: cmdLineConfig, }); } // Place the mixed NODE_CONFIG into the environment env['NODE_CONFIG'] = JSON.stringify(util.extendDeep(envConfig, cmdLineConfig, {})); } // Override with environment variables if there is a custom-environment-variables.EXT mapping file var customEnvVars = util.getCustomEnvVars(CONFIG_DIR, extNames); util.extendDeep(config, customEnvVars); // Extend the original config with the contents of runtime.json (backwards compatibility) var runtimeJson = util.parseFile(RUNTIME_JSON_FILENAME) || {}; util.extendDeep(config, runtimeJson); util.resolveDeferredConfigs(config); // Return the configuration object return config; }; // Using basic recursion pattern, find all the deferred values and resolve them. util.resolveDeferredConfigs = function (config) { var completeConfig = config; function _iterate (prop) { // We put the properties we are going to look it in an array to keep the order predictable var propsToSort = []; // First step is to put the properties of interest in an array for (var property in prop) { if (prop.hasOwnProperty(property) && prop[property] != null) { propsToSort.push(property); } } // Second step is to iterate of the elements in a predictable (sorted) order propsToSort.sort().forEach(function (property) { if (prop[property].constructor == Object) { _iterate(prop[property]); } else if (prop[property].constructor == Array) { for (var i = 0; i < prop[property].length; i++) { _iterate(prop[property][i]); } } else { if (prop[property] instanceof DeferredConfig ) { prop[property]= prop[property].resolve.call(completeConfig,completeConfig, prop[property]._original); } else { // Nothing to do. Keep the property how it is. } } }); } _iterate(config); } /** * Parse and return the specified configuration file. * * If the file exists in the application config directory, it will * parse and return it as a JavaScript object. * * The file extension determines the parser to use. * * .js = File to run that has a module.exports containing the config object * .coffee = File to run that has a module.exports with coffee-script containing the config object * .iced = File to run that has a module.exports with iced-coffee-script containing the config object * All other supported file types (yaml, toml, json, cson, hjson, json5, properties, xml) * are parsed with util.parseString. * * If the file doesn't exist, a null will be returned. If the file can't be * parsed, an exception will be thrown. * * This method performs synchronous file operations, and should not be called * after synchronous module loading. * * @protected * @method parseFile * @param fullFilename {string} The full file path and name * @return {configObject} The configuration object parsed from the file */ util.parseFile = function(fullFilename) { // Initialize var t = this, extension = fullFilename.substr(fullFilename.lastIndexOf('.') + 1), configObject = null, fileContent = null, stat = null; // Return null if the file doesn't exist. // Note that all methods here are the Sync versions. This is appropriate during // module loading (which is a synchronous operation), but not thereafter. try { stat = FileSystem.statSync(fullFilename); if (!stat || stat.size < 1) { return null; } } catch (e1) { return null } // Try loading the file. try { fileContent = FileSystem.readFileSync(fullFilename, 'UTF-8'); fileContent = fileContent.replace(/^\uFEFF/, ''); } catch (e2) { throw new Error('Config file ' + fullFilename + ' cannot be read'); } // Parse the file based on extension try { // skip if it's a gitcrypt file and CONFIG_SKIP_GITCRYPT is true if (CONFIG_SKIP_GITCRYPT) { if (gitCryptTestRegex.test(fileContent)) { console.error('WARNING: ' + fullFilename + ' is a git-crypt file and CONFIG_SKIP_GITCRYPT is set. skipping.'); return null; } } if (extension === 'js') { // Use the built-in parser for .js files configObject = require(fullFilename); } else if (extension === 'ts') { if (!require.extensions['.ts']) { require(TS_DEP).register({ lazy: true, compilerOptions: { allowJs: true, } }); } // Because of ES6 modules usage, `default` is treated as named export (like any other) // Therefore config is a value of `default` key. configObject = require(fullFilename).default; } else if (extension === 'coffee') { // .coffee files can be loaded with either coffee-script or iced-coffee-script. // Prefer iced-coffee-script, if it exists. // Lazy load the appropriate extension if (!Coffee) { Coffee = {}; // The following enables iced-coffee-script on .coffee files, if iced-coffee-script is available. // This is commented as per a decision on a pull request. //try { // Coffee = require("iced-coffee-script"); //} //catch (e) { // Coffee = require("coffee-script"); //} try { // Try to load coffeescript Coffee = require(COFFEE_2_DEP); } catch (e) { // If it doesn't exist, try to load it using the deprecated module name Coffee = require(COFFEE_DEP); } // coffee-script >= 1.7.0 requires explicit registration for require() to work if (Coffee.register) { Coffee.register(); } } // Use the built-in parser for .coffee files with coffee-script configObject = require(fullFilename); } else if (extension === 'iced') { Iced = require(ICED_DEP); // coffee-script >= 1.7.0 requires explicit registration for require() to work if (Iced.register) { Iced.register(); } } else { configObject = util.parseString(fileContent, extension); } } catch (e3) { if (gitCryptTestRegex.test(fileContent)) { console.error('ERROR: ' + fullFilename + ' is a git-crypt file and CONFIG_SKIP_GITCRYPT is not set.'); } throw new Error("Cannot parse config file: '" + fullFilename + "': " + e3); } // Keep track of this configuration sources, including empty ones if (typeof configObject === 'object') { configSources.push({ name: fullFilename, original: fileContent, parsed: configObject, }); } return configObject; }; /** * Parse and return the specied string with the specified format. * * The format determines the parser to use. * * json = File is parsed using JSON.parse() * yaml (or yml) = Parsed with a YAML parser * toml = Parsed with a TOML parser * cson = Parsed with a CSON parser * hjson = Parsed with a HJSON parser * json5 = Parsed with a JSON5 parser * properties = Parsed with the 'properties' node package * xml = Parsed with a XML parser * * If the file doesn't exist, a null will be returned. If the file can't be * parsed, an exception will be thrown. * * This method performs synchronous file operations, and should not be called * after synchronous module loading. * * @protected * @method parseString * @param content {string} The full content * @param format {string} The format to be parsed * @return {configObject} The configuration object parsed from the string */ util.parseString = function (content, format) { // Initialize var configObject = null; // Parse the file based on extension if (format === 'yaml' || format === 'yml') { if (!Yaml && !VisionmediaYaml) { // Lazy loading try { // Try to load the better js-yaml module Yaml = require(JS_YAML_DEP); } catch (e) { try { // If it doesn't exist, load the fallback visionmedia yaml module. VisionmediaYaml = require(YAML_DEP); } catch (e) { } } } if (Yaml) { configObject = Yaml.load(content); } else if (VisionmediaYaml) { // The yaml library doesn't like strings that have newlines but don't // end in a newline: https://github.com/visionmedia/js-yaml/issues/issue/13 content += '\n'; configObject = VisionmediaYaml.eval(util.stripYamlComments(content)); } else { console.error("No YAML parser loaded. Suggest adding js-yaml dependency to your package.json file.") } } else if (format === 'json') { try { configObject = JSON.parse(content); } catch (e) { // All JS Style comments will begin with /, so all JSON parse errors that // encountered a syntax error will complain about this character. if (e.name !== 'SyntaxError' || e.message.indexOf('Unexpected token /') !== 0) { throw e; } if (!JSON5) { JSON5 = require(JSON5_DEP); } configObject = JSON5.parse(content); } } else if (format === 'json5') { if (!JSON5) { JSON5 = require(JSON5_DEP); } configObject = JSON5.parse(content); } else if (format === 'hjson') { if (!HJSON) { HJSON = require(HJSON_DEP); } configObject = HJSON.parse(content); } else if (format === 'toml') { if(!TOML) { TOML = require(TOML_DEP); } configObject = TOML.parse(content); } else if (format === 'cson') { if (!CSON) { CSON = require(CSON_DEP); } // Allow comments in CSON files if (typeof CSON.parseSync === 'function') { configObject = CSON.parseSync(util.stripComments(content)); } else { configObject = CSON.parse(util.stripComments(content)); } } else if (format === 'properties') { if (!PPARSER) { PPARSER = require(PPARSER_DEP); } configObject = PPARSER.parse(content, { namespaces: true, variables: true, sections: true }); } else if (format === 'xml') { if (!XML) { XML = require(XML_DEP); } var x2js = new XML(); configObject = x2js.xml2js(content); var rootKeys = Object.keys(configObject); if(rootKeys.length == 1) { configObject = configObject[rootKeys[0]]; } } return configObject; }; /** * Attach the Config class prototype to all config objects recursively. * *

* This allows you to do anything with CONFIG sub-objects as you can do with * the top-level CONFIG object. It's so you can do this: *

* *
 *   var CUST_CONFIG = require('config').Customer;
 *   CUST_CONFIG.get(...)
 * 
* * @protected * @method attachProtoDeep * @param toObject * @param depth * @return toObject */ util.attachProtoDeep = function(toObject, depth) { if (toObject instanceof RawConfig) { return toObject; } // Recursion detection var t = this; depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth); if (depth < 0) { return toObject; } // Adding Config.prototype methods directly to toObject as hidden properties // because adding to toObject.__proto__ exposes the function in toObject for (var fnName in Config.prototype) { if (!toObject[fnName]) { util.makeHidden(toObject, fnName, Config.prototype[fnName]); } } // Add prototypes to sub-objects for (var prop in toObject) { if (util.isObject(toObject[prop])) { util.attachProtoDeep(toObject[prop], depth - 1); } } // Return the original object return toObject; }; /** * Return a deep copy of the specified object. * * This returns a new object with all elements copied from the specified * object. Deep copies are made of objects and arrays so you can do anything * with the returned object without affecting the input object. * * @protected * @method cloneDeep * @param parent {object} The original object to copy from * @param [depth=20] {Integer} Maximum depth (default 20) * @return {object} A new object with the elements copied from the copyFrom object * * This method is copied from https://github.com/pvorb/node-clone/blob/17eea36140d61d97a9954c53417d0e04a00525d9/clone.js * * Copyright © 2011-2014 Paul Vorbach and contributors. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the “Software”), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: The above copyright notice and this permission * notice shall be included in all copies or substantial portions of the Software. */ util.cloneDeep = function cloneDeep(parent, depth, circular, prototype) { // maintain two arrays for circular references, where corresponding parents // and children have the same index var allParents = []; var allChildren = []; var useBuffer = typeof Buffer != 'undefined'; if (typeof circular === 'undefined') circular = true; if (typeof depth === 'undefined') depth = 20; // recurse this function so we don't reset allParents and allChildren function _clone(parent, depth) { // cloning null always returns null if (parent === null) return null; if (depth === 0) return parent; var child; if (typeof parent != 'object') { return parent; } if (Utils.isArray(parent)) { child = []; } else if (Utils.isRegExp(parent)) { child = new RegExp(parent.source, util.getRegExpFlags(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (Utils.isDate(parent)) { child = new Date(parent.getTime()); } else if (useBuffer && Buffer.isBuffer(parent)) { child = new Buffer(parent.length); parent.copy(child); return child; } else { if (typeof prototype === 'undefined') child = Object.create(Object.getPrototypeOf(parent)); else child = Object.create(prototype); } if (circular) { var index = allParents.indexOf(parent); if (index != -1) { return allChildren[index]; } allParents.push(parent); allChildren.push(child); } for (var i in parent) { var propDescriptor = Object.getOwnPropertyDescriptor(parent,i); var hasGetter = ((propDescriptor !== undefined) && (propDescriptor.get !== undefined)); if (hasGetter){ Object.defineProperty(child,i,propDescriptor); } else { child[i] = _clone(parent[i], depth - 1); } } return child; } return _clone(parent, depth); }; /** * Set objects given a path as a string list * * @protected * @method setPath * @param object {object} - Object to set the property on * @param path {array[string]} - Array path to the property * @param value {*} - value to set, ignoring null */ util.setPath = function (object, path, value) { var nextKey = null; if (value === null || path.length === 0) { return; } else if (path.length === 1) { // no more keys to make, so set the value object[path.shift()] = value; } else { nextKey = path.shift(); if (!object.hasOwnProperty(nextKey)) { object[nextKey] = {}; } util.setPath(object[nextKey], path, value); } }; /** * Create a new object patterned after substitutionMap, where: * 1. Terminal string values in substitutionMap are used as keys * 2. To look up values in a key-value store, variables * 3. And parent keys are created as necessary to retain the structure of substitutionMap. * * @protected * @method substituteDeep * @param substitionMap {object} - an object whose terminal (non-subobject) values are strings * @param variables {object[string:value]} - usually process.env, a flat object used to transform * terminal values in a copy of substititionMap. * @returns {object} - deep copy of substitutionMap with only those paths whose terminal values * corresponded to a key in `variables` */ util.substituteDeep = function (substitutionMap, variables) { var result = {}; function _substituteVars(map, vars, pathTo) { for (var prop in map) { var value = map[prop]; if (typeof(value) === 'string') { // We found a leaf variable name if (vars[value]) { // if the vars provide a value set the value in the result map util.setPath(result, pathTo.concat(prop), vars[value]); } } else if (util.isObject(value)) { // work on the subtree, giving it a clone of the pathTo if('__name' in value && '__format' in value && vars[value.__name]) { var parsedValue = util.parseString(vars[value.__name], value.__format); util.setPath(result, pathTo.concat(prop), parsedValue); } else { _substituteVars(value, vars, pathTo.concat(prop)); } } else { msg = "Illegal key type for substitution map at " + pathTo.join('.') + ': ' + typeof(value); throw Error(msg); } } } _substituteVars(substitutionMap, variables, []); return result; }; /* Map environment variables into the configuration if a mapping file, * `custom-environment-variables.EXT` exists. * * @protected * @method getCustomEnvVars * @param CONFIG_DIR {string} - the passsed configuration directory * @param extNames {Array[string]} - acceptable configuration file extension names. * @returns {object} - mapped environment variables or {} if there are none */ util.getCustomEnvVars = function (CONFIG_DIR, extNames) { var result = {}; extNames.forEach(function (extName) { var fullFilename = Path.join(CONFIG_DIR , 'custom-environment-variables' + '.' + extName); var configObj = util.parseFile(fullFilename); if (configObj) { var environmentSubstitutions = util.substituteDeep(configObj, process.env); util.extendDeep(result, environmentSubstitutions); } }); return result; }; /** * Return true if two objects have equal contents. * * @protected * @method equalsDeep * @param object1 {object} The object to compare from * @param object2 {object} The object to compare with * @param depth {integer} An optional depth to prevent recursion. Default: 20. * @return {boolean} True if both objects have equivalent contents */ util.equalsDeep = function(object1, object2, depth) { // Recursion detection var t = this; depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth); if (depth < 0) { return {}; } // Fast comparisons if (!object1 || !object2) { return false; } if (object1 === object2) { return true; } if (typeof(object1) != 'object' || typeof(object2) != 'object') { return false; } // They must have the same keys. If their length isn't the same // then they're not equal. If the keys aren't the same, the value // comparisons will fail. if (Object.keys(object1).length != Object.keys(object2).length) { return false; } // Compare the values for (var prop in object1) { // Call recursively if an object or array if (object1[prop] && typeof(object1[prop]) === 'object') { if (!util.equalsDeep(object1[prop], object2[prop], depth - 1)) { return false; } } else { if (object1[prop] !== object2[prop]) { return false; } } } // Test passed. return true; }; /** * Returns an object containing all elements that differ between two objects. *

* This method was designed to be used to create the runtime.json file * contents, but can be used to get the diffs between any two Javascript objects. *

*

* It works best when object2 originated by deep copying object1, then * changes were made to object2, and you want an object that would give you * the changes made to object1 which resulted in object2. *

* * @protected * @method diffDeep * @param object1 {object} The base object to compare to * @param object2 {object} The object to compare with * @param depth {integer} An optional depth to prevent recursion. Default: 20. * @return {object} A differential object, which if extended onto object1 would * result in object2. */ util.diffDeep = function(object1, object2, depth) { // Recursion detection var t = this, diff = {}; depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth); if (depth < 0) { return {}; } // Process each element from object2, adding any element that's different // from object 1. for (var parm in object2) { var value1 = object1[parm]; var value2 = object2[parm]; if (value1 && value2 && util.isObject(value2)) { if (!(util.equalsDeep(value1, value2))) { diff[parm] = util.diffDeep(value1, value2, depth - 1); } } else if (Array.isArray(value1) && Array.isArray(value2)) { if(!util.equalsDeep(value1, value2)) { diff[parm] = value2; } } else if (value1 !== value2){ diff[parm] = value2; } } // Return the diff object return diff; }; /** * Extend an object, and any object it contains. * * This does not replace deep objects, but dives into them * replacing individual elements instead. * * @protected * @method extendDeep * @param mergeInto {object} The object to merge into * @param mergeFrom... {object...} - Any number of objects to merge from * @param depth {integer} An optional depth to prevent recursion. Default: 20. * @return {object} The altered mergeInto object is returned */ util.extendDeep = function(mergeInto) { // Initialize var t = this; var vargs = Array.prototype.slice.call(arguments, 1); var depth = vargs.pop(); if (typeof(depth) != 'number') { vargs.push(depth); depth = DEFAULT_CLONE_DEPTH; } // Recursion detection if (depth < 0) { return mergeInto; } // Cycle through each object to extend vargs.forEach(function(mergeFrom) { // Cycle through each element of the object to merge from for (var prop in mergeFrom) { // save original value in deferred elements var fromIsDeferredFunc = mergeFrom[prop] instanceof DeferredConfig; var isDeferredFunc = mergeInto[prop] instanceof DeferredConfig; if (fromIsDeferredFunc && mergeInto.hasOwnProperty(prop)) { mergeFrom[prop]._original = isDeferredFunc ? mergeInto[prop]._original : mergeInto[prop]; } // Extend recursively if both elements are objects and target is not really a deferred function if (mergeFrom[prop] instanceof Date) { mergeInto[prop] = mergeFrom[prop]; } if (mergeFrom[prop] instanceof RegExp) { mergeInto[prop] = mergeFrom[prop]; } else if (util.isObject(mergeInto[prop]) && util.isObject(mergeFrom[prop]) && !isDeferredFunc) { util.extendDeep(mergeInto[prop], mergeFrom[prop], depth - 1); } // Copy recursively if the mergeFrom element is an object (or array or fn) else if (mergeFrom[prop] && typeof mergeFrom[prop] === 'object') { mergeInto[prop] = util.cloneDeep(mergeFrom[prop], depth -1); } // Copy property descriptor otherwise, preserving accessors else if (Object.getOwnPropertyDescriptor(Object(mergeFrom), prop)){ Object.defineProperty(mergeInto, prop, Object.getOwnPropertyDescriptor(Object(mergeFrom), prop)); } else { mergeInto[prop] = mergeFrom[prop]; } } }); // Chain return mergeInto; }; /** * Strip YAML comments from the string * * The 2.0 yaml parser doesn't allow comment-only or blank lines. Strip them. * * @protected * @method stripYamlComments * @param fileString {string} The string to strip comments from * @return {string} The string with comments stripped. */ util.stripYamlComments = function(fileStr) { // First replace removes comment-only lines // Second replace removes blank lines return fileStr.replace(/^\s*#.*/mg,'').replace(/^\s*[\n|\r]+/mg,''); } /** * Strip all Javascript type comments from the string. * * The string is usually a file loaded from the O/S, containing * newlines and javascript type comments. * * Thanks to James Padolsey, and all who contributed to this implementation. * http://james.padolsey.com/javascript/javascript-comment-removal-revisted/ * * @protected * @method stripComments * @param fileString {string} The string to strip comments from * @param stringRegex {RegExp} Optional regular expression to match strings that * make up the config file * @return {string} The string with comments stripped. */ util.stripComments = function(fileStr, stringRegex) { stringRegex = stringRegex || /(['"])(\\\1|.)+?\1/g; var uid = '_' + +new Date(), primitives = [], primIndex = 0; return ( fileStr /* Remove strings */ .replace(stringRegex, function(match){ primitives[primIndex] = match; return (uid + '') + primIndex++; }) /* Remove Regexes */ .replace(/([^\/])(\/(?!\*|\/)(\\\/|.)+?\/[gim]{0,3})/g, function(match, $1, $2){ primitives[primIndex] = $2; return $1 + (uid + '') + primIndex++; }) /* - Remove single-line comments that contain would-be multi-line delimiters E.g. // Comment /* <-- - Remove multi-line comments that contain would be single-line delimiters E.g. /* // <-- */ .replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '') /* Remove single and multi-line comments, no consideration of inner-contents */ .replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '') /* Remove multi-line comments that have a replaced ending (string/regex) Greedy, so no inner strings/regexes will stop it. */ .replace(RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '') /* Bring back strings & regexes */ .replace(RegExp(uid + '(\\d+)', 'g'), function(match, n){ return primitives[n]; }) ); }; /** * Is the specified argument a regular javascript object? * * The argument is an object if it's a JS object, but not an array. * * @protected * @method isObject * @param arg {*} An argument of any type. * @return {boolean} TRUE if the arg is an object, FALSE if not */ util.isObject = function(obj) { return (obj !== null) && (typeof obj === 'object') && !(Array.isArray(obj)); }; /** *

Initialize a parameter from the command line or process environment

* *

* This method looks for the parameter from the command line in the format * --PARAMETER=VALUE, then from the process environment, then from the * default specified as an argument. *

* * @method initParam * @param paramName {String} Name of the parameter * @param [defaultValue] {Any} Default value of the parameter * @return {Any} The found value, or default value */ util.initParam = function (paramName, defaultValue) { var t = this; // Record and return the value var value = util.getCmdLineArg(paramName) || process.env[paramName] || defaultValue; env[paramName] = value; return value; } /** *

Get Command Line Arguments

* *

* This method allows you to retrieve the value of the specified command line argument. *

* *

* The argument is case sensitive, and must be of the form '--ARG_NAME=value' *

* * @method getCmdLineArg * @param searchFor {String} The argument name to search for * @return {*} false if the argument was not found, the argument value if found */ util.getCmdLineArg = function (searchFor) { var cmdLineArgs = process.argv.slice(2, process.argv.length), argName = '--' + searchFor + '='; for (var argvIt = 0; argvIt < cmdLineArgs.length; argvIt++) { if (cmdLineArgs[argvIt].indexOf(argName) === 0) { return cmdLineArgs[argvIt].substr(argName.length); } } return false; } /** *

Get a Config Environment Variable Value

* *

* This method returns the value of the specified config environment variable, * including any defaults or overrides. *

* * @method getEnv * @param varName {String} The environment variable name * @return {String} The value of the environment variable */ util.getEnv = function (varName) { return env[varName]; } /** * Returns a string of flags for regular expression `re`. * * @param {RegExp} re Regular expression * @returns {string} Flags */ util.getRegExpFlags = function (re) { var flags = ''; re.global && (flags += 'g'); re.ignoreCase && (flags += 'i'); re.multiline && (flags += 'm'); return flags; }; /** * Returns a new deep copy of the current config object, or any part of the config if provided. * * @param {Object} config The part of the config to copy and serialize. Omit this argument to return the entire config. * @returns {Object} The cloned config or part of the config */ util.toObject = function(config) { return JSON.parse(JSON.stringify(config || this)); }; // Run strictness checks on NODE_ENV and NODE_APP_INSTANCE and throw an error if there's a problem. util.runStrictnessChecks = function (config) { var sources = config.util.getConfigSources(); var sourceFilenames = sources.map(function (src) { return Path.basename(src.name); }); NODE_ENV.forEach(function(env) { // Throw an exception if there's no explicit config file for NODE_ENV var anyFilesMatchEnv = sourceFilenames.some(function (filename) { return filename.match(env); }); // development is special-cased because it's the default value if (env && (env !== 'development') && !anyFilesMatchEnv) { _warnOrThrow("NODE_ENV value of '"+env+"' did not match any deployment config file names."); } // Throw if NODE_ENV matches' default' or 'local' if ((env === 'default') || (env === 'local')) { _warnOrThrow("NODE_ENV value of '"+env+"' is ambiguous."); } }); // Throw an exception if there's no explict config file for NODE_APP_INSTANCE var anyFilesMatchInstance = sourceFilenames.some(function (filename) { return filename.match(APP_INSTANCE); }); if (APP_INSTANCE && !anyFilesMatchInstance) { _warnOrThrow("NODE_APP_INSTANCE value of '"+APP_INSTANCE+"' did not match any instance config file names."); } function _warnOrThrow (msg) { var beStrict = process.env.NODE_CONFIG_STRICT_MODE; var prefix = beStrict ? 'FATAL: ' : 'WARNING: '; var seeURL = 'See https://github.com/lorenwest/node-config/wiki/Strict-Mode'; console.error(prefix+msg); console.error(prefix+seeURL); // Accept 1 and true as truthy values. When set via process.env, Node.js casts them to strings. if (["true", "1"].indexOf(beStrict) >= 0) { throw new Error(prefix+msg+' '+seeURL); } } } // Process pre-1.0 utility names var utilWarnings = {}; ['watch', 'setModuleDefaults', 'makeHidden', 'makeImmutable', 'getConfigSources', '_loadFileConfigs', '_parseFile', '_attachProtoDeep', '_cloneDeep', '_equalsDeep', '_diffDeep', '_extendDeep', '_stripYamlComments', '_stripComments', '_isObject', '_initParam', '_getCmdLineArg'].forEach(function(oldName) { // Config.util names don't have underscores var newName = oldName; if (oldName.indexOf('_') === 0) { newName = oldName.substr(1); } // Build the wrapper with warning Config.prototype[oldName] = function(){ // Produce the warning if (!utilWarnings[oldName]) { console.error('WARNING: config.' + oldName + '() is deprecated. Use config.util.' + newName + '() instead.'); console.error('WARNING: See https://github.com/lorenwest/node-config/wiki/Future-Compatibility#upcoming-incompatibilities'); utilWarnings[oldName] = true; } // Forward the call return util[newName].apply(this, arguments); } }); // Instantiate and export the configuration var config = module.exports = new Config(); // Produce warnings if the configuration is empty var showWarnings = !(util.initParam('SUPPRESS_NO_CONFIG_WARNING')); if (showWarnings && Object.keys(config).length === 0) { console.error('WARNING: No configurations found in configuration directory:' +CONFIG_DIR); console.error('WARNING: To disable this warning set SUPPRESS_NO_CONFIG_WARNING in the environment.'); }