#!/usr/bin/env node // istanbul ignore file import 'source-map-support/register'; import './punycode.cjs'; import { dequal } from 'dequal'; import { pathExists, readFile } from 'fs-extra'; import { configFileNames } from './config/app-strings'; import { massageConfig } from './config/massage'; import { migrateConfig } from './config/migration'; import type { RenovateConfig } from './config/types'; import { validateConfig } from './config/validation'; import { logger } from './logger'; import { getConfig as getFileConfig, getParsedContent, } from './workers/global/config/parse/file'; let returnVal = 0; async function validate( configType: 'global' | 'repo', desc: string, config: RenovateConfig, strict: boolean, isPreset = false, ): Promise { const { isMigrated, migratedConfig } = migrateConfig(config); if (isMigrated) { logger.warn( { oldConfig: config, newConfig: migratedConfig, }, 'Config migration necessary', ); if (strict) { returnVal = 1; } } const massagedConfig = massageConfig(migratedConfig); const res = await validateConfig(configType, massagedConfig, 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; } } type PackageJson = { renovate?: RenovateConfig; 'renovate-config'?: Record; }; (async () => { const strictArgIndex = process.argv.indexOf('--strict'); const strict = strictArgIndex >= 0; if (strict) { process.argv.splice(strictArgIndex, 1); } if (process.argv.length > 2) { for (const file of process.argv.slice(2)) { 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}`); await validate('global', 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; } } } else { for (const file of configFileNames.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; } } try { const pkgJson = JSON.parse( await readFile('package.json', 'utf8'), ) as PackageJson; if (pkgJson.renovate) { logger.info(`Validating package.json > renovate`); await validate( 'repo', 'package.json > renovate', pkgJson.renovate, strict, ); } 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, ); } } } catch { // ignore } try { const fileConfig = await getFileConfig(process.env); if (!dequal(fileConfig, {})) { const file = process.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; } } } catch { // ignore } } if (returnVal !== 0) { process.exit(returnVal); } logger.info('Config validated successfully'); })().catch((e) => { // eslint-disable-next-line no-console console.error(e); process.exit(99); });