import { regEx } from "../util/regex.js";
import { getConfigFileNames } from "./app-strings.js";
import { GlobalConfig } from "./global.js";
import { anyMatchRegexOrGlobList, getRegexPredicate, isRegexMatch, matchRegexOrGlobList } from "../util/string-match.js";
import { logger } from "../logger/index.js";
import { AllManagersListLiteral, CustomManagersListLiteral } from "../manager-list.generated.js";
import { AllVersioningsListLiteral } from "../versioning-list.generated.js";
import { supportedDatasources } from "./presets/internal/merge-confidence.preset.js";
import { allowedStatusCheckStrings } from "./types.js";
import { getOptions } from "./options/index.js";
import { parseUrl } from "../util/url.js";
import { migrateConfig } from "./migration.js";
import { instrument } from "../instrumentation/index.js";
import { validate } from "../util/template/index.js";
import { isCustomManager } from "../modules/manager/custom/index.js";
import { packageCacheNamespaces } from "../util/cache/package/namespaces.js";
import { getToolConfig } from "../util/exec/containerbase.js";
import { isConstraintName, isToolName } from "../util/exec/types.js";
import { isValidCommitTrailer } from "../util/git/commit-trailers.js";
import { getExpression } from "../util/jsonata.js";
import { hasValidSchedule, hasValidTimezone } from "../workers/repository/update/branch/schedule.js";
import { parsePreset } from "./presets/parse.js";
import { resolveConfigPresets } from "./presets/index.js";
import { check } from "./validation-helpers/match-base-branches.js";
import { check as check$1 } from "./validation-helpers/regex-glob-matchers.js";
import { getParentName, isFalseGlobal, validateJSONataManagerFields, validateNumber, validatePlainObject, validateRegexManagerFields } from "./validation-helpers/utils.js";
import is, { isArray, isEmptyString, isNonEmptyArray, isNonEmptyString, isObject, isPlainObject, isString, isUndefined } from "@sindresorhus/is";
//#region lib/config/validation.ts
const options = getOptions();
let optionsInitialized = false;
let optionTypes;
let optionParents;
let optionGlobals;
let optionInherits;
let optionRegexOrGlob;
let optionAllowsNegativeIntegers;
let optionSupportsTemplating;
const managerList = AllManagersListLiteral;
const allManagersList = [...AllManagersListLiteral, ...CustomManagersListLiteral];
const topLevelObjects = [...managerList, "env"];
const ignoredNodes = [
	"$schema",
	"headers",
	"depType",
	"npmToken",
	"packageFile",
	"forkToken",
	"repository",
	"vulnerabilityAlertsOnly",
	"vulnerabilityAlert",
	"isVulnerabilityAlert",
	"vulnerabilityFixVersion",
	"copyLocalLibs",
	"prBody",
	"minimumConfidence"
];
const tzRe = regEx(/^:timezone\((.+)\)$/);
const rulesRe = regEx(/p.*Rules\[\d+\]$/);
function isIgnored(key) {
	return ignoredNodes.includes(key);
}
function getUnsupportedEnabledManagers(enabledManagers) {
	return enabledManagers.filter((manager) => !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.`
	};
	if (deprecatedOptions[option]) return deprecatedOptions[option];
	const found = options.find((o) => o.name === option);
	if (!found) return;
	if (!found.deprecationMsg) return;
	return `The '${option}' option is deprecated: ${found.deprecationMsg}`;
}
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 = /* @__PURE__ */ new Set();
	optionTypes = {};
	optionRegexOrGlob = /* @__PURE__ */ new Set();
	optionGlobals = /* @__PURE__ */ new Set();
	optionAllowsNegativeIntegers = /* @__PURE__ */ new Set();
	optionSupportsTemplating = /* @__PURE__ */ 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);
		if (option.supportsTemplating) optionSupportsTemplating.add(option.name);
	}
	optionsInitialized = true;
}
async function validateConfig(configType, config, isPreset, parentPath) {
	return instrument(`validateConfig(${configType}, ${JSON.stringify(parentPath)})`, async () => {
		initOptions();
		let errors = [];
		let warnings = [];
		for (const [key, val] of Object.entries(config)) {
			const currentPath = parentPath ? `${parentPath}.${key}` : key;
			/* v8 ignore next 7 -- TODO: add test */
			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 (isNonEmptyArray(unsupportedManagers)) errors.push({
					topic: "Configuration Error",
					message: `The following managers configured in enabledManagers are not supported: "${unsupportedManagers.join(", ")}"`
				});
			}
			if (key === "registryUrls" && !parentPath && isNonEmptyArray(val)) warnings.push({
				topic: "Configuration Warning",
				message: "Setting `registryUrls` at the top level of your config will apply it to all managers and datasources, which can cause the wrong registry URL to be used for some packages. Use `registryUrls` inside `packageRules` to target specific managers or packages."
			});
			if (key === "defaultRegistryUrls" && !parentPath && isNonEmptyArray(val)) warnings.push({
				topic: "Configuration Warning",
				message: "Setting `defaultRegistryUrls` at the top level of your config will apply it to all managers and datasources, which can cause the wrong registry URL to be used for some packages. Use `defaultRegistryUrls` inside `packageRules` to target specific managers or packages."
			});
			if (!isIgnored(key) && !is.function(val)) {
				if (getDeprecationMessage(key)) warnings.push({
					topic: "Deprecation Warning",
					message: getDeprecationMessage(key)
				});
				if (optionSupportsTemplating.has(key) && val) try {
					validate(val.toString());
				} 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)) {
					const message = `"${key}" can't be used in "${parentName}". Allowed objects: ${optionParents[key]?.toSorted().join(", ")}.`;
					warnings.push({
						topic: `${parentPath ? `${parentPath}.` : ""}${key}`,
						message
					});
				}
				// v8 ignore else -- intentionally unhandled - if we knew what was to be covered here, we'd add validation
				if (!optionTypes[key]) errors.push({
					topic: "Configuration Error",
					message: `Invalid configuration option: ${currentPath}`
				});
				else if (key === "schedule") {
					const [validSchedule, errorMessage] = hasValidSchedule(val);
					if (!validSchedule) errors.push({
						topic: "Configuration Error",
						message: `Invalid ${currentPath}: \`${errorMessage}\``
					});
				} else if ([
					"allowedVersions",
					"matchCurrentVersion",
					"matchCurrentValue",
					"matchNewValue"
				].includes(key) && isRegexMatch(val)) {
					if (!getRegexPredicate(val)) errors.push({
						topic: "Configuration Error",
						message: `Invalid regExp for ${currentPath}: \`${val}\``
					});
				} else if (key === "timezone" && val !== null) {
					const [validTimezone, errorMessage] = 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") {
						const allowsNegative = optionAllowsNegativeIntegers.has(key);
						errors.push(...validateNumber(key, val, allowsNegative, currentPath));
					} else if (type === "array" && val) if (isArray(val)) {
						for (const [subIndex, subval] of val.entries()) if (isObject(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(...check$1({
							val,
							currentPath
						}));
						if (key === "extends") for (const subval of val) if (isString(subval)) {
							if (configType !== "global" && subval.startsWith("global:")) errors.push({
								topic: "Configuration Error",
								message: `${currentPath}: you cannot extend from "global:" presets in a repository config's "extends"`
							});
							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] = hasValidTimezone(timezone);
								if (!validTimezone) errors.push({
									topic: "Configuration Error",
									message: `${currentPath}: ${errorMessage}`
								});
							}
							if (!subval.includes("{{")) try {
								parsePreset(subval);
							} catch {
								errors.push({
									topic: "Configuration Error",
									message: `${currentPath}: preset "${subval}" is not valid`
								});
							}
						} else errors.push({
							topic: "Configuration Error",
							message: `${currentPath}: preset value is not a string`
						});
						if (key === "commitTrailers") {
							for (const subval of val) if (!isValidCommitTrailer(subval)) errors.push({
								topic: "Configuration Error",
								message: `Invalid commit trailer: \`${JSON.stringify(subval)}\`. Must be a single-line string in the form \`Key: value\`, where the key contains only letters, digits and \`-\`.`
							});
						}
						const selectors = [
							"matchFileNames",
							"matchLanguages",
							"matchCategories",
							"matchBaseBranches",
							"matchManagers",
							"matchDatasources",
							"matchDepTypes",
							"matchDepNames",
							"matchPackageNames",
							"matchCurrentValue",
							"matchCurrentVersion",
							"matchSourceUrls",
							"matchRegistryUrls",
							"matchUpdateTypes",
							"matchConfidence",
							"matchCurrentAge",
							"matchRepositories",
							"matchNewValue",
							"matchJsonata"
						];
						if (key === "packageRules") for (const [subIndex, packageRule] of val.entries()) if (isObject(packageRule)) {
							const { config: resolved } = await resolveConfigPresets(packageRule, config);
							const resolvedRule = migrateConfig({ packageRules: [resolved] }).migratedConfig.packageRules[0];
							warnings.push(...check({
								resolvedRule,
								currentPath: `${currentPath}[${subIndex}]`,
								baseBranchPatterns: config.baseBranchPatterns
							}));
							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
								});
							}
							const preLookupOptions = [
								"allowedVersions",
								"extractVersion",
								"followTag",
								"ignoreDeps",
								"ignoreUnstable",
								"rangeStrategy",
								"registryUrls",
								"respectLatest",
								"rollbackPrs",
								"separateMajorMinor",
								"separateMinorPatch",
								"separateMultipleMajor",
								"separateMultipleMinor",
								"versioning"
							];
							if (isNonEmptyArray(resolvedRule.matchUpdateTypes)) {
								for (const option of preLookupOptions) if (resolvedRule[option] !== void 0) {
									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",
								"fileFormat",
								"managerFilePatterns",
								"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 (isNonEmptyString(customManager.customType) && isCustomManager(customManager.customType)) if (isNonEmptyArray(customManager.managerFilePatterns)) switch (customManager.customType) {
								case "regex":
									validateRegexManagerFields(customManager, currentPath, errors);
									break;
								case "jsonata":
									validateJSONataManagerFields(customManager, currentPath, errors);
									break;
							}
							else errors.push({
								topic: "Configuration Error",
								message: `Each Custom Manager must contain a non-empty managerFilePatterns array`
							});
							else if (isEmptyString(customManager.customType) || isUndefined(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 = regEx(/!?\//);
							const endPattern = regEx(/\/g?i?$/);
							for (const pattern of val) if (startPattern.test(pattern) && endPattern.test(pattern)) try {
								regEx(pattern.replace(startPattern, "/"));
							} catch {
								errors.push({
									topic: "Configuration Error",
									message: `Invalid regExp for ${currentPath}: \`${pattern}\``
								});
							}
						}
						if (key === "baseBranchPatterns") {
							for (const baseBranchPattern of val) if (isRegexMatch(baseBranchPattern) && !getRegexPredicate(baseBranchPattern)) errors.push({
								topic: "Configuration Error",
								message: `Invalid regExp for ${currentPath}: \`${baseBranchPattern}\``
							});
						}
						if ((selectors.includes(key) || key === "matchCurrentVersion" || key === "matchCurrentValue") && !rulesRe.test(parentPath) && (isString(parentPath) || !isPreset)) 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 (!isString(val)) errors.push({
							topic: "Configuration Error",
							message: `Configuration option \`${currentPath}\` should be a string`
						});
					} else if (type === "object") if (isPlainObject(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 ?? [] : GlobalConfig.get("allowedEnv");
							for (const [envVarName, envVarValue] of Object.entries(val)) {
								if (!isString(envVarValue)) errors.push({
									topic: "Configuration Error",
									message: `Invalid env variable value: \`${currentPath}.${envVarName}\` must be a string.`
								});
								if (!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 (!allowedStatusCheckStrings.includes(statusCheckKey)) errors.push({
								topic: "Configuration Error",
								message: `Invalid \`${currentPath}.${key}.${statusCheckKey}\` configuration: key is not allowed.`
							});
							if (!(isString(statusCheckValue) || null === statusCheckValue)) {
								errors.push({
									topic: "Configuration Error",
									message: `Invalid \`${currentPath}.${statusCheckKey}\` configuration: status check is not a string.`
								});
								continue;
							}
						}
						else if (key === "statusCheckWhen") {
							const allowedWhenValues = [
								"always",
								"never",
								"failed"
							];
							for (const [statusCheckKey, statusCheckValue] of Object.entries(val)) {
								if (!allowedStatusCheckStrings.includes(statusCheckKey)) errors.push({
									topic: "Configuration Error",
									message: `Invalid \`${currentPath}.${key}.${statusCheckKey}\` configuration: key is not allowed.`
								});
								if (!isString(statusCheckValue) || !allowedWhenValues.includes(statusCheckValue)) {
									errors.push({
										topic: "Configuration Error",
										message: `Invalid \`${currentPath}.${statusCheckKey}\` configuration: value must be one of "always", "never", or "failed".`
									});
									continue;
								}
							}
						} else if (key === "customDatasources") {
							const allowedKeys = [
								"description",
								"defaultRegistryUrlTemplate",
								"format",
								"transformTemplates"
							];
							for (const [customDatasourceName, customDatasourceValue] of Object.entries(val)) {
								if (!isPlainObject(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 (!isArray(subValue, isString)) errors.push({
										topic: "Configuration Error",
										message: `Invalid \`${currentPath}.${subKey}\` configuration: is not an array of string`
									});
								} else if (subKey === "description") {
									if (!(isString(subValue) || isArray(subValue, isString))) errors.push({
										topic: "Configuration Error",
										message: `Invalid \`${currentPath}.${subKey}\` configuration: is not an array of strings`
									});
								} else if (!isString(subValue)) errors.push({
									topic: "Configuration Error",
									message: `Invalid \`${currentPath}.${subKey}\` configuration: is a string`
								});
							}
						} else if (key === "installTools") {
							for (const toolName of Object.keys(val)) if (!isToolName(toolName)) warnings.push({
								topic: "Configuration Error",
								message: `Invalid \`${currentPath}.${toolName}\` configuration: not a valid tool name.`
							});
						} else if (key === "constraints") {
							const { get: getVersioning } = await import("../modules/versioning/index.js");
							for (const [k, v] of Object.entries(val)) {
								if (!isString(v)) {
									errors.push({
										topic: "Configuration Error",
										message: `Configuration option \`${currentPath}.${k}\` should be an object of key-value pairs of constraints and their value`
									});
									break;
								}
								if (!isConstraintName(k)) warnings.push({
									topic: "Configuration Error",
									message: `Configuration option \`${currentPath}.${k}\`: \`${k}\` is not a supported constraint name`
								});
								else if (isToolName(k)) {
									const versioningId = getToolConfig(k).versioning;
									if (!getVersioning(versioningId).isValid(v)) warnings.push({
										topic: "Configuration Error",
										message: `Configuration option \`${currentPath}.${k}=${v}\` is not a valid tool version constraint, according to \`${versioningId}\` versioning`
									});
								}
							}
						} else if (key === "constraintsVersioning") for (const [k, v] of Object.entries(val)) {
							if (!isString(v)) {
								errors.push({
									topic: "Configuration Error",
									message: `Configuration option \`${currentPath}.${k}\` should be an object of key-value pairs of additional constraint names and their versioning`
								});
								break;
							}
							if (isToolName(k)) errors.push({
								topic: "Configuration Error",
								message: `Configuration option \`${currentPath}.${k}\` is not a valid additional constraint name, as \`${k}\` is a tool name, and \`constraintsVersioning\` can only override the versioning for a non-tool constraint`
							});
							else if (isConstraintName(k)) {
								const versioningName = v.split(":")[0];
								if (!AllVersioningsListLiteral.includes(versioningName)) errors.push({
									topic: "Configuration Error",
									message: `Configuration option \`${currentPath}.${k}=${v}\`: \`${v}\` is not a valid versioning scheme`
								});
							} else errors.push({
								topic: "Configuration Error",
								message: `Configuration option \`${currentPath}.${k}\`: \`${k}\` is not a known additional constraint name`
							});
						}
						else if (!options.filter((option) => option.freeChoice).map((option) => option.name).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`
					});
					else
 // v8 ignore next -- intentionally unhandled - if we knew what was to be covered here, we'd add validation
					logger.debug({}, `Unhandled validation for ${type} at \`${currentPath}\``);
				}
			}
			if (key === "hostRules" && isArray(val)) {
				const allowedHeaders = configType === "global" ? config.allowedHeaders ?? [] : GlobalConfig.get("allowedHeaders");
				for (const rule of val) {
					if (isNonEmptyString(rule.matchHost)) {
						if (rule.matchHost.includes("://") && parseUrl(rule.matchHost) === null) errors.push({
							topic: "Configuration Error",
							message: `hostRules matchHost \`${rule.matchHost}\` is not a valid URL.`
						});
					} else if (isEmptyString(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 (!isString(value)) errors.push({
							topic: "Configuration Error",
							message: `Invalid hostRules headers value configuration: header must be a string.`
						});
						if (!matchRegexOrGlobList(header, allowedHeaders)) errors.push({
							topic: "Configuration Error",
							message: `hostRules header \`${header}\` is not allowed by this bot's \`allowedHeaders\`.`
						});
					}
				}
			}
			if (key === "matchJsonata" && isArray(val, isString)) for (const expression of val) {
				const res = getExpression(expression);
				if (res instanceof Error) errors.push({
					topic: "Configuration Error",
					message: `Invalid JSONata expression for ${currentPath}: ${res.message}`
				});
			}
		}
		function sortAll(a, b) {
			if (a.topic === b.topic) return a.message > b.message ? 1 : -1;
			return a.topic > b.topic ? 1 : -1;
		}
		errors.sort(sortAll);
		warnings.sort(sortAll);
		return {
			errors,
			warnings
		};
	});
}
/**
* Basic validation for global config options
*/
async function validateGlobalConfig(key, val, type, warnings, errors, currentPath, config) {
	/* v8 ignore next 5 -- not testable yet */
	if (getDeprecationMessage(key)) warnings.push({
		topic: "Deprecation Warning",
		message: getDeprecationMessage(key)
	});
	if (key === "binarySource" && val === "docker") warnings.push({
		topic: "Deprecation Warning",
		message: "Usage of `binarySource=docker` is deprecated, and will be removed in the future. Please migrate to `binarySource=install`. Feedback on the usage of `binarySource=docker` is welcome at https://github.com/renovatebot/renovate/discussions/40742"
	});
	if (val !== null) {
		// v8 ignore else -- TODO: add test #40625
		if (type === "string") if (isString(val)) {
			if (key === "onboardingConfigFileName" && !getPossibleConfigFileNames({
				configFileNames: config.configFileNames,
				platform: config.platform
			}).includes(val)) warnings.push({
				topic: "Configuration Error",
				message: `Invalid value \`${val}\` for \`${currentPath}\`. The allowed values are ${getPossibleConfigFileNames({
					configFileNames: config.configFileNames,
					platform: config.platform
				}).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) && !isString(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") {
			const allowsNegative = optionAllowsNegativeIntegers.has(key);
			warnings.push(...validateNumber(key, val, allowsNegative, 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 (isArray(val)) {
			for (const [subIndex, subval] of val.entries()) if (isObject(subval)) {
				const subValidation = await validateConfig("global", subval, false, `${currentPath}[${subIndex}]`);
				warnings.push(...subValidation.warnings);
				errors.push(...subValidation.errors);
			}
			if (isRegexOrGlobOption(key)) warnings.push(...check$1({
				val,
				currentPath
			}));
			if (key === "gitNoVerify") {
				const allowedValues = ["commit", "push"];
				for (const value of val)
 // v8 ignore else -- TODO: add test #40625
				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 = supportedDatasources;
				for (const value of val)
 // v8 ignore else -- TODO: add test #40625
				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 (isPlainObject(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)) {
			const allowsNegative = optionAllowsNegativeIntegers.has(key);
			warnings.push(...validateNumber(key, subValue, allowsNegative, currentPath, subKey));
			if (!packageCacheNamespaces.includes(subKey) && !anyMatchRegexOrGlobList(packageCacheNamespaces, [subKey])) errors.push({
				message: `${currentPath}: namespace \`${subKey}\` does not exist`,
				topic: "Configuration Error"
			});
		}
		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.`
		});
	}
}
function getPossibleConfigFileNames({ configFileNames, platform }) {
	const filenames = getConfigFileNames(platform);
	if (isNonEmptyArray(configFileNames)) return filenames.concat(configFileNames);
	return filenames;
}
//#endregion
export { validateConfig };

//# sourceMappingURL=validation.js.map