harthat-chain 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of harthat-chain 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
|
+
}
|