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