renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
207 lines (201 loc) • 8.02 kB
JavaScript
import { require_punycode } from "./punycode.js";
import { pkg } from "./expose.js";
import { getEnv } from "./util/env.js";
import { regEx } from "./util/regex.js";
import { getConfigFileNames } from "./config/app-strings.js";
import { GlobalConfig } from "./config/global.js";
import { init, logger } from "./logger/index.js";
import { massageConfig } from "./config/massage.js";
import { migrateConfig } from "./config/migration.js";
import { add } from "./util/host-rules.js";
import { validateConfig } from "./config/validation.js";
import { getParsedContent } from "./workers/global/config/parse/util.js";
import { getConfig } from "./workers/global/config/parse/file.js";
import { parseConfigs } from "./workers/global/config/parse/index.js";
import "source-map-support/register.js";
import { styleText } from "node:util";
import { isNonEmptyStringAndNotWhitespace } from "@sindresorhus/is";
import { Command, CommanderError } from "commander";
import { dequal } from "dequal";
import { diffLines } from "diff";
import fs from "fs-extra";
require_punycode();
await init();
const { pathExists, readFile } = fs;
let returnVal = 0;
/**
* Make sure that we've resolved configuration from the different places that Renovate users would expect them to be specified
*
* This then allows a `configType=repo` config to i.e. be validated alongside a `config.js` or `env RENOVATE_ALLOWED_COMMANDS=...`
*
* Note that we intentionally don't fully initialize Renovate and its modules, as we're not fully running, and it would require a Platform to be configured
* */
async function partiallyGlobalInitialize() {
const globalConfig = await parseConfigs(getEnv(), []);
GlobalConfig.set(globalConfig);
if (globalConfig.hostRules) for (const hostRule of globalConfig.hostRules) add(hostRule);
}
async function validate(configType, desc, config, strict, isPreset = false) {
if (config.hostRules) for (const hostRule of config.hostRules) add(hostRule);
const { isMigrated, migratedConfig } = migrateConfig(config);
if (isMigrated) {
logger.warn({
oldConfig: config,
newConfig: migratedConfig
}, "Config migration necessary");
const changedObjects = diffLines(JSON.stringify(config, null, 2), JSON.stringify(migratedConfig, null, 2), {
ignoreWhitespace: false,
newlineIsToken: false
});
const added = styleText("green", "+ ");
const removed = styleText("red", "- ");
const msg = changedObjects.flatMap((part) => {
let linePrefix;
if (part.added) linePrefix = added;
else if (part.removed) linePrefix = removed;
else linePrefix = " ";
return part.value.split("\n").filter(isNonEmptyStringAndNotWhitespace).map((line) => line.replace(regEx(/^(?<ws> *)/), `${linePrefix}$<ws>`));
}).join("\n");
logger.warn(`Config migration diff:\n${msg}`);
if (strict) returnVal = 1;
}
const res = await validateConfig(configType, massageConfig(migratedConfig), isPreset);
if (res.errors.length) {
logger.error({
file: desc,
errors: res.errors
}, "Found errors in configuration");
returnVal = 1;
}
if (res.warnings.length) {
logger.warn({
file: desc,
warnings: res.warnings
}, "Found errors in configuration");
returnVal = 1;
}
}
(async () => {
await partiallyGlobalInitialize();
const program = new Command("renovate-config-validator").summary("Validate Renovate configuration files").description("Validate your Renovate configuration (repo config, shared presets or global configuration) files\nIf no [config-files...] are given, renovate-config-validator will look at the default config file locations (https://docs.renovatebot.com/configuration-options/)").addHelpText("after", `
When specifying [config-files...], Renovate will treat them as global self-hosted configuration files. You can disable this behaviour with --no-global
Examples:
$ renovate-config-validator
$ renovate-config-validator --strict
$ renovate-config-validator first_config.json
$ renovate-config-validator --strict config.js
$ renovate-config-validator --no-global renovate.json5
$ env RENOVATE_CONFIG_FILE=obscure-name.json renovate-config-validator
Global configuration:
If you have specified global self-hosted configuration (https://docs.renovatebot.com/self-hosted-configuration/) in environment variables or in a \`config.js\`, this will be detected:
$ env RENOVATE_ALLOWED_ENV='["GO*"]' renovate-config-validator
# if passing the filename, make sure it's not validating as a global config
$ env RENOVATE_ALLOWED_ENV='["GO*"]' renovate-config-validator --no-global renovate.json`).argument("[config-files...]").version(pkg.version, "-v, --version").option("--strict", "Fail command if any configuration warnings, errors, or a migration is needed").option("--no-global", "When specifying [config-files], do not treat them as global self-hosted configuration file(s)", true).exitOverride();
program.action(async (files, opts) => {
const strict = opts.strict ?? false;
let filesValidated = 0;
if (files.length) {
let isGlobalConfig = true;
if (opts.global === false) isGlobalConfig = false;
const configType = isGlobalConfig ? "global" : "repo";
for (const file of files) {
try {
if (!await pathExists(file)) {
returnVal = 1;
logger.error({ file }, "File does not exist");
break;
}
const parsedContent = await getParsedContent(file);
try {
logger.info(`Validating ${file} as ${configType} config`);
await validate(configType, file, parsedContent, strict);
} catch (err) {
logger.warn({
file,
err
}, "File is not valid Renovate config");
returnVal = 1;
}
} catch (err) {
logger.warn({
file,
err
}, "File could not be parsed");
returnVal = 1;
}
filesValidated++;
}
} else {
for (const file of getConfigFileNames().filter((name) => name !== "package.json")) {
try {
if (!await pathExists(file)) continue;
const parsedContent = await getParsedContent(file);
try {
logger.info(`Validating ${file}`);
await validate("repo", file, parsedContent, strict);
} catch (err) {
logger.warn({
file,
err
}, "File is not valid Renovate config");
returnVal = 1;
}
} catch (err) {
logger.warn({
file,
err
}, "File could not be parsed");
returnVal = 1;
}
filesValidated++;
}
try {
const pkgJson = JSON.parse(await readFile("package.json", "utf8"));
if (pkgJson.renovate) {
logger.info(`Validating package.json > renovate`);
await validate("repo", "package.json > renovate", pkgJson.renovate, strict);
filesValidated++;
}
if (pkgJson["renovate-config"]) {
logger.info(`Validating package.json > renovate-config`);
for (const presetConfig of Object.values(pkgJson["renovate-config"])) {
await validate("repo", "package.json > renovate-config", presetConfig, strict, true);
filesValidated++;
}
}
} catch {}
try {
const env = getEnv();
const fileConfig = await getConfig(env);
if (!dequal(fileConfig, {})) {
const file = env.RENOVATE_CONFIG_FILE ?? "config.js";
logger.info(`Validating ${file}`);
try {
await validate("global", file, fileConfig, strict);
} catch (err) {
logger.error({
file,
err
}, "File is not valid Renovate config");
returnVal = 1;
}
filesValidated++;
}
} catch {}
}
if (returnVal === 0 && filesValidated) logger.info(`Config validated successfully against ${filesValidated} file(s)`);
else if (!filesValidated) logger.warn(`No files to perform configuration validation against`);
process.exitCode = returnVal;
});
await program.parseAsync();
})().catch((e) => {
if (e instanceof CommanderError) {
if (e.code === "commander.helpDisplayed" || e.code === "commander.version") return;
}
console.error(e);
process.exit(99);
});
//#endregion
export {};
//# sourceMappingURL=config-validator.js.map