UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

207 lines (201 loc) • 8.02 kB
#!/usr/bin/env node 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