"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getParentName = getParentName;
exports.validateConfig = validateConfig;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const logger_1 = require("../logger");
const manager_1 = require("../modules/manager");
const custom_1 = require("../modules/manager/custom");
const regex_1 = require("../util/regex");
const string_match_1 = require("../util/string-match");
const template = tslib_1.__importStar(require("../util/template"));
const url_1 = require("../util/url");
const schedule_1 = require("../workers/repository/update/branch/schedule");
const app_strings_1 = require("./app-strings");
const global_1 = require("./global");
const migration_1 = require("./migration");
const options_1 = require("./options");
const presets_1 = require("./presets");
const merge_confidence_1 = require("./presets/internal/merge-confidence");
const types_1 = require("./types");
const managerValidator = tslib_1.__importStar(require("./validation-helpers/managers"));
const matchBaseBranchesValidator = tslib_1.__importStar(require("./validation-helpers/match-base-branches"));
const regexOrGlobValidator = tslib_1.__importStar(require("./validation-helpers/regex-glob-matchers"));
const options = (0, options_1.getOptions)();
let optionsInitialized = false;
let optionTypes;
let optionParents;
let optionGlobals;
let optionInherits;
let optionRegexOrGlob;
let optionAllowsNegativeIntegers;
const managerList = (0, manager_1.getManagerList)();
const topLevelObjects = [...managerList, 'env'];
const ignoredNodes = [
    '$schema',
    'headers',
    'depType',
    'npmToken',
    'packageFile',
    'forkToken',
    'repository',
    'vulnerabilityAlertsOnly',
    'vulnerabilityAlert',
    'isVulnerabilityAlert',
    'copyLocalLibs', // deprecated - functionality is now enabled by default
    'prBody', // deprecated
    'minimumConfidence', // undocumented feature flag
];
const tzRe = (0, regex_1.regEx)(/^:timezone\((.+)\)$/);
const rulesRe = (0, regex_1.regEx)(/p.*Rules\[\d+\]$/);
function isManagerPath(parentPath) {
    return ((0, regex_1.regEx)(/^customManagers\[[0-9]+]$/).test(parentPath) ||
        managerList.includes(parentPath));
}
function isIgnored(key) {
    return ignoredNodes.includes(key);
}
function validatePlainObject(val) {
    for (const [key, value] of Object.entries(val)) {
        if (!is_1.default.string(value)) {
            return key;
        }
    }
    return true;
}
function validateNumber(key, val, currentPath, subKey) {
    const errors = [];
    const path = `${currentPath}${subKey ? '.' + subKey : ''}`;
    if (is_1.default.number(val)) {
        if (val < 0 && !optionAllowsNegativeIntegers.has(key)) {
            errors.push({
                topic: 'Configuration Error',
                message: `Configuration option \`${path}\` should be a positive integer. Found negative value instead.`,
            });
        }
    }
    else {
        errors.push({
            topic: 'Configuration Error',
            message: `Configuration option \`${path}\` should be an integer. Found: ${JSON.stringify(val)} (${typeof val}).`,
        });
    }
    return errors;
}
function getUnsupportedEnabledManagers(enabledManagers) {
    return enabledManagers.filter((manager) => !manager_1.allManagersList.includes(manager.replace('custom.', '')));
}
function getDeprecationMessage(option) {
    const deprecatedOptions = {
        branchName: `Direct editing of branchName is now deprecated. Please edit branchPrefix, additionalBranchPrefix, or branchTopic instead`,
        commitMessage: `Direct editing of commitMessage is now deprecated. Please edit commitMessage's subcomponents instead.`,
        prTitle: `Direct editing of prTitle is now deprecated. Please edit commitMessage subcomponents instead as they will be passed through to prTitle.`,
    };
    return deprecatedOptions[option];
}
function isInhertConfigOption(key) {
    return optionInherits.has(key);
}
function isRegexOrGlobOption(key) {
    return optionRegexOrGlob.has(key);
}
function isGlobalOption(key) {
    return optionGlobals.has(key);
}
function initOptions() {
    if (optionsInitialized) {
        return;
    }
    optionParents = {};
    optionInherits = new Set();
    optionTypes = {};
    optionRegexOrGlob = new Set();
    optionGlobals = new Set();
    optionAllowsNegativeIntegers = new Set();
    for (const option of options) {
        optionTypes[option.name] = option.type;
        if (option.parents) {
            optionParents[option.name] = option.parents;
        }
        if (option.inheritConfigSupport) {
            optionInherits.add(option.name);
        }
        if (option.patternMatch) {
            optionRegexOrGlob.add(option.name);
        }
        if (option.globalOnly) {
            optionGlobals.add(option.name);
        }
        if (option.allowNegative) {
            optionAllowsNegativeIntegers.add(option.name);
        }
    }
    optionsInitialized = true;
}
function getParentName(parentPath) {
    return parentPath
        ? parentPath
            .replace((0, regex_1.regEx)(/\.?encrypted$/), '')
            .replace((0, regex_1.regEx)(/\[\d+\]$/), '')
            .split('.')
            .pop()
        : '.';
}
async function validateConfig(configType, config, isPreset, parentPath) {
    initOptions();
    let errors = [];
    let warnings = [];
    for (const [key, val] of Object.entries(config)) {
        const currentPath = parentPath ? `${parentPath}.${key}` : key;
        // istanbul ignore if
        if (key === '__proto__') {
            errors.push({
                topic: 'Config security error',
                message: '__proto__',
            });
            continue;
        }
        if (parentPath &&
            parentPath !== 'onboardingConfig' &&
            topLevelObjects.includes(key)) {
            errors.push({
                topic: 'Configuration Error',
                message: `The "${key}" object can only be configured at the top level of a config but was found inside "${parentPath}"`,
            });
        }
        if (isGlobalOption(key)) {
            if (configType === 'global') {
                await validateGlobalConfig(key, val, optionTypes[key], warnings, errors, currentPath, config);
                continue;
            }
            else if (!isFalseGlobal(key, parentPath) &&
                !(configType === 'inherit' && isInhertConfigOption(key))) {
                warnings.push({
                    topic: 'Configuration Error',
                    message: `The "${key}" option is a global option reserved only for Renovate's global configuration and cannot be configured within a repository's config file.`,
                });
                continue;
            }
        }
        if (key === 'enabledManagers' && val) {
            const unsupportedManagers = getUnsupportedEnabledManagers(val);
            if (is_1.default.nonEmptyArray(unsupportedManagers)) {
                errors.push({
                    topic: 'Configuration Error',
                    message: `The following managers configured in enabledManagers are not supported: "${unsupportedManagers.join(', ')}"`,
                });
            }
        }
        if (key === 'fileMatch') {
            if (parentPath === undefined) {
                errors.push({
                    topic: 'Config error',
                    message: `"fileMatch" may not be defined at the top level of a config and must instead be within a manager block`,
                });
            }
            else if (!isManagerPath(parentPath)) {
                warnings.push({
                    topic: 'Config warning',
                    message: `"fileMatch" must be configured in a manager block and not here: ${parentPath}`,
                });
            }
        }
        if (!isIgnored(key) && // We need to ignore some reserved keys
            !is_1.default.function(val) // Ignore all functions
        ) {
            if (getDeprecationMessage(key)) {
                warnings.push({
                    topic: 'Deprecation Warning',
                    message: getDeprecationMessage(key),
                });
            }
            const templateKeys = [
                'branchName',
                'commitBody',
                'commitMessage',
                'prTitle',
                'semanticCommitScope',
            ];
            if ((key.endsWith('Template') || templateKeys.includes(key)) && val) {
                try {
                    // TODO: validate string #22198
                    let res = template.compile(val.toString(), config, false);
                    res = template.compile(res, config, false);
                    template.compile(res, config, false);
                }
                catch {
                    errors.push({
                        topic: 'Configuration Error',
                        message: `Invalid template in config path: ${currentPath}`,
                    });
                }
            }
            const parentName = getParentName(parentPath);
            if (!isPreset &&
                optionParents[key] &&
                !optionParents[key].includes(parentName)) {
                // TODO: types (#22198)
                const message = `${key} should only be configured within one of "${optionParents[key]?.join(' or ')}" objects. Was found in ${parentName}`;
                warnings.push({
                    topic: `${parentPath ? `${parentPath}.` : ''}${key}`,
                    message,
                });
            }
            if (!optionTypes[key]) {
                errors.push({
                    topic: 'Configuration Error',
                    message: `Invalid configuration option: ${currentPath}`,
                });
            }
            else if (key === 'schedule') {
                const [validSchedule, errorMessage] = (0, schedule_1.hasValidSchedule)(val);
                if (!validSchedule) {
                    errors.push({
                        topic: 'Configuration Error',
                        message: `Invalid ${currentPath}: \`${errorMessage}\``,
                    });
                }
            }
            else if ([
                'allowedVersions',
                'matchCurrentVersion',
                'matchCurrentValue',
                'matchNewValue',
            ].includes(key) &&
                (0, string_match_1.isRegexMatch)(val)) {
                if (!(0, string_match_1.getRegexPredicate)(val)) {
                    errors.push({
                        topic: 'Configuration Error',
                        message: `Invalid regExp for ${currentPath}: \`${val}\``,
                    });
                }
            }
            else if (key === 'timezone' && val !== null) {
                const [validTimezone, errorMessage] = (0, schedule_1.hasValidTimezone)(val);
                if (!validTimezone) {
                    errors.push({
                        topic: 'Configuration Error',
                        message: `${currentPath}: ${errorMessage}`,
                    });
                }
            }
            else if (val !== null) {
                const type = optionTypes[key];
                if (type === 'boolean') {
                    if (val !== true && val !== false) {
                        errors.push({
                            topic: 'Configuration Error',
                            message: `Configuration option \`${currentPath}\` should be boolean. Found: ${JSON.stringify(val)} (${typeof val})`,
                        });
                    }
                }
                else if (type === 'integer') {
                    errors.push(...validateNumber(key, val, currentPath));
                }
                else if (type === 'array' && val) {
                    if (is_1.default.array(val)) {
                        for (const [subIndex, subval] of val.entries()) {
                            if (is_1.default.object(subval)) {
                                const subValidation = await validateConfig(configType, subval, isPreset, `${currentPath}[${subIndex}]`);
                                warnings = warnings.concat(subValidation.warnings);
                                errors = errors.concat(subValidation.errors);
                            }
                        }
                        if (isRegexOrGlobOption(key)) {
                            errors.push(...regexOrGlobValidator.check({
                                val,
                                currentPath,
                            }));
                        }
                        if (key === 'extends') {
                            for (const subval of val) {
                                if (is_1.default.string(subval)) {
                                    if (parentName === 'packageRules' &&
                                        subval.startsWith('group:')) {
                                        warnings.push({
                                            topic: 'Configuration Warning',
                                            message: `${currentPath}: you should not extend "group:" presets`,
                                        });
                                    }
                                    if (tzRe.test(subval)) {
                                        const [, timezone] = tzRe.exec(subval);
                                        const [validTimezone, errorMessage] = (0, schedule_1.hasValidTimezone)(timezone);
                                        if (!validTimezone) {
                                            errors.push({
                                                topic: 'Configuration Error',
                                                message: `${currentPath}: ${errorMessage}`,
                                            });
                                        }
                                    }
                                }
                                else {
                                    errors.push({
                                        topic: 'Configuration Warning',
                                        message: `${currentPath}: preset value is not a string`,
                                    });
                                }
                            }
                        }
                        const selectors = [
                            'matchFileNames',
                            'matchLanguages',
                            'matchCategories',
                            'matchBaseBranches',
                            'matchManagers',
                            'matchDatasources',
                            'matchDepTypes',
                            'matchDepNames',
                            'matchPackageNames',
                            'matchCurrentValue',
                            'matchCurrentVersion',
                            'matchSourceUrls',
                            'matchUpdateTypes',
                            'matchConfidence',
                            'matchCurrentAge',
                            'matchRepositories',
                            'matchNewValue',
                        ];
                        if (key === 'packageRules') {
                            for (const [subIndex, packageRule] of val.entries()) {
                                if (is_1.default.object(packageRule)) {
                                    const resolvedRule = (0, migration_1.migrateConfig)({
                                        packageRules: [
                                            await (0, presets_1.resolveConfigPresets)(packageRule, config),
                                        ],
                                    }).migratedConfig.packageRules[0];
                                    errors.push(...managerValidator.check({ resolvedRule, currentPath }));
                                    warnings.push(...matchBaseBranchesValidator.check({
                                        resolvedRule,
                                        currentPath: `${currentPath}[${subIndex}]`,
                                        baseBranches: config.baseBranches,
                                    }));
                                    const selectorLength = Object.keys(resolvedRule).filter((ruleKey) => selectors.includes(ruleKey)).length;
                                    if (!selectorLength) {
                                        const message = `${currentPath}[${subIndex}]: Each packageRule must contain at least one match* or exclude* selector. Rule: ${JSON.stringify(packageRule)}`;
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message,
                                        });
                                    }
                                    if (selectorLength === Object.keys(resolvedRule).length) {
                                        const message = `${currentPath}[${subIndex}]: Each packageRule must contain at least one non-match* or non-exclude* field. Rule: ${JSON.stringify(packageRule)}`;
                                        warnings.push({
                                            topic: 'Configuration Error',
                                            message,
                                        });
                                    }
                                    // It's too late to apply any of these options once you already have updates determined
                                    const preLookupOptions = [
                                        'allowedVersions',
                                        'extractVersion',
                                        'followTag',
                                        'ignoreDeps',
                                        'ignoreUnstable',
                                        'rangeStrategy',
                                        'registryUrls',
                                        'respectLatest',
                                        'rollbackPrs',
                                        'separateMajorMinor',
                                        'separateMinorPatch',
                                        'separateMultipleMajor',
                                        'separateMultipleMinor',
                                        'versioning',
                                    ];
                                    if (is_1.default.nonEmptyArray(resolvedRule.matchUpdateTypes)) {
                                        for (const option of preLookupOptions) {
                                            if (resolvedRule[option] !== undefined) {
                                                const message = `${currentPath}[${subIndex}]: packageRules cannot combine both matchUpdateTypes and ${option}. Rule: ${JSON.stringify(packageRule)}`;
                                                errors.push({
                                                    topic: 'Configuration Error',
                                                    message,
                                                });
                                            }
                                        }
                                    }
                                }
                                else {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `${currentPath} must contain JSON objects`,
                                    });
                                }
                            }
                        }
                        if (key === 'customManagers') {
                            const allowedKeys = [
                                'customType',
                                'description',
                                'fileMatch',
                                'matchStrings',
                                'matchStringsStrategy',
                                'depNameTemplate',
                                'packageNameTemplate',
                                'datasourceTemplate',
                                'versioningTemplate',
                                'registryUrlTemplate',
                                'currentValueTemplate',
                                'extractVersionTemplate',
                                'autoReplaceStringTemplate',
                                'depTypeTemplate',
                            ];
                            for (const customManager of val) {
                                if (Object.keys(customManager).some((k) => !allowedKeys.includes(k))) {
                                    const disallowedKeys = Object.keys(customManager).filter((k) => !allowedKeys.includes(k));
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Custom Manager contains disallowed fields: ${disallowedKeys.join(', ')}`,
                                    });
                                }
                                else if (is_1.default.nonEmptyString(customManager.customType) &&
                                    (0, custom_1.isCustomManager)(customManager.customType)) {
                                    if (is_1.default.nonEmptyArray(customManager.fileMatch)) {
                                        switch (customManager.customType) {
                                            case 'regex':
                                                validateRegexManagerFields(customManager, currentPath, errors);
                                                break;
                                        }
                                    }
                                    else {
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message: `Each Custom Manager must contain a non-empty fileMatch array`,
                                        });
                                    }
                                }
                                else {
                                    if (is_1.default.emptyString(customManager.customType) ||
                                        is_1.default.undefined(customManager.customType)) {
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message: `Each Custom Manager must contain a non-empty customType string`,
                                        });
                                    }
                                    else {
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message: `Invalid customType: ${customManager.customType}. Key is not a custom manager`,
                                        });
                                    }
                                }
                            }
                        }
                        if (['matchPackageNames', 'matchDepNames'].includes(key)) {
                            const startPattern = (0, regex_1.regEx)(/!?\//);
                            const endPattern = (0, regex_1.regEx)(/\/g?i?$/);
                            for (const pattern of val) {
                                if (startPattern.test(pattern) && endPattern.test(pattern)) {
                                    try {
                                        // regEx isn't aware of our !/ prefix but can handle the suffix
                                        (0, regex_1.regEx)(pattern.replace(startPattern, '/'));
                                    }
                                    catch {
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message: `Invalid regExp for ${currentPath}: \`${pattern}\``,
                                        });
                                    }
                                }
                            }
                        }
                        if (key === 'fileMatch') {
                            for (const fileMatch of val) {
                                try {
                                    (0, regex_1.regEx)(fileMatch);
                                }
                                catch {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Invalid regExp for ${currentPath}: \`${fileMatch}\``,
                                    });
                                }
                            }
                        }
                        if (key === 'baseBranches') {
                            for (const baseBranch of val) {
                                if ((0, string_match_1.isRegexMatch)(baseBranch) &&
                                    !(0, string_match_1.getRegexPredicate)(baseBranch)) {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Invalid regExp for ${currentPath}: \`${baseBranch}\``,
                                    });
                                }
                            }
                        }
                        if ((selectors.includes(key) ||
                            key === 'matchCurrentVersion' ||
                            key === 'matchCurrentValue') &&
                            // TODO: can be undefined ? #22198
                            !rulesRe.test(parentPath) && // Inside a packageRule
                            (is_1.default.string(parentPath) || !isPreset) // top level in a preset
                        ) {
                            errors.push({
                                topic: 'Configuration Error',
                                message: `${currentPath}: ${key} should be inside a \`packageRule\` only`,
                            });
                        }
                    }
                    else {
                        errors.push({
                            topic: 'Configuration Error',
                            message: `Configuration option \`${currentPath}\` should be a list (Array)`,
                        });
                    }
                }
                else if (type === 'string') {
                    if (!is_1.default.string(val)) {
                        errors.push({
                            topic: 'Configuration Error',
                            message: `Configuration option \`${currentPath}\` should be a string`,
                        });
                    }
                }
                else if (type === 'object' &&
                    currentPath !== 'compatibility' &&
                    key !== 'constraints') {
                    if (is_1.default.plainObject(val)) {
                        if (key === 'registryAliases') {
                            const res = validatePlainObject(val);
                            if (res !== true) {
                                errors.push({
                                    topic: 'Configuration Error',
                                    message: `Invalid \`${currentPath}.${key}.${res}\` configuration: value is not a string`,
                                });
                            }
                        }
                        else if (key === 'env') {
                            const allowedEnvVars = configType === 'global'
                                ? (config.allowedEnv ?? [])
                                : global_1.GlobalConfig.get('allowedEnv', []);
                            for (const [envVarName, envVarValue] of Object.entries(val)) {
                                if (!is_1.default.string(envVarValue)) {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Invalid env variable value: \`${currentPath}.${envVarName}\` must be a string.`,
                                    });
                                }
                                if (!(0, string_match_1.matchRegexOrGlobList)(envVarName, allowedEnvVars)) {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Env variable name \`${envVarName}\` is not allowed by this bot's \`allowedEnv\`.`,
                                    });
                                }
                            }
                        }
                        else if (key === 'statusCheckNames') {
                            for (const [statusCheckKey, statusCheckValue] of Object.entries(val)) {
                                if (!types_1.allowedStatusCheckStrings.includes(statusCheckKey)) {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Invalid \`${currentPath}.${key}.${statusCheckKey}\` configuration: key is not allowed.`,
                                    });
                                }
                                if (!(is_1.default.string(statusCheckValue) || is_1.default.null_(statusCheckValue))) {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Invalid \`${currentPath}.${statusCheckKey}\` configuration: status check is not a string.`,
                                    });
                                    continue;
                                }
                            }
                        }
                        else if (key === 'customDatasources') {
                            const allowedKeys = [
                                'description',
                                'defaultRegistryUrlTemplate',
                                'format',
                                'transformTemplates',
                            ];
                            for (const [customDatasourceName, customDatasourceValue,] of Object.entries(val)) {
                                if (!is_1.default.plainObject(customDatasourceValue)) {
                                    errors.push({
                                        topic: 'Configuration Error',
                                        message: `Invalid \`${currentPath}.${customDatasourceName}\` configuration: customDatasource is not an object`,
                                    });
                                    continue;
                                }
                                for (const [subKey, subValue] of Object.entries(customDatasourceValue)) {
                                    if (!allowedKeys.includes(subKey)) {
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message: `Invalid \`${currentPath}.${subKey}\` configuration: key is not allowed`,
                                        });
                                    }
                                    else if (subKey === 'transformTemplates') {
                                        if (!is_1.default.array(subValue, is_1.default.string)) {
                                            errors.push({
                                                topic: 'Configuration Error',
                                                message: `Invalid \`${currentPath}.${subKey}\` configuration: is not an array of string`,
                                            });
                                        }
                                    }
                                    else if (subKey === 'description') {
                                        if (!(is_1.default.string(subValue) || is_1.default.array(subValue, is_1.default.string))) {
                                            errors.push({
                                                topic: 'Configuration Error',
                                                message: `Invalid \`${currentPath}.${subKey}\` configuration: is not an array of strings`,
                                            });
                                        }
                                    }
                                    else if (!is_1.default.string(subValue)) {
                                        errors.push({
                                            topic: 'Configuration Error',
                                            message: `Invalid \`${currentPath}.${subKey}\` configuration: is a string`,
                                        });
                                    }
                                }
                            }
                        }
                        else {
                            const ignoredObjects = options
                                .filter((option) => option.freeChoice)
                                .map((option) => option.name);
                            if (!ignoredObjects.includes(key)) {
                                const subValidation = await validateConfig(configType, val, isPreset, currentPath);
                                warnings = warnings.concat(subValidation.warnings);
                                errors = errors.concat(subValidation.errors);
                            }
                        }
                    }
                    else {
                        errors.push({
                            topic: 'Configuration Error',
                            message: `Configuration option \`${currentPath}\` should be a json object`,
                        });
                    }
                }
            }
        }
        if (key === 'hostRules' && is_1.default.array(val)) {
            const allowedHeaders = configType === 'global'
                ? (config.allowedHeaders ?? [])
                : global_1.GlobalConfig.get('allowedHeaders', []);
            for (const rule of val) {
                if (is_1.default.nonEmptyString(rule.matchHost)) {
                    if (rule.matchHost.includes('://')) {
                        if ((0, url_1.parseUrl)(rule.matchHost) === null) {
                            errors.push({
                                topic: 'Configuration Error',
                                message: `hostRules matchHost \`${rule.matchHost}\` is not a valid URL.`,
                            });
                        }
                    }
                }
                else if (is_1.default.emptyString(rule.matchHost)) {
                    errors.push({
                        topic: 'Configuration Error',
                        message: 'Invalid value for hostRules matchHost. It cannot be an empty string.',
                    });
                }
                if (!rule.headers) {
                    continue;
                }
                for (const [header, value] of Object.entries(rule.headers)) {
                    if (!is_1.default.string(value)) {
                        errors.push({
                            topic: 'Configuration Error',
                            message: `Invalid hostRules headers value configuration: header must be a string.`,
                        });
                    }
                    if (!(0, string_match_1.matchRegexOrGlobList)(header, allowedHeaders)) {
                        errors.push({
                            topic: 'Configuration Error',
                            message: `hostRules header \`${header}\` is not allowed by this bot's \`allowedHeaders\`.`,
                        });
                    }
                }
            }
        }
    }
    function sortAll(a, b) {
        // istanbul ignore else: currently never happen
        if (a.topic === b.topic) {
            return a.message > b.message ? 1 : -1;
        }
        // istanbul ignore next: currently never happen
        return a.topic > b.topic ? 1 : -1;
    }
    errors.sort(sortAll);
    warnings.sort(sortAll);
    return { errors, warnings };
}
function hasField(customManager, field) {
    const templateField = `${field}Template`;
    return !!(customManager[templateField] ??
        customManager.matchStrings?.some((matchString) => matchString.includes(`(?<${field}>`)));
}
function validateRegexManagerFields(customManager, currentPath, errors) {
    if (is_1.default.nonEmptyArray(customManager.matchStrings)) {
        for (const matchString of customManager.matchStrings) {
            try {
                (0, regex_1.regEx)(matchString);
            }
            catch (err) {
                logger_1.logger.debug({ err }, 'customManager.matchStrings regEx validation error');
                errors.push({
                    topic: 'Configuration Error',
                    message: `Invalid regExp for ${currentPath}: \`${matchString}\``,
                });
            }
        }
    }
    else {
        errors.push({
            topic: 'Configuration Error',
            message: `Each Custom Manager must contain a non-empty matchStrings array`,
        });
    }
    const mandatoryFields = ['currentValue', 'datasource'];
    for (const field of mandatoryFields) {
        if (!hasField(customManager, field)) {
            errors.push({
                topic: 'Configuration Error',
                message: `Regex Managers must contain ${field}Template configuration or regex group named ${field}`,
            });
        }
    }
    const nameFields = ['depName', 'packageName'];
    if (!nameFields.some((field) => hasField(customManager, field))) {
        errors.push({
            topic: 'Configuration Error',
            message: `Regex Managers must contain depName or packageName regex groups or templates`,
        });
    }
}
/**
 * Basic validation for global config options
 */
