Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

config

Package Overview
Dependencies
Maintainers
0
Versions
118
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

config - npm Package Compare versions

Comparing version 0.2.9 to 0.4.0

config/default.js

920

lib/config.js

@@ -1,125 +0,853 @@

/*******************************************************************************
* config.js - Main module for node-config
********************************************************************************
*/
// Dependencies
var deps = require('../deps');
var _ = deps._;
var ext = require('./extensions');
var File = require('fs');
var Yaml = require('yaml');
var FileSystem = require('fs');
// Saved configurations key=moduleName, value=configObj
var savedConfigs = {};
// Static members
var DEFAULT_CLONE_DEPTH = 6,
FILE_WATCHER_INTERVAL = 3000,
CONFIG_PATH = process.cwd() + '/config/',
runtimeJsonFilename = CONFIG_PATH + 'runtime.json',
originalConfig = null, // Not including the runtime.json values
runtimeJson = {}, // Current runtimeJson extensions
isQueuedForPersistence = false;
// Saved configuration files. key=filename, value=configObj
var savedConfigFiles = {};
/**
* <p>Runtime Application Configurations</p>
*
* <p>
* The config module exports a singleton object representing all runtime
* configurations for this application deployment.
* </p>
*
* <p>
* 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.).
* </p>
*
* <p>
* For example, with the following config/default.yaml file:
* </p>
*
* <pre>
* ...
* customer:
* &nbsp;&nbsp;initialCredit: 500
* &nbsp;&nbsp;db:
* &nbsp;&nbsp;&nbsp;&nbsp;name: customer
* &nbsp;&nbsp;&nbsp;&nbsp;port: 5984
* ...
* </pre>
*
* <p>
* The following code loads the customer section into the CONFIG variable:
* <p>
*
* <pre>
* var CONFIG = require('config').customer;
* ...
* newCustomer.creditLimit = CONFIG.initialCredit;
* database.open(CONFIG.db.name, CONFIG.db.port);
* ...
* </pre>
*
* @module config
* @class Config
*/
/*******************************************************************************
* config() - Build a module configuration object
********************************************************************************
* This takes a default configuration object for a module, and folds in values
* from configuration files and values passed in from the command line.
*
* See README.md for more information
*
* Input:
* modName - Module name
* defaultConf - The program default configurations
*
* Output:
* conf - The mixed configuration object
*/
module.exports = function(modName, origConfig) {
/**
* <p>Get the configuration object.</p>
*
* <p>
* The configuration object is a shared singleton object within the applicaiton,
* attained by calling require('config').
* </p>
*
* <p>
* 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:
* </p>
* <pre>
* var CONFIG = require('config');
* </pre>
*
* <p>
* 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:
* </p>
* <pre>
* var CONFIG = require('config').customer;
* or
* var CUSTOMER_CONFIG = require('config').customer;
* </pre>
*
* <script type="text/javascript">
* document.getElementById("showProtected").style.display = "block";
* </script>
*
* @method constructor
* @return CONFIG {object} - The top level configuration object
*/
var Config = function() {
var t = this;
t._loadFileConfigs();
t._watchForConfigFileChanges();
t._persistConfigsOnChange();
};
/**
* <p>Monitor a configuration value for runtime changes.</p>
*
* <p>
* Configuration values can be changed at runtime by the application or by a
* manual change to the config/runtime.json file.
* This method lets you specify a function to run when a configuration
* value changes.
* </p>
*
* <p>
* This was built for monitoring changes to configuration values,
* but it can be used for watching changes to any javascript object.
* </p>
*
* @method watch
* @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.
*/
Config.prototype.watch = function(object, property, handler, depth) {
// Initialize
var mixedConfig =_.cloneDeep(origConfig || {});
var t = object;
var allProperties = property ? [property] : Object.keys(t);
// If no origConfig is given, return an existing config or all configs
if (origConfig == null) {
return (modName ? savedConfigs[modName] : savedConfigs);
// Depth detection
depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
if (depth < 0) {
return;
}
// Mixin configuration files
var argName = '-config';
_.each(process.argv, function(arg, pos){
if (argName == arg && ++pos < process.argv.length) {
// Create hidden properties on the object
function makeHiddenProperty(hiddenProp, initialValue) {
Object.defineProperty(t, hiddenProp, {
value: initialValue,
writable : true,
enumerable : false,
configurable : false
});
}
if (!t.__watchers)
makeHiddenProperty('__watchers', {});
if (!t.__propertyValues)
makeHiddenProperty('__propertyValues', {});
// If the filename is relative, make it relative to the process cwd
var configFile = process.argv[pos];
if (configFile.indexOf('./') == 0 || configFile.indexOf('../') == 0) {
configFile = process.cwd() + '/' + configFile;
}
// Attach watchers to all requested properties
allProperties.forEach(function(prop){
// Get the configuration object from the file
var configObject = savedConfigFiles[configFile];
if (!configObject) {
// Setup the property for watching (first time only)
if (typeof(t.__propertyValues[prop]) == 'undefined') {
// Determine the file type
var fileParts = configFile.split('.');
lastPart = fileParts[fileParts.length - 1].toLowerCase();
var isYAML = (lastPart == 'yaml');
var isJSON = (lastPart == 'json');
var isJS = (!isYAML && !isJSON);
// Don't error re-defining the property if immutable
var descriptor = Object.getOwnPropertyDescriptor(t,prop);
if (descriptor && descriptor.writable === false)
return;
// Load and parse the file into a javascript object
try {
if (isYAML) {
var text = File.readFileSync(configFile).toString();
// 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
text += '\n';
configObject = Yaml.eval(text);
}
else if (isJSON) {
var text = File.readFileSync(configFile).toString();
configObject = JSON.parse(text);
}
else {
configObject = require(configFile);
}
// Remember the object so the file doesn't have to be parsed
// again for the next module.
savedConfigFiles[configFile] = configObject;
// Copy the value to the hidden field, and add the property to watchers
t.__propertyValues[prop] = [t[prop]];
t.__watchers[prop] = [];
// Attach the property watcher
Object.defineProperty(t, prop, {
enumerable : true,
get : function(){
// If more than 1 item is in the values array,
// then we're currently processing watchers.
if (t.__propertyValues[prop].length == 1)
// Current value
return t.__propertyValues[prop][0];
else
// [0] is prior value, [1] is new value being processed
return t.__propertyValues[prop][1];
},
set : function(newValue) {
// Return early if no change
var origValue = t[prop];
if (origValue === newValue)
return;
// Remember the new value, and return if we're in another setter
t.__propertyValues[prop].push(newValue);
if (t.__propertyValues[prop].length > 2)
return;
// Call all watchers for each change requested
var numIterations = 0;
while (t.__propertyValues[prop].length > 1) {
// Detect recursion
if (++numIterations > 20) {
t.__propertyValues[prop] = [origValue];
throw new Error('Recursion detected while setting [' + prop + ']');
}
// Call each watcher for the current values
var oldValue = t.__propertyValues[prop][0];
newValue = t.__propertyValues[prop][1];
t.__watchers[prop].forEach(function(watcher) {
try {
watcher(t, prop, oldValue, newValue);
} catch (e) {
// Log an error and continue with subsequent watchers
console.error("Exception in watcher for " + prop);
}
});
// Done processing this value
t.__propertyValues[prop].splice(0,1);
}
}
catch (e)
{
console.log("\nError parsing config file: " + configFile);
console.log(e.message);
process.exit(1);
}
}
});
// Mixin the module from the configuration file object
_.extendDeep(mixedConfig, configObject[modName]);
} // Done setting up the property for watching (first time)
// Add the watcher to the property
t.__watchers[prop].push(handler);
// Recurs if this is an object...
if (typeof(t[prop]) == 'object') {
Config.prototype.watch(t[prop], null, handler, depth - 1);
}
}); // Done processing each property
// Return the original object - for chaining
return t;
};
/**
* <p>
* Set default configurations for a node.js module.
* </p>
*
* <p>
* This allows module developers to attach their configurations onto the
* default configuration object so they can be configured by the consumers
* of the module.
* </p>
*
* <p>Using the function within your module:</p>
* <pre>
* var CONFIG = require("config");
* CONFIG.setModuleDefaults("MyModule", {
* &nbsp;&nbsp;templateName: "t-50",
* &nbsp;&nbsp;colorScheme: "green"
* });
* <br>
* // Template name may be overridden by application config files
* console.log("Template: " + CONFIG.MyModule.templateName);
* </pre>
*
* <p>
* The above example results in a "MyModule" element of the configuration
* object, containing an object with the specified default values.
* </p>
*
* @method setModuleDefaults
* @param moduleName {string} - Name of your module.
* @param defaultProperties {object} - The default module configuration.
* @return moduleConfig {object} - The module level configuration object.
*/
Config.prototype.setModuleDefaults = function(moduleName, defaultProperties) {
// Copy the properties into a new object
var t = this;
var moduleConfig = t._extendDeep({}, defaultProperties);
// Attach handlers & watchers onto the module config object
t._attachProtoDeep(moduleConfig);
t._persistConfigsOnChange(moduleConfig);
// Extend the module config object with values from originalConfig
if (originalConfig[moduleName]) {
t._extendDeep(moduleConfig, originalConfig[moduleName]);
// Save the mixed module config as the original
originalConfig[moduleName] = moduleConfig;
}
// Extend the module config object with values from runtimeJson
if (runtimeJson[moduleName]) {
t._extendDeep(moduleConfig, runtimeJson[moduleName]);
}
// Attach the object onto the CONFIG object
t[moduleName] = moduleConfig;
};
/**
* <p>Make a configuration property immutable (assuring it cannot be changed
* from the current value).</p>
*
* <p>
* This method was built for disabling runtime changes to configuration values,
* but it can be applied to any javascript object.
* </p>
*
* <p>
* This operation cannot be un-done.
* </p>
*
* @method makeImmutable
* @param object {object} - The object to attach an immutable property into.
* @param property {string} - The name of the property to make immutable.
* @return object {object} - The original object is returned - for chaining.
*/
Config.prototype.makeImmutable = function(object, property) {
// Disable writing, and make sure the property cannot be re-configured.
Object.defineProperty(object, property, {
value : object[property],
writable : false,
configurable: false
});
// Mixin configurations from the command line
// -ModName.FldName FldValue
var modDot = '-' + modName + '.';
_.each(process.argv, function(arg, pos){
return object;
};
// Process if this is in the right format
if (arg.indexOf(modDot) == 0 && ++pos < process.argv.length) {
// Get the field name & value
var fldName = arg.substr(modDot.length);
var fldValue = process.argv[pos];
// Quote the field value unless it's complex
if (fldValue[0] != '{' && fldValue[0] != '[') {
fldValue = "'" + fldValue.replace(/'/,"\\'") + "'";
/**
* Monitor the filesystem for configuration file changes.
*
* <p>
* Runtime configuration changes are made by modifying the runtime.json file.
* This paradigm allows multiple application servers to internally notify
* listeners whenever the configuration changes.
* </p>
*
* <p>
* This method attaches the file watcher onto the runtime.json file, reloading
* the file on change and merging the new values into the CONFIG object.
* </p>
*
* @protected
* @method _watchForConfigFileChanges
*/
Config.prototype._watchForConfigFileChanges = function() {
// Attach the file watcher
var t = this;
FileSystem.watchFile(runtimeJsonFilename, function(curr, prev) {
// no-op if the file hasn't changed
if (curr.mtime == prev.mtime) {
return;
}
// Load the runtime.json file asynchronously.
FileSystem.readFile(runtimeJsonFilename, 'UTF-8', function(err, fileContent) {
// Not much to do on error
if (err) {
console.error("Error loading " + runtimeJsonFilename);
return;
}
// Variable names & values can be complex, so execute them.
eval('mixedConfig.' + fldName + '=' + fldValue);
// Parse the file and mix it in to this config object.
// This notifies listeners
try {
var configObject = JSON.parse(fileContent);
t._extendDeep(t, configObject);
} catch (e) {
console.error("Error parsing " + runtimeJsonFilename, e);
return;
}
});
});
};
/**
* <p>
* Watch the specified object for a change in properties, and persist changes
* to runtime.json when a change is detected.
* </p>
*
* @protected
* @param object {object} - The config object to watch
* @method _persistConfigsOnChange
*/
Config.prototype._persistConfigsOnChange = function(objectToWatch) {
// Watch for configuration value changes
var t = this;
objectToWatch = objectToWatch || t;
t.watch(objectToWatch, null, function(){
// Return early if we're already queued up for persisting
if (isQueuedForPersistence)
return;
// Defer persisting until the next tick. This results in a single
// persist across any number of config changes in a single event cycle.
isQueuedForPersistence = true;
process.nextTick(function(){
// Persist if necessary
var newDiffs = t._diffDeep(originalConfig, t);
if (!t._equalsDeep(newDiffs, runtimeJson)) {
FileSystem.writeFile(runtimeJsonFilename, JSON.stringify(newDiffs, null, 2), 'utf-8', function(error){
if (error)
console.error("Error writing " + runtimeJsonFilename, error);
});
};
// Set up for next time
isQueuedForPersistence = false;
});
});
};
/**
* Load the individual file configurations.
*
* <p>
* This method builds a map of filename to the configuration object defined
* by the file. The search order is:
* </p>
*
* <pre>
* default.EXT
* (hostname).EXT
* (deployment).EXT
* (hostname)-(deployment).EXT
* runtime.json
* </pre>
*
* <p>
* EXT can be yaml, json, or js signifying the file type. yaml is in YAML format,
* json is in strict JSON format, and js is a javascript executable file that is
* require()'d with module.exports being the config object.
* </p>
*
* <p>
* (hostname) is the $HOST environment variable if set, otherwise the
* hostname found from require('os').hostname()
* </p>
*
* <p>
* (deployment) is the deployment type, found in the $NODE_ENV environment
* variable. Defaults to 'development'.
* </p>
*
* <p>
* The runtime.json file contains configuration changes made at runtime either
* manually, or by the application setting a configuration value.
* </p>
*
* @protected
* @method _loadFileConfigs
* @return {this} The configuration object
*/
Config.prototype._loadFileConfigs = function() {
// Initialize
var t = this;
// Singleton
if (originalConfig)
return t;
// Determine the host name from the OS module, $HOST, or $HOSTNAME
// Remove any . appendages, and default to null if not set
try {
var OS = require('os');
var hostName = OS.hostname();
} catch (e) {
hostName = process.env.HOST || process.env.HOSTNAME;
}
hostName = hostName ? hostName.split('.')[0] : null;
// Get the deployment type from NODE_ENV
var deployment = process.env.NODE_ENV || 'development';
// Read each file in turn
var baseNames = ['default', hostName, deployment, hostName + '-' + deployment];
var extNames = ['js', 'json', 'yaml'];
baseNames.forEach(function(baseName) {
extNames.forEach(function(extName) {
// Try merging the config object into this object
var fullFilename = CONFIG_PATH + baseName + '.' + extName;
var configObj = t._parseFile(fullFilename);
if (configObj) {
t._extendDeep(t, configObj);
}
});
});
// Remember the original configuration
originalConfig = t._cloneDeep(t);
// Extend the original config with any prior runtime.json diffs
runtimeJson = t._parseFile(runtimeJsonFilename);
if (runtimeJson) {
t._extendDeep(t, runtimeJson);
}
// Attach the config.prototype to all sub-objects.
t._attachProtoDeep(t);
// Return the configuration object
return t;
};
/**
* 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
* .json = File is parsed using JSON.parse()
* .yaml = Parsed with a YAML parser
*
* If the file doesn't exist, a null will be returned.
*
* If the file can't be parsed, an exception will be thrown.
*
* @protected
* @method _parseFile
* @param fullFilename {string} The full file path and name
* @return {configObject} The configuration object parsed from the file
*/
Config.prototype._parseFile = function(fullFilename) {
// Initialize
var extension = fullFilename.substr(fullFilename.lastIndexOf('.') + 1),
configObject = null,
fileContent = null;
// Return null if the file doesn't exist.
// Note that all methods here are the Sync versions. This allows the
// config package to follow the same calling semantics as require('filename')
// which is also synchronous.
try {
var 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');
}
catch (e2) {
throw new Error('Config file ' + fullFilename + ' cannot be read');
}
try {
if (extension == 'yaml') {
// 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
fileContent += '\n';
configObject = Yaml.eval(fileContent);
}
else if (extension == 'json') {
configObject = JSON.parse(fileContent);
}
else if (extension == 'js') {
configObject = require(fullFilename);
}
}
catch (e3) {
throw new Error("Cannot parse config file: '" + fullFilename + "': " + e3);
}
return configObject;
};
/**
* Attach the Config class prototype to all config objects recursively.
*
* <p>
* 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:
* </p>
*
* <pre>
* var CUST_CONFIG = require('config').Customer;
* CUST_CONFIG.watch(...)
* </pre>
*
* @protected
* @method _attachProtoDeep
* @param toObject
* @param depth
* @return toObject
*/
Config.prototype._attachProtoDeep = function(toObject, depth) {
// Recursion detection
var t = this;
depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
if (depth < 0) {
return;
}
// Attach the prototype to this object
toObject.__proto__ = Config.prototype;
// Cycle through each element
for (var prop in toObject) {
// Call recursively if an object
if (t._isObject(toObject[prop])) {
t._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 copyFrom {object} The original object to copy from
* @param depth {integer} An optional depth to prevent recursion. Default: 20.
* @return {object} A new object with the elements copied from the copyFrom object
*/
Config.prototype._cloneDeep = function(obj, depth) {
// Recursion detection
var t = this;
depth = (depth === null ? DEFAULT_CLONE_DEPTH : depth);
if (depth < 0) {
return {};
}
// Create the copy of the correct type
var copy = Array.isArray(obj) ? [] : {};
// Cycle through each element
for (var prop in obj) {
// Call recursively if an object or array
if (Array.isArray(obj[prop]) || t._isObject(obj[prop])) {
copy[prop] = t._cloneDeep(obj[prop], depth - 1);
}
else {
copy[prop] = obj[prop];
}
}
// Return the copied object
return copy;
};
/**
* 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
*/
Config.prototype._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 (typeof(object1[prop] == 'object')) {
if (!t._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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*
* @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.
*/
Config.prototype._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 (typeof(value2) == 'object') {
if (!(t._equalsDeep(value1, value2))) {
diff[parm] = t._diffDeep(value1, value2, depth - 1);
}
}
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
*/
Config.prototype._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) {
// Extend recursively if both elements are objects
if (t._isObject(mergeInto[prop]) && t._isObject(mergeFrom[prop])) {
t._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] = t._cloneDeep(mergeFrom[prop], depth - 1);
}
// Simple assignment otherwise
else {
mergeInto[prop] = mergeFrom[prop];
}
}
});
// Remember the configuration, and return it
return savedConfigs[modName] = mixedConfig;
// Chain
return mergeInto;
};
/**
* 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 {MIXED} An argument of any type.
* @return {boolean} TRUE if the arg is an object, FALSE if not
*/
Config.prototype._isObject = function(obj) {
if (Array.isArray(obj)) {
return false;
}
return typeof obj == 'object';
};
// The module exports a singleton instance of the Config class so the
// instance is immediately available on require(), and the prototype methods
// aren't a part of the object namespace when inspected.
module.exports = new Config();

14

package.json
{
"name": "config",
"version": "0.2.9",
"version": "0.4.0",
"main": "./lib/config.js",
"description": "Runtime configuration for node.js deployment",
"description": "Configuration control for production node deployments",
"modules" : { "test" : "./test" },

@@ -10,11 +10,9 @@ "author": "Loren West <open_source@lorenwest.com>",

"dependencies": {
"underscore" : "1.1.4",
"vows" : ">=0.5.3",
"eyes" : ">=0.1.6",
"yaml" : ">=0.1.1"
"yaml" : "0.1.x",
"vows" : "0.5.x"
},
"engines": {"node": ">=0.2.4"},
"engines": {"node": ">0.4.x"},
"scripts": {
"test" : "vows test/*.js --spec"
"test" : "./node_modules/vows/bin/vows test/*.js --spec"
}
}
node-config
===========
Runtime configuration for node.js deployment
Configuration control for production node deployments

@@ -9,154 +9,62 @@ Introduction

node-config lets you apply a consistent pattern to module development
making it easy to work with deployment configuration parameters.
Node-config is a configuration system for Node.js application server
deployments. It lets you define a default set of application parameters,
and tune them for different runtime environments (development, qa,
staging, production, etc.).
As a module developer, node-config lets you define your parameters,
then gets out of your way.
Parameters defined by node-config can be monitored and tuned at runtime
without bouncing your production servers.
When running applications following this pattern, node-config lets you
easily discover and set deployment-specific configuration parameters.
Online documentation is available at <http://lorenwest.github.com/node-config/latest>
Synopsis
--------
Quick Start
-----------
Configurations are defined at the top of your module. The following example
is for a *Customers* module:
**In your project directory, install and verify using npm:**
// Configuration parameters and default values
var config = require('config')('Customers', {
dbHost: 'localhost',
dbPort: 5984,
dbName: 'customers',
syncFrequency: 60 // Minutes between synchronizations
});
my-project$ npm install config
my-project$ npm test config
This gives you a *config* variable for your module, along with default values
to use during development.
**Edit the default configuration file (.js, .json, or .yaml):**
Use the *config* object anywhere in your module:
my-project$ mkdir config
my-project$ vi config/default.yaml
// Connect to the database
var dbConn = db.connect(config.dbHost, config.dbPort);
(example default.yaml file):
When running the application, you can specify module configuration overrides
on the command line, or in a configuration file.
Customer:
dbHost: localhost
dbPort: 5984
dbName: customers
For example, the above *dbHost* value of the *Customers* module can be
overridden on the command line, or in a config file.
**Edit the production configuration file:**
Parameters specified on the command line:
$ node billing.js -Customers.dbHost smokin-db
my-project$ vi config/production.yaml
Parameters specified in a configuration file:
$ node billing.js -config ./smokeTest.js
(example production.yaml file):
Any number of command line parameters and/or config files can be specified when
running the application.
Customer:
dbHost: prod-db-server
Installation & Testing
----------------------
**Use the configuration in your code:**
Node-config installs with *npm* and comes with an extensive suite of tests to
make sure it performs well in your deployment environment.
var CONFIG = require('config').Customer;
...
db.connect(CONFIG.dbHost, CONFIG.dbPort, CONFIG.dbName);
To install and test node-config:
$ npm install config
$ npm test config
**Start your application server:**
my-project$ export NODE_ENV=production
my-project$ node app.js
How It Works
------------
Running in this configuration, CONFIG.dbPort and CONFIG.dbName
will come from the `default.yaml` file, and CONFIG.dbHost will
come from the `production.yaml` file.
When developing a module, use the following pattern for defining parameters
and default values:
// Customers.js - Customer management utilities
// Configuration parameters and default values
var config = require('config')('Customers', {
dbHost: 'localhost',
dbPort: 5984,
dbName: 'customers',
syncFrequency: 60 // Minutes between synchronizations
});
When the application runs, node-config extends these default values with
overrides from configuration files, followed by command line overrides,
in the order they're specified on the command line.
Configuration Files
-------------------
Configuration files let you define application deployment configurations in
one place.
A configuration file is a JavaScript module that exports a single configuration
object containing the modules, parameters, and values you want to override for
your application.
The format of the object is best described by example *smokeTest.js*:
// Configuration overrides for smoke testing
module.exports = {
'mod-sync': {
remoteHost: 'smokin-sync',
remotePort: 5984
},
Customers: {
dbHost: 'smokin-db',
syncFrequency: 1
}
};
When running the app, you can specify this config file and additional
parameters at the same time:
$ node billing.js -config smokeTest.js -Customers.dbPort 5985
This results in a *config* object in the *Customers* module with these values:
{
dbHost: 'smokin-db',
dbPort: 5985,
dbName: 'customers',
syncFrequency: 1
}
Advanced Usage
--------------
**Programmatic configuration** If you'd rather specify configuration files
and parameter overrides in your application, just add the command line
arguments programatically before the module is loaded.
// Always use port 5994 for the Customers service
process.argv.push('-Customers.dbPort', 5994);
require('Customers');
**Configuration discovery** You can retrieve the current configuration for
any module by omitting the second parameter, or all configurations by
omitting all parameters to config.
// Load the Customer module parameters
var custConfig = require('config')('Customers');
// Load all application parameters
var allConfigs = require('config')();
**Complex command line variables** Variable names and values passed on through
the command line can be strings, objects, or arrays.
$ node billing.js -Customers.custTemplate.region North
$ node billing.js -Customers.mailings[1] WelcomeMailing
$ node billing.js -Customers.mailingsSent [3,4,6]
$ node billing.js -Customers.homeOffice '{street:"4778 S. Main"}'
See Also
--------
The node-monitor project is a good example of a module that makes use of
the node-config module. It's also a pretty good way to monitor your running
node.js application.
[node-config] - Online documentation<br>
[node-monitor] - Monitor your running node applications

@@ -170,2 +78,5 @@ License

Copyright (c) 2010 Loren West
Copyright (c) 2011 Loren West
[node-config]: http://lorenwest.github.com/node-config/latest
[node-monitor]: http://lorenwest.github.com/node-monitor/latest

@@ -1,198 +0,172 @@

/*******************************************************************************
* config-test.js - Test for the node-config library
********************************************************************************
*/
// Hardcode $NODE_ENV=test for testing
process.env.NODE_ENV='test';
// Dependencies
var deps = require('../deps');
var _ = deps._;
var vows = deps.vows;
var assert = deps.assert;
var config = require('../lib/config');
var vows = require('vows');
assert = require('assert'),
CONFIG = require('../lib/config'),
FileSystem = require('fs'),
originalWatchedValue = CONFIG.watchThisValue,
newWatchedValue = Math.floor(Math.random() * 100000);
// Module default parameters
var defaultParms = {
dbHost: 'localhost',
dbPort: 5984,
dbName: 'customers',
custTemplate: {
credit: 200,
region: 'Central',
mailings: ['intro', 'month1']
}
};
// These tests require the directory to be the root of the node-config project
process.chdir(__dirname + '/..');
var CONFIG_PATH = process.cwd() + '/config/',
runtimeJsonFilename = CONFIG_PATH + 'runtime.json';
/*******************************************************************************
* ConfigTest
********************************************************************************
*/
/**
* <p>Unit tests for the node-config library. To run type:</p>
* <pre>npm test config</pre>
*
* @class ConfigTest
*/
exports.ConfigTest = vows.describe('Test suite for node-config').addBatch({
'Library initialization': {
'Config library is available': function() {
assert.isFunction(config);
assert.isObject(CONFIG);
},
'Config utils are included with the library': function() {
// Normal underscore + config extensions
assert.isFunction(_);
assert.isFunction(_.cloneDeep);
'Config extensions are included with the library': function() {
assert.isFunction(CONFIG._cloneDeep);
}
},
'Configuration Tests': {
'Configuration file Tests': {
topic: function() {
// Remember the original command line argument values
orig = {argv:process.argv};
return orig;
return CONFIG;
},
'Default configuration is correct': function() {
process.argv = [];
var conf = config('Customers', defaultParms);
var shouldBe = {
dbHost: 'localhost',
dbPort: 5984,
dbName: 'customers',
custTemplate: {
credit: 200,
region: 'Central',
mailings: ['intro', 'month1']
}
};
assert.deepEqual(conf, shouldBe);
'Loading configurations from a JS module is correct': function() {
assert.equal(CONFIG.Customers.dbHost, 'base');
assert.equal(CONFIG.TestModule.parm1, 'value1');
},
'Alpha configuration was mixed in': function() {
process.argv = ['arg1', '-config', './config/alpha.js'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbHost:"alpha",
dbPort:5999
});
assert.deepEqual(conf, shouldBe);
'Loading configurations from a JSON file is correct': function() {
assert.equal(CONFIG.AnotherModule.parm1, 'value1');
},
'Multiple configurations can be mixed in': function() {
process.argv = ['-config', './config/base.js', 'arg1',
'-config', './config/alpha.js', 'arg2'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbName:'base_customers',
dbHost:"alpha",
dbPort:5999
});
assert.deepEqual(conf, shouldBe);
'Loading configurations from a YAML file is correct': function() {
assert.equal(CONFIG.AnotherModule.parm2, 'value2');
},
'Command line configurations can specified': function() {
process.argv = ['-Customers.dbName', 'cmdLineName',
"-Customers.newArg", "Beth's"];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbName:'cmdLineName',
newArg:"Beth's"
});
assert.deepEqual(conf, shouldBe);
'Loading prior runtime.json configurations is correct': function() {
assert.equal(CONFIG.Customers.dbName, 'override_from_runtime_json');
}
},
'Assuring a configuration can be made immutable': {
topic: function() {
CONFIG.makeImmutable(CONFIG.TestModule, 'parm1');
CONFIG.TestModule.parm1 = "setToThis";
return CONFIG.TestModule.parm1;
},
'Command line configurations override file configurations': function() {
process.argv = ['-config', './config/base.js', 'arg1',
'-Customers.dbName', 'cmdLineName'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbHost:'base',
dbName:'cmdLineName'
});
assert.deepEqual(conf, shouldBe);
'The makeImmutable() method is available': function() {
assert.isFunction(CONFIG.makeImmutable);
},
'Configurations can be programmatically extended': function() {
process.argv = ['arg1', '-config', './config/production'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbHost:'production',
dbName:'base_customers',
dbPort:4456,
custTemplate:{region:"North"}
});
assert.deepEqual(conf, shouldBe);
'Correctly unable to change an immutable configuration': function(value) {
assert.isTrue(value != "setToThis");
},
'Configurations can be programmatically extended': function() {
process.argv = ['arg1', '-config', './config/production'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbHost:'production',
dbName:'base_customers',
dbPort:4456,
custTemplate:{region:"North"}
'Left the original value intact after attempting the change': function(value) {
assert.equal(value, "value1");
}
},
'Configuration for module developers': {
topic: function() {
// Set some parameters for the test module
CONFIG.setModuleDefaults("TestModule", {
parm1: 1000, parm2: 2000
});
assert.deepEqual(conf, shouldBe);
return CONFIG;
},
'Command line variable names can be complex': function() {
process.argv = ['-Customers.custTemplate["region"]', 'Northeast'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
custTemplate:{region:"Northeast"}
'The setModuleDefaults() method is available': function() {
assert.isFunction(CONFIG.setModuleDefaults);
},
'The module config is in the CONFIG object': function(config) {
assert.isTrue(typeof(config.TestModule) != "undefined");
},
'Local configurations are mixed in': function(config) {
assert.equal(config.TestModule.parm1, "value1");
},
'Defaults remain intact unless overridden': function(config) {
assert.equal(config.TestModule.parm2, 2000);
}
},
'Change Notification Tests': {
topic: function() {
// Attach this topic as a watcher
var cb = this.callback;
CONFIG.watch(CONFIG, null, function(obj, prop, oldValue, newValue){
cb(null, {obj:obj, prop:prop, oldValue:oldValue, newValue:newValue});
});
assert.deepEqual(conf, shouldBe);
// Write the new watched value out to the runtime.json file
CONFIG.watchThisValue = newWatchedValue;
},
'Command line values can be complex': function() {
process.argv = ['-Customers.custTemplate["mailings"][1]', '{name:"intro",mailed:"Y"}'];
var conf = config('Customers', defaultParms);
var shouldBe = _.cloneDeep(defaultParms);
shouldBe.custTemplate.mailings[1] = {name:"intro", mailed:"Y"};
assert.deepEqual(conf, shouldBe);
'The watch() method is available': function() {
assert.isFunction(CONFIG.watch);
},
'Specific configurations are discoverable': function() {
// Read the current Customers parameters
var conf = config('Customers');
var shouldBe = _.cloneDeep(defaultParms);
shouldBe.custTemplate.mailings[1] = {name:"intro", mailed:"Y"};
assert.deepEqual(conf, shouldBe);
'The change handler callback was fired': function(err, obj) {
assert.isTrue(true);
},
'All configurations are discoverable': function() {
var allConfs = config();
assert.isObject(allConfs);
assert.isObject(allConfs.Customers);
var conf = allConfs.Customers;
var shouldBe = _.cloneDeep(defaultParms);
shouldBe.custTemplate.mailings[1] = {name:"intro", mailed:"Y"};
assert.deepEqual(conf, shouldBe);
'And it was called on the correct object': function(err, obj) {
assert.isTrue(obj.obj === CONFIG);
},
'JSON configuration files can be loaded': function() {
process.argv = ['-config', './config/base.json', 'arg1',
'-config', './config/alpha.js', 'arg2'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbName:'base_customers',
dbHost:"alpha",
dbPort:5999
});
assert.deepEqual(conf, shouldBe);
'And it was called with the correct parameter': function(err, obj) {
assert.equal(obj.prop, 'watchThisValue');
},
'And it has the correct prior value': function(err, obj) {
assert.equal(obj.oldValue, originalWatchedValue);
},
'And it has the correct new value': function(err, obj) {
assert.equal(obj.newValue, newWatchedValue);
},
'YAML configuration files can be loaded': function() {
process.argv = ['-config', './config/base.yaml', 'arg1',
'-config', './config/alpha.js', 'arg2'];
var conf = config('Customers', defaultParms);
var shouldBe = _.extendDeep({}, defaultParms, {
dbName:'base_customers',
dbHost:"alpha",
dbPort:5999
});
assert.deepEqual(conf, shouldBe);
'And the config value was correctly set': function(err, obj) {
assert.equal(CONFIG.watchThisValue, newWatchedValue);
},
'Resetting command line args': function(orig) {
process.argv = orig.argv;
assert.deepEqual(process.argv, orig.argv);
'waiting for O/S change notification...': function(err, obj) {
// This is just a message for the next test
assert.isTrue(true);
}
},
'Runtime Configuration Changes are Persisted to runtime.json': {
topic: function() {
// Watch the file for changes
var t = this;
FileSystem.watchFile(runtimeJsonFilename, function(){
t.callback(null, CONFIG._parseFile(runtimeJsonFilename));
});
},
'The O/S notified us of the configuration file change': function(err, runtimeObj) {
assert.isTrue(!err);
},
'Prior configuration values were kept intact': function(err, runtimeObj) {
assert.equal(runtimeObj.Customers.dbName, "override_from_runtime_json");
},
'Changed configuration values were persisted': function(err, runtimeObj) {
assert.equal(runtimeObj.watchThisValue, CONFIG.watchThisValue);
}
}
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc