mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-13 15:36:25 +00:00
291 lines
8.7 KiB
TypeScript
291 lines
8.7 KiB
TypeScript
import is from '@sindresorhus/is';
|
|
import minimatch from 'minimatch';
|
|
import slugify from 'slugify';
|
|
import { mergeChildConfig } from '../config';
|
|
import type { PackageRule, PackageRuleInputConfig } from '../config/types';
|
|
import { logger } from '../logger';
|
|
import * as allVersioning from '../versioning';
|
|
import { configRegexPredicate, regEx } from './regex';
|
|
|
|
function matchesRule(
|
|
inputConfig: PackageRuleInputConfig,
|
|
packageRule: PackageRule
|
|
): boolean {
|
|
const {
|
|
versioning,
|
|
packageFile,
|
|
lockFiles,
|
|
depType,
|
|
depTypes,
|
|
depName,
|
|
currentValue,
|
|
currentVersion,
|
|
lockedVersion,
|
|
updateType,
|
|
isBump,
|
|
sourceUrl,
|
|
language,
|
|
baseBranch,
|
|
manager,
|
|
datasource,
|
|
} = inputConfig;
|
|
const unconstrainedValue = lockedVersion && is.undefined(currentValue);
|
|
// Setting empty arrays simplifies our logic later
|
|
const matchFiles = packageRule.matchFiles || [];
|
|
const matchPaths = packageRule.matchPaths || [];
|
|
const matchLanguages = packageRule.matchLanguages || [];
|
|
const matchBaseBranches = packageRule.matchBaseBranches || [];
|
|
const matchManagers = packageRule.matchManagers || [];
|
|
const matchDatasources = packageRule.matchDatasources || [];
|
|
const matchDepTypes = packageRule.matchDepTypes || [];
|
|
const matchPackageNames = packageRule.matchPackageNames || [];
|
|
let matchPackagePatterns = packageRule.matchPackagePatterns || [];
|
|
const matchPackagePrefixes = packageRule.matchPackagePrefixes || [];
|
|
const excludePackageNames = packageRule.excludePackageNames || [];
|
|
const excludePackagePatterns = packageRule.excludePackagePatterns || [];
|
|
const excludePackagePrefixes = packageRule.excludePackagePrefixes || [];
|
|
const matchSourceUrlPrefixes = packageRule.matchSourceUrlPrefixes || [];
|
|
const matchCurrentVersion = packageRule.matchCurrentVersion || null;
|
|
const matchUpdateTypes = packageRule.matchUpdateTypes || [];
|
|
let positiveMatch = false;
|
|
// Massage a positive patterns patch if an exclude one is present
|
|
if (
|
|
(excludePackageNames.length ||
|
|
excludePackagePatterns.length ||
|
|
excludePackagePrefixes.length) &&
|
|
!(
|
|
matchPackageNames.length ||
|
|
matchPackagePatterns.length ||
|
|
matchPackagePrefixes.length
|
|
)
|
|
) {
|
|
matchPackagePatterns = ['.*'];
|
|
}
|
|
if (matchFiles.length) {
|
|
const isMatch = matchFiles.some(
|
|
(fileName) =>
|
|
packageFile === fileName ||
|
|
(is.array(lockFiles) && lockFiles?.includes(fileName))
|
|
);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchPaths.length) {
|
|
const isMatch = matchPaths.some(
|
|
(rulePath) =>
|
|
packageFile.includes(rulePath) ||
|
|
minimatch(packageFile, rulePath, { dot: true })
|
|
);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchDepTypes.length) {
|
|
const isMatch =
|
|
matchDepTypes.includes(depType) ||
|
|
depTypes?.some((dt) => matchDepTypes.includes(dt));
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchLanguages.length) {
|
|
const isMatch = matchLanguages.includes(language);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchBaseBranches.length) {
|
|
const isMatch = matchBaseBranches.includes(baseBranch);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchManagers.length) {
|
|
const isMatch = matchManagers.includes(manager);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchDatasources.length) {
|
|
const isMatch = matchDatasources.includes(datasource);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchUpdateTypes.length) {
|
|
const isMatch =
|
|
matchUpdateTypes.includes(updateType) ||
|
|
(isBump && matchUpdateTypes.includes('bump'));
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (
|
|
depName &&
|
|
(matchPackageNames.length ||
|
|
matchPackagePatterns.length ||
|
|
matchPackagePrefixes.length)
|
|
) {
|
|
let isMatch = matchPackageNames.includes(depName);
|
|
// name match is "or" so we check patterns if we didn't match names
|
|
if (!isMatch) {
|
|
for (const packagePattern of matchPackagePatterns) {
|
|
const packageRegex = regEx(
|
|
packagePattern === '^*$' || packagePattern === '*'
|
|
? '.*'
|
|
: packagePattern
|
|
); // TODO #12071
|
|
if (packageRegex.test(depName)) {
|
|
logger.trace(`${depName} matches against ${String(packageRegex)}`);
|
|
isMatch = true;
|
|
}
|
|
}
|
|
}
|
|
// prefix match is also "or"
|
|
if (!isMatch && matchPackagePrefixes.length) {
|
|
isMatch = matchPackagePrefixes.some((prefix) =>
|
|
depName.startsWith(prefix)
|
|
);
|
|
}
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (excludePackageNames.length) {
|
|
const isMatch = excludePackageNames.includes(depName);
|
|
if (isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (depName && excludePackagePatterns.length) {
|
|
let isMatch = false;
|
|
for (const pattern of excludePackagePatterns) {
|
|
const packageRegex = regEx(
|
|
pattern === '^*$' || pattern === '*' ? '.*' : pattern
|
|
); // TODO #12071
|
|
if (packageRegex.test(depName)) {
|
|
logger.trace(`${depName} matches against ${String(packageRegex)}`);
|
|
isMatch = true;
|
|
}
|
|
}
|
|
if (isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (depName && excludePackagePrefixes.length) {
|
|
const isMatch = excludePackagePrefixes.some((prefix) =>
|
|
depName.startsWith(prefix)
|
|
);
|
|
if (isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchSourceUrlPrefixes.length) {
|
|
const upperCaseSourceUrl = sourceUrl?.toUpperCase();
|
|
const isMatch = matchSourceUrlPrefixes.some((prefix) =>
|
|
upperCaseSourceUrl?.startsWith(prefix.toUpperCase())
|
|
);
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
}
|
|
if (matchCurrentVersion) {
|
|
const version = allVersioning.get(versioning);
|
|
const matchCurrentVersionStr = matchCurrentVersion.toString();
|
|
const matchCurrentVersionPred = configRegexPredicate(
|
|
matchCurrentVersionStr
|
|
);
|
|
if (matchCurrentVersionPred) {
|
|
if (!unconstrainedValue && !matchCurrentVersionPred(currentValue)) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
} else if (version.isVersion(matchCurrentVersionStr)) {
|
|
let isMatch = false;
|
|
try {
|
|
isMatch =
|
|
unconstrainedValue ||
|
|
version.matches(matchCurrentVersionStr, currentValue);
|
|
} catch (err) {
|
|
// Do nothing
|
|
}
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
} else {
|
|
const compareVersion =
|
|
currentValue && version.isVersion(currentValue)
|
|
? currentValue // it's a version so we can match against it
|
|
: lockedVersion || currentVersion; // need to match against this currentVersion, if available
|
|
if (compareVersion) {
|
|
// istanbul ignore next
|
|
if (version.isVersion(compareVersion)) {
|
|
const isMatch = version.matches(compareVersion, matchCurrentVersion);
|
|
// istanbul ignore if
|
|
if (!isMatch) {
|
|
return false;
|
|
}
|
|
positiveMatch = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
logger.debug(
|
|
{ matchCurrentVersionStr, currentValue },
|
|
'Could not find a version to compare'
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return positiveMatch;
|
|
}
|
|
|
|
export function applyPackageRules<T extends PackageRuleInputConfig>(
|
|
inputConfig: T
|
|
): T {
|
|
let config = { ...inputConfig };
|
|
const packageRules = config.packageRules || [];
|
|
logger.trace(
|
|
{ dependency: config.depName, packageRules },
|
|
`Checking against ${packageRules.length} packageRules`
|
|
);
|
|
packageRules.forEach((packageRule) => {
|
|
// This rule is considered matched if there was at least one positive match and no negative matches
|
|
if (matchesRule(config, packageRule)) {
|
|
// Package rule config overrides any existing config
|
|
const toApply = { ...packageRule };
|
|
if (config.groupSlug && packageRule.groupName && !packageRule.groupSlug) {
|
|
// Need to apply groupSlug otherwise the existing one will take precedence
|
|
toApply.groupSlug = slugify(packageRule.groupName, {
|
|
lower: true,
|
|
});
|
|
}
|
|
config = mergeChildConfig(config, toApply);
|
|
delete config.matchPackageNames;
|
|
delete config.matchPackagePatterns;
|
|
delete config.matchPackagePrefixes;
|
|
delete config.excludePackageNames;
|
|
delete config.excludePackagePatterns;
|
|
delete config.excludePackagePrefixes;
|
|
delete config.matchDepTypes;
|
|
delete config.matchCurrentVersion;
|
|
}
|
|
});
|
|
return config;
|
|
}
|