async function validateGlobalConfig(key, val, type, warnings, errors, currentPath, config) {
    // istanbul ignore if
    if (getDeprecationMessage(key)) {
        warnings.push({
            topic: 'Deprecation Warning',
            message: getDeprecationMessage(key),
        });
    }
    if (val !== null) {
        if (type === 'string') {
            if (is_1.default.string(val)) {
                if (key === 'onboardingConfigFileName' &&
                    !app_strings_1.configFileNames.includes(val)) {
                    warnings.push({
                        topic: 'Configuration Error',
                        message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${app_strings_1.configFileNames.join(', ')}.`,
                    });
                }
                else if (key === 'repositoryCache' &&
                    !['enabled', 'disabled', 'reset'].includes(val)) {
                    warnings.push({
                        topic: 'Configuration Error',
                        message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['enabled', 'disabled', 'reset'].join(', ')}.`,
                    });
                }
                else if (key === 'dryRun' &&
                    !['extract', 'lookup', 'full'].includes(val)) {
                    warnings.push({
                        topic: 'Configuration Error',
                        message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['extract', 'lookup', 'full'].join(', ')}.`,
                    });
                }
                else if (key === 'binarySource' &&
                    !['docker', 'global', 'install', 'hermit'].includes(val)) {
                    warnings.push({
                        topic: 'Configuration Error',
                        message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['docker', 'global', 'install', 'hermit'].join(', ')}.`,
                    });
                }
                else if (key === 'requireConfig' &&
                    !['required', 'optional', 'ignored'].includes(val)) {
                    warnings.push({
                        topic: 'Configuration Error',
                        message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['required', 'optional', 'ignored'].join(', ')}.`,
                    });
                }
                else if (key === 'gitUrl' &&
                    !['default', 'ssh', 'endpoint'].includes(val)) {
                    warnings.push({
                        topic: 'Configuration Error',
                        message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${['default', 'ssh', 'endpoint'].join(', ')}.`,
                    });
                }
                if (key === 'reportType' &&
                    ['s3', 'file'].includes(val) &&
                    !is_1.default.string(config.reportPath)) {
                    errors.push({
                        topic: 'Configuration Error',
                        message: `reportType '${val}' requires a configured reportPath`,
                    });
                }
            }
            else {
                warnings.push({
                    topic: 'Configuration Error',
                    message: `Configuration option \`${currentPath}\` should be a string.`,
                });
            }
        }
        else if (type === 'integer') {
            warnings.push(...validateNumber(key, val, currentPath));
        }
        else if (type === 'boolean') {
            if (val !== true && val !== false) {
                warnings.push({
                    topic: 'Configuration Error',
                    message: `Configuration option \`${currentPath}\` should be a boolean. Found: ${JSON.stringify(val)} (${typeof val}).`,
                });
            }
        }
        else if (type === 'array') {
            if (is_1.default.array(val)) {
                if (isRegexOrGlobOption(key)) {
                    warnings.push(...regexOrGlobValidator.check({
                        val,
                        currentPath: currentPath,
                    }));
                }
                if (key === 'gitNoVerify') {
                    const allowedValues = ['commit', 'push'];
                    for (const value of val) {
                        if (!allowedValues.includes(value)) {
                            warnings.push({
                                topic: 'Configuration Error',
                                message: `Invalid value for \`${currentPath}\`. The allowed values are ${allowedValues.join(', ')}.`,
                            });
                        }
                    }
                }
                if (key === 'mergeConfidenceDatasources') {
                    const allowedValues = merge_confidence_1.supportedDatasources;
                    for (const value of val) {
                        if (!allowedValues.includes(value)) {
                            warnings.push({
                                topic: 'Configuration Error',
                                message: `Invalid value \`${value}\` for \`${currentPath}\`. The allowed values are ${allowedValues.join(', ')}.`,
                            });
                        }
                    }
                }
            }
            else {
                warnings.push({
                    topic: 'Configuration Error',
                    message: `Configuration option \`${currentPath}\` should be a list (Array).`,
                });
            }
        }
        else if (type === 'object') {
            if (is_1.default.plainObject(val)) {
                if (key === 'onboardingConfig') {
                    const subValidation = await validateConfig('repo', val);
                    for (const warning of subValidation.warnings.concat(subValidation.errors)) {
                        warnings.push(warning);
                    }
                }
                else if (key === 'force') {
                    const subValidation = await validateConfig('global', val);
                    for (const warning of subValidation.warnings.concat(subValidation.errors)) {
                        warnings.push(warning);
                    }
                }
                else if (key === 'cacheTtlOverride') {
                    for (const [subKey, subValue] of Object.entries(val)) {
                        warnings.push(...validateNumber(key, subValue, currentPath, subKey));
                    }
                }
                else {
                    const res = validatePlainObject(val);
                    if (res !== true) {
                        warnings.push({
                            topic: 'Configuration Error',
                            message: `Invalid \`${currentPath}.${res}\` configuration: value must be a string.`,
                        });
                    }
                }
            }
            else {
                warnings.push({
                    topic: 'Configuration Error',
                    message: `Configuration option \`${currentPath}\` should be a JSON object.`,
                });
            }
        }
    }
}
/**  An option is a false global if it has the same name as a global only option
 *   but is actually just the field of a non global option or field an children of the non global option
 *   eg. token: it's global option used as the bot's token as well and
 *   also it can be the token used for a platform inside the hostRules configuration
 */
function isFalseGlobal(optionName, parentPath) {
    if (parentPath?.includes('hostRules')) {
        if (optionName === 'token' ||
            optionName === 'username' ||
            optionName === 'password') {
            return true;
        }
    }
    return false;
}
//# sourceMappingURL=validation.js.map