|
| 1 | +var fs = require('fs'); |
| 2 | +var path = require('path'); |
| 3 | + |
| 4 | +var ConfigLoader = exports; |
| 5 | + |
| 6 | +/** |
| 7 | + * Load application config from `app.json` and friends. |
| 8 | + * @param {String} rootDir Directory where to look for files. |
| 9 | + * @param {String} env Environment, usually `process.env.NODE_ENV` |
| 10 | + * @returns {Object} |
| 11 | + */ |
| 12 | +ConfigLoader.loadAppConfig = function(rootDir, env) { |
| 13 | + return loadNamed(rootDir, env, 'app', mergeAppConfig); |
| 14 | +}; |
| 15 | + |
| 16 | +/** |
| 17 | + * Load data-sources config from `datasources.json` and friends. |
| 18 | + * @param {String} rootDir Directory where to look for files. |
| 19 | + * @param {String} env Environment, usually `process.env.NODE_ENV` |
| 20 | + * @returns {Object} |
| 21 | + */ |
| 22 | +ConfigLoader.loadDataSources = function(rootDir, env) { |
| 23 | + return loadNamed(rootDir, env, 'datasources', mergeDataSourceConfig); |
| 24 | +}; |
| 25 | + |
| 26 | +/** |
| 27 | + * Load models config from `models.json` and friends. |
| 28 | + * @param {String} rootDir Directory where to look for files. |
| 29 | + * @param {String} env Environment, usually `process.env.NODE_ENV` |
| 30 | + * @returns {Object} |
| 31 | + */ |
| 32 | +ConfigLoader.loadModels = function(rootDir, env) { |
| 33 | + /*jshint unused:false */ |
| 34 | + return tryReadJsonConfig(rootDir, 'models') || {}; |
| 35 | +}; |
| 36 | + |
| 37 | +/*-- Implementation --*/ |
| 38 | + |
| 39 | +/** |
| 40 | + * Load named configuration. |
| 41 | + * @param {String} rootDir Directory where to look for files. |
| 42 | + * @param {String} env Environment, usually `process.env.NODE_ENV` |
| 43 | + * @param {String} name |
| 44 | + * @param {function(target:Object, config:Object, filename:String)} mergeFn |
| 45 | + * @returns {Object} |
| 46 | + */ |
| 47 | +function loadNamed(rootDir, env, name, mergeFn) { |
| 48 | + var files = findConfigFiles(rootDir, env, name); |
| 49 | + var configs = loadConfigFiles(files); |
| 50 | + return mergeConfigurations(configs, mergeFn); |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * Search `appRootDir` for all files containing configuration for `name`. |
| 55 | + * @param {String} appRootDir |
| 56 | + * @param {String} env Environment, usually `process.env.NODE_ENV` |
| 57 | + * @param {String} name |
| 58 | + * @returns {Array.<String>} Array of absolute file paths. |
| 59 | + */ |
| 60 | +function findConfigFiles(appRootDir, env, name) { |
| 61 | + var master = ifExists(name + '.json'); |
| 62 | + if (!master) return []; |
| 63 | + |
| 64 | + var candidates = [ |
| 65 | + master, |
| 66 | + ifExistsWithAnyExt(name + '.local'), |
| 67 | + ifExistsWithAnyExt(name + '.' + env) |
| 68 | + ]; |
| 69 | + |
| 70 | + return candidates.filter(function(c) { return c !== undefined; }); |
| 71 | + |
| 72 | + function ifExists(fileName) { |
| 73 | + var filepath = path.resolve(appRootDir, fileName); |
| 74 | + return fs.existsSync(filepath) ? filepath : undefined; |
| 75 | + } |
| 76 | + |
| 77 | + function ifExistsWithAnyExt(fileName) { |
| 78 | + return ifExists(fileName + '.js') || ifExists(fileName + '.json'); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +/** |
| 83 | + * Load configuration files into an array of objects. |
| 84 | + * Attach non-enumerable `_filename` property to each object. |
| 85 | + * @param {Array.<String>} files |
| 86 | + * @returns {Array.<Object>} |
| 87 | + */ |
| 88 | +function loadConfigFiles(files) { |
| 89 | + return files.map(function(f) { |
| 90 | + var config = require(f); |
| 91 | + Object.defineProperty(config, '_filename', { |
| 92 | + enumerable: false, |
| 93 | + value: f |
| 94 | + }); |
| 95 | + return config; |
| 96 | + }); |
| 97 | +} |
| 98 | + |
| 99 | +/** |
| 100 | + * Merge multiple configuration objects into a single one. |
| 101 | + * @param {Array.<Object>} configObjects |
| 102 | + * @param {function(target:Object, config:Object, filename:String)} mergeFn |
| 103 | + */ |
| 104 | +function mergeConfigurations(configObjects, mergeFn) { |
| 105 | + var result = configObjects.shift() || {}; |
| 106 | + while(configObjects.length) { |
| 107 | + var next = configObjects.shift(); |
| 108 | + mergeFn(result, next, next._filename); |
| 109 | + } |
| 110 | + return result; |
| 111 | +} |
| 112 | + |
| 113 | +function mergeDataSourceConfig(target, config, fileName) { |
| 114 | + for (var ds in target) { |
| 115 | + var err = applyCustomConfig(target[ds], config[ds]); |
| 116 | + if (err) { |
| 117 | + throw new Error('Cannot apply ' + fileName + ' to `' + ds + '`: ' + err); |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +function mergeAppConfig(target, config, fileName) { |
| 123 | + var err = applyCustomConfig(target, config); |
| 124 | + if (err) { |
| 125 | + throw new Error('Cannot apply ' + fileName + ': ' + err); |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +function applyCustomConfig(target, config) { |
| 130 | + for (var key in config) { |
| 131 | + var value = config[key]; |
| 132 | + if (typeof value === 'object') { |
| 133 | + return 'override for the option `' + key + '` is not a value type.'; |
| 134 | + } |
| 135 | + target[key] = value; |
| 136 | + } |
| 137 | + return null; // no error |
| 138 | +} |
| 139 | + |
| 140 | +/** |
| 141 | + * Try to read a config file with .json extension |
| 142 | + * @param cwd Dirname of the file |
| 143 | + * @param fileName Name of the file without extension |
| 144 | + * @returns {Object|undefined} Content of the file, undefined if not found. |
| 145 | + */ |
| 146 | +function tryReadJsonConfig(cwd, fileName) { |
| 147 | + try { |
| 148 | + return require(path.join(cwd, fileName + '.json')); |
| 149 | + } catch(e) { |
| 150 | + if(e.code !== 'MODULE_NOT_FOUND') { |
| 151 | + throw e; |
| 152 | + } |
| 153 | + } |
| 154 | +} |
0 commit comments