2017-08-18 04:10:19 +00:00
|
|
|
const configParser = require('./index');
|
|
|
|
const massage = require('./massage');
|
2017-08-25 04:25:25 +00:00
|
|
|
const migration = require('./migration');
|
2018-02-27 09:49:24 +00:00
|
|
|
const npm = require('../datasource/npm');
|
2017-08-18 04:10:19 +00:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
resolveConfigPresets,
|
|
|
|
replaceArgs,
|
|
|
|
parsePreset,
|
|
|
|
getPreset,
|
|
|
|
};
|
|
|
|
|
2017-11-08 05:44:03 +00:00
|
|
|
async function resolveConfigPresets(inputConfig, existingPresets = []) {
|
2017-08-18 04:10:19 +00:00
|
|
|
logger.trace(
|
|
|
|
{ config: inputConfig, existingPresets },
|
|
|
|
'resolveConfigPresets'
|
|
|
|
);
|
|
|
|
let config = {};
|
|
|
|
// First, merge all the preset configs from left to right
|
2017-08-18 19:01:08 +00:00
|
|
|
if (inputConfig.extends && inputConfig.extends.length) {
|
2017-08-18 04:10:19 +00:00
|
|
|
for (const preset of inputConfig.extends) {
|
|
|
|
// istanbul ignore if
|
|
|
|
if (existingPresets.indexOf(preset) !== -1) {
|
|
|
|
logger.warn(`Already seen preset ${preset} in ${existingPresets}`);
|
|
|
|
} else {
|
2017-10-11 16:54:09 +00:00
|
|
|
logger.trace(`Resolving preset "${preset}"`);
|
2017-12-18 08:39:52 +00:00
|
|
|
let fetchedPreset;
|
|
|
|
try {
|
|
|
|
fetchedPreset = await getPreset(preset);
|
|
|
|
} catch (err) {
|
2018-03-31 04:13:35 +00:00
|
|
|
const error = new Error('config-validation');
|
|
|
|
if (err.message === 'dep not found') {
|
|
|
|
error.validationError = `Cannot find preset's package (${preset})`;
|
|
|
|
} else if (err.message === 'preset renovate-config not found') {
|
|
|
|
// istanbul ignore next
|
|
|
|
error.validationError = `Preset package is missing a renovate-config entry (${preset})`;
|
|
|
|
} else if (err.message === 'preset not found') {
|
|
|
|
error.validationError = `Preset name not found within published preset config (${preset})`;
|
2017-12-18 08:39:52 +00:00
|
|
|
} else {
|
2018-03-31 04:13:35 +00:00
|
|
|
/* istanbul ignore next */ // eslint-disable-next-line
|
|
|
|
if (err.message === 'registry-failure') {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// istanbul ignore if
|
|
|
|
if (existingPresets.length) {
|
|
|
|
error.validationError +=
|
|
|
|
'. Note: this is a *nested* preset so please contact the preset author if you are unable to fix it yourself.';
|
2017-12-18 08:39:52 +00:00
|
|
|
}
|
2018-03-31 04:13:35 +00:00
|
|
|
logger.info('Throwing preset error');
|
|
|
|
throw error;
|
2017-12-18 08:39:52 +00:00
|
|
|
}
|
2018-03-06 14:54:27 +00:00
|
|
|
const presetConfig = await resolveConfigPresets(
|
|
|
|
fetchedPreset,
|
|
|
|
existingPresets.concat([preset])
|
|
|
|
);
|
2017-08-18 04:10:19 +00:00
|
|
|
config = configParser.mergeChildConfig(config, presetConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logger.trace({ config }, `Post-preset resolve config`);
|
|
|
|
// Now assign "regular" config on top
|
|
|
|
config = configParser.mergeChildConfig(config, inputConfig);
|
|
|
|
delete config.extends;
|
|
|
|
logger.trace({ config }, `Post-merge resolve config`);
|
2017-11-10 12:46:16 +00:00
|
|
|
for (const [key, val] of Object.entries(config)) {
|
2018-04-09 11:29:47 +00:00
|
|
|
const ignoredKeys = ['content', 'onboardingConfig'];
|
2017-08-18 19:01:08 +00:00
|
|
|
if (isObject(val) && ignoredKeys.indexOf(key) === -1) {
|
2017-08-18 04:10:19 +00:00
|
|
|
// Resolve nested objects
|
|
|
|
logger.trace(`Resolving object "${key}"`);
|
2017-11-08 05:44:03 +00:00
|
|
|
config[key] = await resolveConfigPresets(val, existingPresets);
|
2017-08-18 04:10:19 +00:00
|
|
|
} else if (Array.isArray(val)) {
|
|
|
|
// Resolve nested objects inside arrays
|
|
|
|
config[key] = [];
|
|
|
|
for (const element of val) {
|
|
|
|
if (isObject(element)) {
|
|
|
|
config[key].push(
|
2017-11-08 05:44:03 +00:00
|
|
|
await resolveConfigPresets(element, existingPresets)
|
2017-08-18 04:10:19 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
config[key].push(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-29 07:25:44 +00:00
|
|
|
logger.trace({ config: inputConfig }, 'Input config');
|
|
|
|
logger.trace({ config }, 'Resolved config');
|
2017-08-18 04:10:19 +00:00
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
|
|
|
function replaceArgs(obj, argMapping) {
|
|
|
|
if (typeof obj === 'string') {
|
|
|
|
let returnStr = obj;
|
2017-11-10 12:46:16 +00:00
|
|
|
for (const [arg, argVal] of Object.entries(argMapping)) {
|
2017-08-18 04:10:19 +00:00
|
|
|
const re = new RegExp(`{{${arg}}}`, 'g');
|
2017-11-10 12:46:16 +00:00
|
|
|
returnStr = returnStr.replace(re, argVal);
|
2017-08-18 04:10:19 +00:00
|
|
|
}
|
|
|
|
return returnStr;
|
|
|
|
}
|
|
|
|
if (isObject(obj)) {
|
|
|
|
const returnObj = {};
|
2017-11-10 12:46:16 +00:00
|
|
|
for (const [key, val] of Object.entries(obj)) {
|
|
|
|
returnObj[key] = replaceArgs(val, argMapping);
|
2017-08-18 04:10:19 +00:00
|
|
|
}
|
|
|
|
return returnObj;
|
|
|
|
}
|
|
|
|
if (Array.isArray(obj)) {
|
|
|
|
const returnArray = [];
|
|
|
|
for (const item of obj) {
|
|
|
|
returnArray.push(replaceArgs(item, argMapping));
|
|
|
|
}
|
|
|
|
return returnArray;
|
|
|
|
}
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
function parsePreset(input) {
|
|
|
|
let str = input;
|
|
|
|
let packageName;
|
|
|
|
let presetName;
|
|
|
|
let params;
|
|
|
|
if (str.includes('(')) {
|
|
|
|
params = str
|
|
|
|
.slice(str.indexOf('(') + 1, -1)
|
|
|
|
.split(',')
|
|
|
|
.map(elem => elem.trim());
|
|
|
|
str = str.slice(0, str.indexOf('('));
|
|
|
|
}
|
|
|
|
if (str[0] === ':') {
|
|
|
|
// default namespace
|
|
|
|
packageName = 'renovate-config-default';
|
|
|
|
presetName = str.slice(1);
|
|
|
|
} else if (str[0] === '@') {
|
|
|
|
// scoped namespace
|
2017-09-15 17:46:25 +00:00
|
|
|
[, packageName] = str.match(/(@.*?)(:|$)/);
|
2017-08-18 04:10:19 +00:00
|
|
|
str = str.slice(packageName.length);
|
|
|
|
if (!packageName.includes('/')) {
|
|
|
|
packageName += '/renovate-config';
|
|
|
|
}
|
|
|
|
if (str === '') {
|
|
|
|
presetName = 'default';
|
|
|
|
} else {
|
|
|
|
presetName = str.slice(1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// non-scoped namespace
|
2017-09-15 17:46:25 +00:00
|
|
|
[, packageName] = str.match(/(.*?)(:|$)/);
|
2017-08-18 04:10:19 +00:00
|
|
|
presetName = str.slice(packageName.length + 1);
|
|
|
|
if (packageName.indexOf('renovate-config-') !== 0) {
|
|
|
|
packageName = `renovate-config-${packageName}`;
|
|
|
|
}
|
|
|
|
if (presetName === '') {
|
|
|
|
presetName = 'default';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return { packageName, presetName, params };
|
|
|
|
}
|
|
|
|
|
2017-11-08 05:44:03 +00:00
|
|
|
async function getPreset(preset) {
|
2017-10-11 16:54:09 +00:00
|
|
|
logger.trace(`getPreset(${preset})`);
|
2017-08-18 04:10:19 +00:00
|
|
|
const { packageName, presetName, params } = parsePreset(preset);
|
|
|
|
let presetConfig;
|
2017-12-18 08:39:52 +00:00
|
|
|
const dep = await npm.getDependency(packageName);
|
|
|
|
if (!dep) {
|
|
|
|
throw Error('dep not found');
|
|
|
|
}
|
|
|
|
if (!dep['renovate-config']) {
|
|
|
|
throw Error('preset renovate-config not found');
|
2017-08-18 04:10:19 +00:00
|
|
|
}
|
2017-12-18 08:39:52 +00:00
|
|
|
presetConfig = dep['renovate-config'][presetName];
|
2017-08-18 04:10:19 +00:00
|
|
|
if (!presetConfig) {
|
2017-12-18 08:39:52 +00:00
|
|
|
throw Error('preset not found');
|
2017-08-18 04:10:19 +00:00
|
|
|
}
|
2018-04-18 19:19:00 +00:00
|
|
|
logger.trace({ presetConfig }, `Found preset ${preset}`);
|
2017-08-18 04:10:19 +00:00
|
|
|
if (params) {
|
|
|
|
const argMapping = {};
|
|
|
|
for (const [index, value] of params.entries()) {
|
|
|
|
argMapping[`arg${index}`] = value;
|
|
|
|
}
|
|
|
|
presetConfig = replaceArgs(presetConfig, argMapping);
|
|
|
|
}
|
2017-10-11 16:54:09 +00:00
|
|
|
logger.trace({ presetConfig }, `Applied params to preset ${preset}`);
|
2017-08-18 04:10:19 +00:00
|
|
|
const presetKeys = Object.keys(presetConfig);
|
|
|
|
if (
|
|
|
|
presetKeys.length === 2 &&
|
|
|
|
presetKeys.includes('description') &&
|
|
|
|
presetKeys.includes('extends')
|
|
|
|
) {
|
|
|
|
// preset is just a collection of other presets
|
|
|
|
delete presetConfig.description;
|
|
|
|
}
|
|
|
|
const packageListKeys = [
|
|
|
|
'description',
|
|
|
|
'packageNames',
|
|
|
|
'excludePackageNames',
|
|
|
|
'packagePatterns',
|
|
|
|
'excludePackagePatterns',
|
2017-12-07 08:50:14 +00:00
|
|
|
'unstablePattern',
|
2017-08-18 04:10:19 +00:00
|
|
|
];
|
|
|
|
if (presetKeys.every(key => packageListKeys.includes(key))) {
|
|
|
|
delete presetConfig.description;
|
|
|
|
}
|
2017-09-15 17:46:25 +00:00
|
|
|
const { migratedConfig } = migration.migrateConfig(presetConfig);
|
2017-08-25 04:25:25 +00:00
|
|
|
return massage.massageConfig(migratedConfig);
|
2017-08-18 04:10:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function isObject(obj) {
|
|
|
|
return Object.prototype.toString.call(obj) === '[object Object]';
|
|
|
|
}
|