renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
742 lines (741 loc) • 31.4 kB
JavaScript
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 { 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 { 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) {
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`
});
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 === "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("://")) {
if (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