harthat-cookie 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of harthat-cookie might be problematic. Click here for more details.
- package/History.md +710 -0
- package/LICENSE +19 -0
- package/README.md +176 -0
- package/async.js +70 -0
- package/defer.js +23 -0
- package/deference.js +81 -0
- package/lib/config.js +1521 -0
- package/package.json +53 -0
- package/parser.js +368 -0
- package/pk.json +52 -0
- package/raw.js +15 -0
package/lib/config.js
ADDED
@@ -0,0 +1,1521 @@
|
|
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
|
+
}
|