1 2'use strict' 3const fs = require('fs') 4const path = require('path') 5const YError = require('./yerror') 6 7let previouslyVisitedConfigs = [] 8 9function checkForCircularExtends (cfgPath) { 10 if (previouslyVisitedConfigs.indexOf(cfgPath) > -1) { 11 throw new YError(`Circular extended configurations: '${cfgPath}'.`) 12 } 13} 14 15function getPathToDefaultConfig (cwd, pathToExtend) { 16 return path.resolve(cwd, pathToExtend) 17} 18 19function mergeDeep (config1, config2) { 20 const target = {} 21 const isObject = obj => obj && typeof obj === 'object' && !Array.isArray(obj) 22 Object.assign(target, config1) 23 for (let key of Object.keys(config2)) { 24 if (isObject(config2[key]) && isObject(target[key])) { 25 target[key] = mergeDeep(config1[key], config2[key]) 26 } else { 27 target[key] = config2[key] 28 } 29 } 30 return target 31} 32 33function applyExtends (config, cwd, mergeExtends) { 34 let defaultConfig = {} 35 36 if (Object.prototype.hasOwnProperty.call(config, 'extends')) { 37 if (typeof config.extends !== 'string') return defaultConfig 38 const isPath = /\.json|\..*rc$/.test(config.extends) 39 let pathToDefault = null 40 if (!isPath) { 41 try { 42 pathToDefault = require.resolve(config.extends) 43 } catch (err) { 44 // most likely this simply isn't a module. 45 } 46 } else { 47 pathToDefault = getPathToDefaultConfig(cwd, config.extends) 48 } 49 // maybe the module uses key for some other reason, 50 // err on side of caution. 51 if (!pathToDefault && !isPath) return config 52 53 checkForCircularExtends(pathToDefault) 54 55 previouslyVisitedConfigs.push(pathToDefault) 56 57 defaultConfig = isPath ? JSON.parse(fs.readFileSync(pathToDefault, 'utf8')) : require(config.extends) 58 delete config.extends 59 defaultConfig = applyExtends(defaultConfig, path.dirname(pathToDefault), mergeExtends) 60 } 61 62 previouslyVisitedConfigs = [] 63 64 return mergeExtends ? mergeDeep(defaultConfig, config) : Object.assign({}, defaultConfig, config) 65} 66 67module.exports = applyExtends 68