mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-13 07:26:26 +00:00
refactor(package-rules): move to class based implementation (#16865)
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
This commit is contained in:
parent
92800240be
commit
dda2ebce92
27 changed files with 778 additions and 340 deletions
|
@ -1,334 +0,0 @@
|
||||||
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 '../modules/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 matchSourceUrls = packageRule.matchSourceUrls ?? [];
|
|
||||||
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 && packageFile) {
|
|
||||||
const isMatch = matchPaths.some(
|
|
||||||
(rulePath) =>
|
|
||||||
packageFile.includes(rulePath) ||
|
|
||||||
minimatch(packageFile, rulePath, { dot: true })
|
|
||||||
);
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (matchDepTypes.length) {
|
|
||||||
const isMatch =
|
|
||||||
(depType && matchDepTypes.includes(depType)) ||
|
|
||||||
depTypes?.some((dt) => matchDepTypes.includes(dt));
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (matchLanguages.length) {
|
|
||||||
if (!language) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const isMatch = matchLanguages.includes(language);
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (matchBaseBranches.length) {
|
|
||||||
if (!baseBranch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const isMatch = matchBaseBranches.some((matchBaseBranch): boolean => {
|
|
||||||
const isAllowedPred = configRegexPredicate(matchBaseBranch);
|
|
||||||
if (isAllowedPred) {
|
|
||||||
return isAllowedPred(baseBranch);
|
|
||||||
}
|
|
||||||
return matchBaseBranch === baseBranch;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (matchManagers.length) {
|
|
||||||
if (!manager) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const isMatch = matchManagers.includes(manager);
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (matchDatasources.length) {
|
|
||||||
if (!datasource) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const isMatch = matchDatasources.includes(datasource);
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (matchUpdateTypes.length) {
|
|
||||||
const isMatch =
|
|
||||||
(updateType && matchUpdateTypes.includes(updateType)) ||
|
|
||||||
(isBump && matchUpdateTypes.includes('bump'));
|
|
||||||
if (!isMatch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
matchPackageNames.length ||
|
|
||||||
matchPackagePatterns.length ||
|
|
||||||
matchPackagePrefixes.length
|
|
||||||
) {
|
|
||||||
if (!depName) {
|
|
||||||
// if using the default rules, return true else false
|
|
||||||
return (
|
|
||||||
is.undefined(packageRule.matchPackagePatterns) &&
|
|
||||||
is.undefined(packageRule.matchPackageNames) &&
|
|
||||||
is.undefined(packageRule.matchPackagePrefixes)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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
|
|
||||||
);
|
|
||||||
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 = depName && 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
|
|
||||||
);
|
|
||||||
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 (matchSourceUrls.length) {
|
|
||||||
const upperCaseSourceUrl = sourceUrl?.toUpperCase();
|
|
||||||
const isMatch = matchSourceUrls.some(
|
|
||||||
(url) => upperCaseSourceUrl === url.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 &&
|
|
||||||
(!currentValue || !matchCurrentVersionPred(currentValue))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
positiveMatch = true;
|
|
||||||
} else if (version.isVersion(matchCurrentVersionStr)) {
|
|
||||||
let isMatch = false;
|
|
||||||
try {
|
|
||||||
isMatch =
|
|
||||||
unconstrainedValue ||
|
|
||||||
!!(
|
|
||||||
currentValue &&
|
|
||||||
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;
|
|
||||||
}
|
|
27
lib/util/package-rules/base-branches.ts
Normal file
27
lib/util/package-rules/base-branches.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { configRegexPredicate } from '../regex';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class BaseBranchesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ baseBranch }: PackageRuleInputConfig,
|
||||||
|
{ matchBaseBranches }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchBaseBranches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.undefined(baseBranch)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchBaseBranches.some((matchBaseBranch): boolean => {
|
||||||
|
const isAllowedPred = configRegexPredicate(matchBaseBranch);
|
||||||
|
if (isAllowedPred) {
|
||||||
|
return isAllowedPred(baseBranch);
|
||||||
|
}
|
||||||
|
return matchBaseBranch === baseBranch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
28
lib/util/package-rules/base.ts
Normal file
28
lib/util/package-rules/base.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import type { MatcherApi } from './types';
|
||||||
|
|
||||||
|
export abstract class Matcher implements MatcherApi {
|
||||||
|
/**
|
||||||
|
* Test exclusion packageRule against inputConfig
|
||||||
|
* @return null if no rules are defined, true if exclusion should be applied and else false
|
||||||
|
* @param inputConfig
|
||||||
|
* @param packageRule
|
||||||
|
*/
|
||||||
|
excludes(
|
||||||
|
inputConfig: PackageRuleInputConfig,
|
||||||
|
packageRule: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test match packageRule against inputConfig
|
||||||
|
* @return null if no rules are defined, true if match should be applied and else false
|
||||||
|
* @param inputConfig
|
||||||
|
* @param packageRule
|
||||||
|
*/
|
||||||
|
abstract matches(
|
||||||
|
inputConfig: PackageRuleInputConfig,
|
||||||
|
packageRule: PackageRule
|
||||||
|
): boolean | null;
|
||||||
|
}
|
43
lib/util/package-rules/current-version.spec.ts
Normal file
43
lib/util/package-rules/current-version.spec.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import pep440 from '../../modules/versioning/pep440';
|
||||||
|
import { CurrentVersionMatcher } from './current-version';
|
||||||
|
|
||||||
|
describe('util/package-rules/current-version', () => {
|
||||||
|
const matcher = new CurrentVersionMatcher();
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('match', () => {
|
||||||
|
it('return false on version exception', () => {
|
||||||
|
const spy = jest.spyOn(pep440, 'matches').mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
});
|
||||||
|
const result = matcher.matches(
|
||||||
|
{
|
||||||
|
versioning: 'pep440',
|
||||||
|
currentValue: '===>1.2.3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchCurrentVersion: '1.2.3',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
expect(spy.mock.calls).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return false if no version could be found', () => {
|
||||||
|
const result = matcher.matches(
|
||||||
|
{
|
||||||
|
versioning: 'pep440',
|
||||||
|
currentValue: 'aaaaaa',
|
||||||
|
lockedVersion: 'bbbbbb',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchCurrentVersion: 'bbbbbb',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
62
lib/util/package-rules/current-version.ts
Normal file
62
lib/util/package-rules/current-version.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import * as allVersioning from '../../modules/versioning';
|
||||||
|
import { configRegexPredicate } from '../regex';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class CurrentVersionMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{
|
||||||
|
versioning,
|
||||||
|
lockedVersion,
|
||||||
|
currentValue,
|
||||||
|
currentVersion,
|
||||||
|
}: PackageRuleInputConfig,
|
||||||
|
{ matchCurrentVersion }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchCurrentVersion)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.nullOrUndefined(currentValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUnconstrainedValue = !!lockedVersion;
|
||||||
|
const version = allVersioning.get(versioning);
|
||||||
|
const matchCurrentVersionStr = matchCurrentVersion.toString();
|
||||||
|
const matchCurrentVersionPred = configRegexPredicate(
|
||||||
|
matchCurrentVersionStr
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchCurrentVersionPred) {
|
||||||
|
return !(!isUnconstrainedValue && !matchCurrentVersionPred(currentValue));
|
||||||
|
}
|
||||||
|
if (version.isVersion(matchCurrentVersionStr)) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
isUnconstrainedValue ||
|
||||||
|
version.matches(matchCurrentVersionStr, currentValue)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const compareVersion = 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 (is.undefined(compareVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (version.isVersion(compareVersion)) {
|
||||||
|
return version.matches(compareVersion, matchCurrentVersion);
|
||||||
|
}
|
||||||
|
logger.debug(
|
||||||
|
{ matchCurrentVersionStr, currentValue },
|
||||||
|
'Could not find a version to compare'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
18
lib/util/package-rules/datasources.ts
Normal file
18
lib/util/package-rules/datasources.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class DatasourcesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ datasource }: PackageRuleInputConfig,
|
||||||
|
{ matchDatasources }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchDatasources)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(datasource)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return matchDatasources.includes(datasource);
|
||||||
|
}
|
||||||
|
}
|
19
lib/util/package-rules/dep-types.ts
Normal file
19
lib/util/package-rules/dep-types.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class DepTypesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ depTypes, depType }: PackageRuleInputConfig,
|
||||||
|
{ matchDepTypes }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchDepTypes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result =
|
||||||
|
(depType && matchDepTypes.includes(depType)) ||
|
||||||
|
depTypes?.some((dt) => matchDepTypes.includes(dt));
|
||||||
|
return result ?? false;
|
||||||
|
}
|
||||||
|
}
|
19
lib/util/package-rules/files.spec.ts
Normal file
19
lib/util/package-rules/files.spec.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { FilesMatcher } from './files';
|
||||||
|
|
||||||
|
describe('util/package-rules/files', () => {
|
||||||
|
const fileMatcher = new FilesMatcher();
|
||||||
|
|
||||||
|
describe('match', () => {
|
||||||
|
it('should return false if packageFile is not defined', () => {
|
||||||
|
const result = fileMatcher.matches(
|
||||||
|
{
|
||||||
|
packageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchFiles: ['frontend/package.json'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
23
lib/util/package-rules/files.ts
Normal file
23
lib/util/package-rules/files.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class FilesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ packageFile, lockFiles }: PackageRuleInputConfig,
|
||||||
|
{ matchFiles }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchFiles)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(packageFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchFiles.some(
|
||||||
|
(fileName) =>
|
||||||
|
packageFile === fileName ||
|
||||||
|
(is.array(lockFiles) && lockFiles?.includes(fileName))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import type { PackageRuleInputConfig, UpdateType } from '../config/types';
|
import type { PackageRuleInputConfig, UpdateType } from '../../config/types';
|
||||||
import { ProgrammingLanguage } from '../constants';
|
import { ProgrammingLanguage } from '../../constants';
|
||||||
|
|
||||||
import { DockerDatasource } from '../modules/datasource/docker';
|
import { DockerDatasource } from '../../modules/datasource/docker';
|
||||||
import { OrbDatasource } from '../modules/datasource/orb';
|
import { OrbDatasource } from '../../modules/datasource/orb';
|
||||||
import { applyPackageRules } from './package-rules';
|
import { applyPackageRules } from './index';
|
||||||
|
|
||||||
type TestConfig = PackageRuleInputConfig & {
|
type TestConfig = PackageRuleInputConfig & {
|
||||||
x?: number;
|
x?: number;
|
||||||
|
@ -11,7 +11,7 @@ type TestConfig = PackageRuleInputConfig & {
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('util/package-rules', () => {
|
describe('util/package-rules/index', () => {
|
||||||
const config1: TestConfig = {
|
const config1: TestConfig = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
|
|
100
lib/util/package-rules/index.ts
Normal file
100
lib/util/package-rules/index.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import slugify from 'slugify';
|
||||||
|
import { mergeChildConfig } from '../../config';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import matchers from './matchers';
|
||||||
|
import { matcherOR } from './utils';
|
||||||
|
|
||||||
|
function matchesRule(
|
||||||
|
inputConfig: PackageRuleInputConfig,
|
||||||
|
packageRule: PackageRule
|
||||||
|
): boolean {
|
||||||
|
let positiveMatch = true;
|
||||||
|
let matchApplied = false;
|
||||||
|
// matches
|
||||||
|
for (const groupMatchers of matchers) {
|
||||||
|
const isMatch = matcherOR(
|
||||||
|
'matches',
|
||||||
|
groupMatchers,
|
||||||
|
inputConfig,
|
||||||
|
packageRule
|
||||||
|
);
|
||||||
|
|
||||||
|
// no rules are defined
|
||||||
|
if (is.nullOrUndefined(isMatch)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchApplied = true;
|
||||||
|
|
||||||
|
if (!is.truthy(isMatch)) {
|
||||||
|
positiveMatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a single match rule is defined --> assume to match everything
|
||||||
|
if (!matchApplied) {
|
||||||
|
positiveMatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing has been matched
|
||||||
|
if (!positiveMatch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// excludes
|
||||||
|
for (const groupExcludes of matchers) {
|
||||||
|
const isExclude = matcherOR(
|
||||||
|
'excludes',
|
||||||
|
groupExcludes,
|
||||||
|
inputConfig,
|
||||||
|
packageRule
|
||||||
|
);
|
||||||
|
|
||||||
|
// no rules are defined
|
||||||
|
if (is.nullOrUndefined(isExclude)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExclude) {
|
||||||
|
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`
|
||||||
|
);
|
||||||
|
for (const packageRule of packageRules) {
|
||||||
|
// 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;
|
||||||
|
}
|
18
lib/util/package-rules/languages.ts
Normal file
18
lib/util/package-rules/languages.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class LanguagesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ language }: PackageRuleInputConfig,
|
||||||
|
{ matchLanguages }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchLanguages)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(language)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return matchLanguages.includes(language);
|
||||||
|
}
|
||||||
|
}
|
18
lib/util/package-rules/managers.ts
Normal file
18
lib/util/package-rules/managers.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class ManagersMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ manager }: PackageRuleInputConfig,
|
||||||
|
{ matchManagers }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchManagers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(manager) || !manager) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return matchManagers.includes(manager);
|
||||||
|
}
|
||||||
|
}
|
35
lib/util/package-rules/matchers.ts
Normal file
35
lib/util/package-rules/matchers.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { BaseBranchesMatcher } from './base-branches';
|
||||||
|
import { CurrentVersionMatcher } from './current-version';
|
||||||
|
import { DatasourcesMatcher } from './datasources';
|
||||||
|
import { DepTypesMatcher } from './dep-types';
|
||||||
|
import { FilesMatcher } from './files';
|
||||||
|
import { LanguagesMatcher } from './languages';
|
||||||
|
import { ManagersMatcher } from './managers';
|
||||||
|
import { PackageNameMatcher } from './package-names';
|
||||||
|
import { PackagePatternsMatcher } from './package-patterns';
|
||||||
|
import { PackagePrefixesMatcher } from './package-prefixes';
|
||||||
|
import { PathsMatcher } from './paths';
|
||||||
|
import { SourceUrlPrefixesMatcher } from './sourceurl-prefixes';
|
||||||
|
import { SourceUrlsMatcher } from './sourceurls';
|
||||||
|
import type { MatcherApi } from './types';
|
||||||
|
import { UpdateTypesMatcher } from './update-types';
|
||||||
|
|
||||||
|
const matchers: MatcherApi[][] = [];
|
||||||
|
export default matchers;
|
||||||
|
|
||||||
|
// each manager under the same key will use a logical OR, if multiple matchers are applied AND will be used
|
||||||
|
matchers.push([
|
||||||
|
new PackageNameMatcher(),
|
||||||
|
new PackagePatternsMatcher(),
|
||||||
|
new PackagePrefixesMatcher(),
|
||||||
|
]);
|
||||||
|
matchers.push([new FilesMatcher()]);
|
||||||
|
matchers.push([new PathsMatcher()]);
|
||||||
|
matchers.push([new DepTypesMatcher()]);
|
||||||
|
matchers.push([new LanguagesMatcher()]);
|
||||||
|
matchers.push([new BaseBranchesMatcher()]);
|
||||||
|
matchers.push([new ManagersMatcher()]);
|
||||||
|
matchers.push([new DatasourcesMatcher()]);
|
||||||
|
matchers.push([new UpdateTypesMatcher()]);
|
||||||
|
matchers.push([new SourceUrlsMatcher(), new SourceUrlPrefixesMatcher()]);
|
||||||
|
matchers.push([new CurrentVersionMatcher()]);
|
33
lib/util/package-rules/package-names.spec.ts
Normal file
33
lib/util/package-rules/package-names.spec.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { PackageNameMatcher } from './package-names';
|
||||||
|
|
||||||
|
describe('util/package-rules/package-names', () => {
|
||||||
|
const packageNameMatcher = new PackageNameMatcher();
|
||||||
|
|
||||||
|
describe('match', () => {
|
||||||
|
it('should return false if packageFile is not defined', () => {
|
||||||
|
const result = packageNameMatcher.matches(
|
||||||
|
{
|
||||||
|
depName: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchPackageNames: ['@opentelemetry/http'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('exclude', () => {
|
||||||
|
it('should return false if packageFile is not defined', () => {
|
||||||
|
const result = packageNameMatcher.excludes(
|
||||||
|
{
|
||||||
|
depName: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
excludePackageNames: ['@opentelemetry/http'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
31
lib/util/package-rules/package-names.ts
Normal file
31
lib/util/package-rules/package-names.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class PackageNameMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ depName }: PackageRuleInputConfig,
|
||||||
|
{ matchPackageNames }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchPackageNames)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(depName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return matchPackageNames.includes(depName);
|
||||||
|
}
|
||||||
|
|
||||||
|
override excludes(
|
||||||
|
{ depName }: PackageRuleInputConfig,
|
||||||
|
{ excludePackageNames }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(excludePackageNames)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(depName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return excludePackageNames.includes(depName);
|
||||||
|
}
|
||||||
|
}
|
19
lib/util/package-rules/package-patterns.spec.ts
Normal file
19
lib/util/package-rules/package-patterns.spec.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { PackagePatternsMatcher } from './package-patterns';
|
||||||
|
|
||||||
|
describe('util/package-rules/package-patterns', () => {
|
||||||
|
const packageNameMatcher = new PackagePatternsMatcher();
|
||||||
|
|
||||||
|
describe('match', () => {
|
||||||
|
it('should return false if depName is not defined', () => {
|
||||||
|
const result = packageNameMatcher.matches(
|
||||||
|
{
|
||||||
|
depName: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchPackagePatterns: ['@opentelemetry/http'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
54
lib/util/package-rules/package-patterns.ts
Normal file
54
lib/util/package-rules/package-patterns.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { regEx } from '../regex';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
import { massagePattern } from './utils';
|
||||||
|
|
||||||
|
export class PackagePatternsMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ depName, updateType }: PackageRuleInputConfig,
|
||||||
|
{ matchPackagePatterns }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchPackagePatterns)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.undefined(depName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMatch = false;
|
||||||
|
for (const packagePattern of matchPackagePatterns) {
|
||||||
|
const packageRegex = regEx(massagePattern(packagePattern));
|
||||||
|
if (packageRegex.test(depName)) {
|
||||||
|
logger.trace(`${depName} matches against ${String(packageRegex)}`);
|
||||||
|
isMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
override excludes(
|
||||||
|
{ depName, updateType }: PackageRuleInputConfig,
|
||||||
|
{ excludePackagePatterns }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
// ignore lockFileMaintenance for backwards compatibility
|
||||||
|
if (is.undefined(excludePackagePatterns)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(depName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMatch = false;
|
||||||
|
for (const pattern of excludePackagePatterns) {
|
||||||
|
const packageRegex = regEx(massagePattern(pattern));
|
||||||
|
if (packageRegex.test(depName)) {
|
||||||
|
logger.trace(`${depName} matches against ${String(packageRegex)}`);
|
||||||
|
isMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isMatch;
|
||||||
|
}
|
||||||
|
}
|
33
lib/util/package-rules/package-prefixes.spec.ts
Normal file
33
lib/util/package-rules/package-prefixes.spec.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { PackagePrefixesMatcher } from './package-prefixes';
|
||||||
|
|
||||||
|
describe('util/package-rules/package-prefixes', () => {
|
||||||
|
const packagePrefixesMatcher = new PackagePrefixesMatcher();
|
||||||
|
|
||||||
|
describe('match', () => {
|
||||||
|
it('should return false if depName is not defined', () => {
|
||||||
|
const result = packagePrefixesMatcher.matches(
|
||||||
|
{
|
||||||
|
depName: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchPackagePrefixes: ['@opentelemetry'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('exclude', () => {
|
||||||
|
it('should return false if depName is not defined', () => {
|
||||||
|
const result = packagePrefixesMatcher.excludes(
|
||||||
|
{
|
||||||
|
depName: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
excludePackagePrefixes: ['@opentelemetry'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
33
lib/util/package-rules/package-prefixes.ts
Normal file
33
lib/util/package-rules/package-prefixes.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class PackagePrefixesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ depName }: PackageRuleInputConfig,
|
||||||
|
{ matchPackagePrefixes }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchPackagePrefixes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(depName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchPackagePrefixes.some((prefix) => depName.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
override excludes(
|
||||||
|
{ depName }: PackageRuleInputConfig,
|
||||||
|
{ excludePackagePrefixes }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(excludePackagePrefixes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(depName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return excludePackagePrefixes.some((prefix) => depName.startsWith(prefix));
|
||||||
|
}
|
||||||
|
}
|
19
lib/util/package-rules/paths.spec.ts
Normal file
19
lib/util/package-rules/paths.spec.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { PathsMatcher } from './paths';
|
||||||
|
|
||||||
|
describe('util/package-rules/paths', () => {
|
||||||
|
const pathsMatcher = new PathsMatcher();
|
||||||
|
|
||||||
|
describe('match', () => {
|
||||||
|
it('should return false if packageFile is not defined', () => {
|
||||||
|
const result = pathsMatcher.matches(
|
||||||
|
{
|
||||||
|
packageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matchPaths: ['opentelemetry/http'],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
24
lib/util/package-rules/paths.ts
Normal file
24
lib/util/package-rules/paths.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import minimatch from 'minimatch';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class PathsMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ packageFile }: PackageRuleInputConfig,
|
||||||
|
{ matchPaths }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchPaths)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(packageFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchPaths.some(
|
||||||
|
(rulePath) =>
|
||||||
|
packageFile.includes(rulePath) ||
|
||||||
|
minimatch(packageFile, rulePath, { dot: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
lib/util/package-rules/sourceurl-prefixes.ts
Normal file
22
lib/util/package-rules/sourceurl-prefixes.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class SourceUrlPrefixesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ sourceUrl }: PackageRuleInputConfig,
|
||||||
|
{ matchSourceUrlPrefixes }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchSourceUrlPrefixes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(sourceUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const upperCaseSourceUrl = sourceUrl?.toUpperCase();
|
||||||
|
|
||||||
|
return matchSourceUrlPrefixes.some((prefix) =>
|
||||||
|
upperCaseSourceUrl?.startsWith(prefix.toUpperCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
lib/util/package-rules/sourceurls.ts
Normal file
22
lib/util/package-rules/sourceurls.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class SourceUrlsMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ sourceUrl }: PackageRuleInputConfig,
|
||||||
|
{ matchSourceUrls }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchSourceUrls)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is.undefined(sourceUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const upperCaseSourceUrl = sourceUrl?.toUpperCase();
|
||||||
|
return matchSourceUrls.some(
|
||||||
|
(url) => upperCaseSourceUrl === url.toUpperCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
14
lib/util/package-rules/types.ts
Normal file
14
lib/util/package-rules/types.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
|
||||||
|
export type MatchType = 'matches' | 'excludes';
|
||||||
|
|
||||||
|
export interface MatcherApi {
|
||||||
|
matches(
|
||||||
|
inputConfig: PackageRuleInputConfig,
|
||||||
|
packageRule: PackageRule
|
||||||
|
): boolean | null;
|
||||||
|
excludes(
|
||||||
|
inputConfig: PackageRuleInputConfig,
|
||||||
|
packageRule: PackageRule
|
||||||
|
): boolean | null;
|
||||||
|
}
|
18
lib/util/package-rules/update-types.ts
Normal file
18
lib/util/package-rules/update-types.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import { Matcher } from './base';
|
||||||
|
|
||||||
|
export class UpdateTypesMatcher extends Matcher {
|
||||||
|
override matches(
|
||||||
|
{ updateType, isBump }: PackageRuleInputConfig,
|
||||||
|
{ matchUpdateTypes }: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
if (is.undefined(matchUpdateTypes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(is.truthy(updateType) && matchUpdateTypes.includes(updateType)) ||
|
||||||
|
(is.truthy(isBump) && matchUpdateTypes.includes('bump'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
lib/util/package-rules/utils.ts
Normal file
40
lib/util/package-rules/utils.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import is from '@sindresorhus/is';
|
||||||
|
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
|
||||||
|
import type { MatchType, MatcherApi } from './types';
|
||||||
|
|
||||||
|
export function matcherOR(
|
||||||
|
matchType: MatchType,
|
||||||
|
groupMatchers: MatcherApi[],
|
||||||
|
inputConfig: PackageRuleInputConfig,
|
||||||
|
packageRule: PackageRule
|
||||||
|
): boolean | null {
|
||||||
|
let positiveMatch = false;
|
||||||
|
let matchApplied = false;
|
||||||
|
for (const matcher of groupMatchers) {
|
||||||
|
let isMatch;
|
||||||
|
switch (matchType) {
|
||||||
|
case 'excludes':
|
||||||
|
isMatch = matcher.excludes(inputConfig, packageRule);
|
||||||
|
break;
|
||||||
|
case 'matches':
|
||||||
|
isMatch = matcher.matches(inputConfig, packageRule);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no rules are defined
|
||||||
|
if (is.nullOrUndefined(isMatch)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchApplied = true;
|
||||||
|
|
||||||
|
if (is.truthy(isMatch)) {
|
||||||
|
positiveMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchApplied ? positiveMatch : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function massagePattern(pattern: string): string {
|
||||||
|
return pattern === '^*$' || pattern === '*' ? '.*' : pattern;
|
||||||
|
}
|
Loading…
Reference in a new issue