renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
330 lines • 14.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectConfigFile = detectConfigFile;
exports.detectRepoFileConfig = detectRepoFileConfig;
exports.checkForRepoConfigError = checkForRepoConfigError;
exports.mergeRenovateConfig = mergeRenovateConfig;
exports.setNpmTokenInNpmrc = setNpmTokenInNpmrc;
exports.resolveStaticRepoConfig = resolveStaticRepoConfig;
exports.tryReadStaticRepoFileConfig = tryReadStaticRepoFileConfig;
exports.mergeStaticConfig = mergeStaticConfig;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const config_1 = require("../../../config");
const app_strings_1 = require("../../../config/app-strings");
const decrypt_1 = require("../../../config/decrypt");
const migrate_validate_1 = require("../../../config/migrate-validate");
const migration_1 = require("../../../config/migration");
const parse_1 = require("../../../config/parse");
const presets = tslib_1.__importStar(require("../../../config/presets"));
const secrets_1 = require("../../../config/secrets");
const configValidation = tslib_1.__importStar(require("../../../config/validation"));
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const npmApi = tslib_1.__importStar(require("../../../modules/datasource/npm"));
const platform_1 = require("../../../modules/platform");
const scm_1 = require("../../../modules/platform/scm");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const repository_1 = require("../../../util/cache/repository");
const common_1 = require("../../../util/common");
const env_1 = require("../../../util/env");
const fs_1 = require("../../../util/fs");
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const queue = tslib_1.__importStar(require("../../../util/http/queue"));
const throttle = tslib_1.__importStar(require("../../../util/http/throttle"));
const mask_1 = require("../../../util/mask");
const regex_1 = require("../../../util/regex");
const config_2 = require("../onboarding/branch/config");
const onboarding_branch_cache_1 = require("../onboarding/branch/onboarding-branch-cache");
const common_2 = require("../onboarding/common");
async function detectConfigFile() {
const fileList = await scm_1.scm.getFileList();
for (const fileName of app_strings_1.configFileNames) {
if (fileName === 'package.json') {
try {
const pJson = JSON.parse((await (0, fs_1.readLocalFile)('package.json', 'utf8')));
if (pJson.renovate) {
logger_1.logger.warn('Using package.json for Renovate config is deprecated - please use a dedicated configuration file instead');
return 'package.json';
}
}
catch {
// Do nothing
}
}
else if (fileList.includes(fileName)) {
return fileName;
}
}
return null;
}
async function detectRepoFileConfig(branchName) {
const cache = (0, repository_1.getCache)();
let { configFileName } = cache;
if (is_1.default.nonEmptyString(configFileName)) {
let configFileRaw;
try {
configFileRaw = await platform_1.platform.getRawFile(configFileName, undefined, branchName);
}
catch (err) {
// istanbul ignore if
if (err instanceof external_host_error_1.ExternalHostError) {
throw err;
}
configFileRaw = null;
}
if (configFileRaw) {
let configFileParsed = (0, common_1.parseJson)(configFileRaw, configFileName);
if (configFileName === 'package.json') {
configFileParsed = configFileParsed.renovate;
}
return { configFileName, configFileParsed };
}
else {
logger_1.logger.debug('Existing config file no longer exists');
delete cache.configFileName;
}
}
if (common_2.OnboardingState.onboardingCacheValid) {
configFileName = (0, onboarding_branch_cache_1.getOnboardingFileNameFromCache)();
}
else {
configFileName = (await detectConfigFile()) ?? undefined;
}
if (!configFileName) {
logger_1.logger.debug('No renovate config file found');
cache.configFileName = '';
return {};
}
cache.configFileName = configFileName;
logger_1.logger.debug(`Found ${configFileName} config file`);
// TODO #22198
let configFileParsed;
let configFileRaw;
if (common_2.OnboardingState.onboardingCacheValid) {
const cachedConfig = (0, onboarding_branch_cache_1.getOnboardingConfigFromCache)();
const parsedConfig = cachedConfig ? JSON.parse(cachedConfig) : undefined;
if (parsedConfig) {
(0, onboarding_branch_cache_1.setOnboardingConfigDetails)(configFileName, JSON.stringify(parsedConfig));
return { configFileName, configFileParsed: parsedConfig };
}
}
if (configFileName === 'package.json') {
// We already know it parses
configFileParsed = JSON.parse(
// TODO #22198
(await (0, fs_1.readLocalFile)('package.json', 'utf8'))).renovate;
if (is_1.default.string(configFileParsed)) {
logger_1.logger.debug('Massaging string renovate config to extends array');
configFileParsed = { extends: [configFileParsed] };
}
logger_1.logger.debug({ config: configFileParsed }, 'package.json>renovate config');
}
else {
configFileRaw = await (0, fs_1.readLocalFile)(configFileName, 'utf8');
// istanbul ignore if
if (!is_1.default.string(configFileRaw)) {
logger_1.logger.warn({ configFileName }, 'Null contents when reading config file');
throw new Error(error_messages_1.REPOSITORY_CHANGED);
}
// istanbul ignore if
if (!configFileRaw.length) {
configFileRaw = '{}';
}
const parseResult = (0, parse_1.parseFileConfig)(configFileName, configFileRaw);
if (!parseResult.success) {
return {
configFileName,
configFileParseError: {
validationError: parseResult.validationError,
validationMessage: parseResult.validationMessage,
},
};
}
configFileParsed = parseResult.parsedContents;
logger_1.logger.debug({ fileName: configFileName, config: configFileParsed }, 'Repository config');
}
(0, onboarding_branch_cache_1.setOnboardingConfigDetails)(configFileName, JSON.stringify(configFileParsed));
return { configFileName, configFileParsed };
}
function checkForRepoConfigError(repoConfig) {
if (!repoConfig.configFileParseError) {
return;
}
const error = new Error(error_messages_1.CONFIG_VALIDATION);
error.validationSource = repoConfig.configFileName;
error.validationError = repoConfig.configFileParseError.validationError;
error.validationMessage = repoConfig.configFileParseError.validationMessage;
throw error;
}
// Check for repository config
async function mergeRenovateConfig(config, branchName) {
let returnConfig = { ...config };
let repoConfig = {};
if (config.requireConfig !== 'ignored') {
repoConfig = await detectRepoFileConfig(branchName);
}
if (!repoConfig.configFileParsed && config.mode === 'silent') {
logger_1.logger.debug('When mode=silent and repo has no config file, we use the onboarding config as repo config');
const configFileName = (0, common_2.getDefaultConfigFileName)(config);
repoConfig = {
configFileName,
configFileParsed: await (0, config_2.getOnboardingConfig)(config),
};
}
const configFileParsed = repoConfig?.configFileParsed ?? {};
const resolvedRepoConfig = await resolveStaticRepoConfig(configFileParsed, process.env.RENOVATE_X_STATIC_REPO_CONFIG_FILE);
if (is_1.default.nonEmptyArray(returnConfig.extends)) {
resolvedRepoConfig.extends = [
...returnConfig.extends,
...(resolvedRepoConfig.extends ?? []),
];
delete returnConfig.extends;
}
checkForRepoConfigError(repoConfig);
const migratedConfig = await (0, migrate_validate_1.migrateAndValidate)(config, resolvedRepoConfig);
if (migratedConfig.errors?.length) {
const error = new Error(error_messages_1.CONFIG_VALIDATION);
error.validationSource = repoConfig.configFileName;
error.validationError =
'The renovate configuration file contains some invalid settings';
error.validationMessage = migratedConfig.errors
.map((e) => e.message)
.join(', ');
throw error;
}
if (migratedConfig.warnings) {
returnConfig.warnings = [
...(returnConfig.warnings ?? []),
...migratedConfig.warnings,
];
}
delete migratedConfig.errors;
delete migratedConfig.warnings;
// TODO #22198
const repository = config.repository;
// Decrypt before resolving in case we need npm authentication for any presets
const decryptedConfig = await (0, decrypt_1.decryptConfig)(migratedConfig, repository);
setNpmTokenInNpmrc(decryptedConfig);
// istanbul ignore if
if (is_1.default.string(decryptedConfig.npmrc)) {
logger_1.logger.debug('Found npmrc in decrypted config - setting');
npmApi.setNpmrc(decryptedConfig.npmrc);
}
// Decrypt after resolving in case the preset contains npm authentication instead
let resolvedConfig = await (0, decrypt_1.decryptConfig)(await presets.resolveConfigPresets(decryptedConfig, config, config.ignorePresets), repository);
logger_1.logger.trace({ config: resolvedConfig }, 'resolved config');
const migrationResult = (0, migration_1.migrateConfig)(resolvedConfig);
if (migrationResult.isMigrated) {
logger_1.logger.debug('Resolved config needs migrating');
logger_1.logger.trace({ config: resolvedConfig }, 'resolved config after migrating');
resolvedConfig = migrationResult.migratedConfig;
}
setNpmTokenInNpmrc(resolvedConfig);
// istanbul ignore if
if (is_1.default.string(resolvedConfig.npmrc)) {
logger_1.logger.debug('Ignoring any .npmrc files in repository due to configured npmrc');
npmApi.setNpmrc(resolvedConfig.npmrc);
}
resolvedConfig = (0, secrets_1.applySecretsAndVariablesToConfig)({
config: resolvedConfig,
secrets: (0, config_1.mergeChildConfig)(config.secrets ?? {}, resolvedConfig.secrets ?? {}),
variables: (0, config_1.mergeChildConfig)(config.variables ?? {}, resolvedConfig.variables ?? {}),
});
// istanbul ignore if
if (resolvedConfig.hostRules) {
logger_1.logger.debug('Setting hostRules from config');
for (const rule of resolvedConfig.hostRules) {
try {
hostRules.add(rule);
}
catch (err) {
logger_1.logger.warn({ err, config: rule }, 'Error setting hostRule from config');
}
}
// host rules can change concurrency
queue.clear();
throttle.clear();
delete resolvedConfig.hostRules;
}
returnConfig = (0, config_1.mergeChildConfig)(returnConfig, resolvedConfig);
returnConfig = await presets.resolveConfigPresets(returnConfig, config);
returnConfig.renovateJsonPresent = true;
// istanbul ignore if
if (returnConfig.ignorePaths?.length) {
logger_1.logger.debug({ ignorePaths: returnConfig.ignorePaths }, `Found repo ignorePaths`);
}
(0, env_1.setUserEnv)(returnConfig.env);
delete returnConfig.env;
return returnConfig;
}
/** needed when using portal secrets for npmToken */
function setNpmTokenInNpmrc(config) {
if (!is_1.default.string(config.npmToken)) {
return;
}
const token = config.npmToken;
logger_1.logger.debug({ npmToken: (0, mask_1.maskToken)(token) }, 'Migrating npmToken to npmrc');
if (!is_1.default.string(config.npmrc)) {
logger_1.logger.debug('Adding npmrc to config');
config.npmrc = `//registry.npmjs.org/:_authToken=${token}\n`;
delete config.npmToken;
return;
}
if (config.npmrc.includes(`\${NPM_TOKEN}`)) {
logger_1.logger.debug(`Replacing \${NPM_TOKEN} with npmToken`);
config.npmrc = config.npmrc.replace((0, regex_1.regEx)(/\${NPM_TOKEN}/g), token);
}
else {
logger_1.logger.debug('Appending _authToken= to end of existing npmrc');
config.npmrc = config.npmrc.replace((0, regex_1.regEx)(/\n?$/), `\n_authToken=${token}\n`);
}
delete config.npmToken;
}
async function resolveStaticRepoConfig(config, filename) {
if (!is_1.default.nonEmptyString(filename)) {
return config;
}
let staticRepoConfig;
try {
staticRepoConfig = await tryReadStaticRepoFileConfig(filename);
}
catch (err) {
logger_1.logger.fatal({ err }, 'Failed to load static repository config file');
process.exit(1);
}
if (!is_1.default.nonEmptyObject(staticRepoConfig)) {
return config;
}
return mergeStaticConfig(config, staticRepoConfig);
}
async function tryReadStaticRepoFileConfig(staticRepoConfigFile) {
logger_1.logger.debug(`Reading static repo config file from ${staticRepoConfigFile}`);
let staticRepoConfigRaw;
try {
staticRepoConfigRaw = await (0, fs_1.readSystemFile)(staticRepoConfigFile, 'utf8');
}
catch (err) {
throw new Error(`Failed to read static repo config file: "${staticRepoConfigFile}"`, { cause: err });
}
const staticRepoConfig = (0, common_1.parseJson)(staticRepoConfigRaw, staticRepoConfigFile);
// validate and log issues here to preserve context, caller handles migration and full validation.
const { errors, warnings } = await configValidation.validateConfig('repo', staticRepoConfig);
if (is_1.default.nonEmptyArray(errors) || is_1.default.nonEmptyArray(warnings)) {
logger_1.logger.info({ errors, warnings }, 'Static repo config validation issues detected');
}
else {
logger_1.logger.debug({ staticRepoConfig }, 'Static repository config file successfully parsed and validated');
}
return staticRepoConfig;
}
function mergeStaticConfig(config, staticRepoConfig) {
// merge extends
if (is_1.default.nonEmptyArray(staticRepoConfig.extends)) {
config.extends = [...staticRepoConfig.extends, ...(config.extends ?? [])];
delete staticRepoConfig.extends;
}
// renovate repo config overrides RENOVATE_STATIC_REPO_CONFIG[_FILE]
return (0, config_1.mergeChildConfig)(staticRepoConfig, config);
}
//# sourceMappingURL=merge.js.map