Skip to content

Commit

Permalink
feat(packageRules)!: support regex or glob matching for all (#28591)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Kriese <[email protected]>
  • Loading branch information
rarkins and viceice committed May 6, 2024
1 parent 148d871 commit 00fe5d5
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 41 deletions.
16 changes: 16 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,8 @@ Instead you should do `> 13 months`.
Use this field if you want to limit a `packageRule` to certain `depType` values.
Invalid if used outside of a `packageRule`.

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### excludeDepNames

### excludeDepPatterns
Expand Down Expand Up @@ -2647,6 +2649,8 @@ The categories can be found in the [manager documentation](modules/manager/index
}
```

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### matchRepositories

Use this field to restrict rules to a particular repository. e.g.
Expand Down Expand Up @@ -2694,6 +2698,8 @@ This field also supports Regular Expressions if they begin and end with `/`. e.g
}
```

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### matchManagers

Use this field to restrict rules to a particular package manager. e.g.
Expand All @@ -2712,6 +2718,8 @@ Use this field to restrict rules to a particular package manager. e.g.

For the full list of available managers, see the [Supported Managers](modules/manager/index.md#supported-managers) documentation.

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### matchMessage

For log level remapping, use this field to match against the particular log messages.
Expand All @@ -2733,6 +2741,8 @@ Use this field to restrict rules to a particular datasource. e.g.
}
```

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### matchCurrentValue

This option is matched against the `currentValue` field of a dependency.
Expand Down Expand Up @@ -2867,6 +2877,8 @@ The following example matches any file in directories starting with `app/`:

It is recommended that you avoid using "negative" globs, like `**/!(package.json)`, because such patterns might still return true if they match against the lock file name (e.g. `package-lock.json`).

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### matchDepNames

This field behaves the same as `matchPackageNames` except it matches against `depName` instead of `packageName`.
Expand Down Expand Up @@ -3029,6 +3041,8 @@ Here's an example of where you use this to group together all packages from the
}
```

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

### matchUpdateTypes

Use `matchUpdateTypes` to match rules against types of updates.
Expand All @@ -3045,6 +3059,8 @@ For example to apply a special label to `major` updates:
}
```

For more details on supported syntax see Renovate's [string pattern matching documentation](./string-pattern-matching.md).

<!-- prettier-ignore -->
!!! warning
Packages that follow SemVer are allowed to make breaking changes in _any_ `0.x` version, even `patch` and `minor`.
Expand Down
11 changes: 5 additions & 6 deletions docs/usage/string-pattern-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ Renovate string matching syntax for some configuration options allows you, as us
- [`minimatch`](https://github.com/isaacs/minimatch) glob patterns, including exact strings matches
- regular expression (regex) patterns

The following fields support this pattern matching:

- `allowedEnv`
- `allowedHeaders`
- `autodiscoverProjects`
- `matchRepositories`
In cases where there are potentially multiple _inputs_, e.g. managers can have multiple categories, then the matcher will return `true` if _any_ of them match.

## Special case: Match everything

Expand Down Expand Up @@ -111,6 +106,10 @@ For example, the pattern `["/^abc/", "!/^abcd/", "!/abce/"]`:
If you find yourself in a situation where you need to positive-match a string which starts with `!`, then you need to do so using a regular expression pattern.
For example, `["/^!abc$/"]` will positively match against the string `"!abc"`.

One limitation of negative matching is when there may be multiple inputs to match against.
For example, a manager may have multiple categories, such as `java` and `docker`.
If you have a rule such as `"matchCategories": ["!docker"]` then this will return `true` because the `java` category satisfies this rule.

## Usage in Renovate configuration options

Renovate has evolved its approach to string pattern matching over time, but this means that existing configurations may have a mix of approaches and not be entirely consistent with each other.
Expand Down
10 changes: 2 additions & 8 deletions lib/util/package-rules/base-branches.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { getRegexPredicate } from '../string-match';
import { matchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class BaseBranchesMatcher extends Matcher {
Expand All @@ -16,12 +16,6 @@ export class BaseBranchesMatcher extends Matcher {
return false;
}

return matchBaseBranches.some((matchBaseBranch): boolean => {
const isAllowedPred = getRegexPredicate(matchBaseBranch);
if (isAllowedPred) {
return isAllowedPred(baseBranch);
}
return matchBaseBranch === baseBranch;
});
return matchRegexOrGlobList(baseBranch, matchBaseBranches);
}
}
3 changes: 2 additions & 1 deletion lib/util/package-rules/categories.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { anyMatchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class CategoriesMatcher extends Matcher {
Expand All @@ -15,6 +16,6 @@ export class CategoriesMatcher extends Matcher {
return false;
}

return matchCategories.some((value) => categories.includes(value));
return anyMatchRegexOrGlobList(categories, matchCategories);
}
}
3 changes: 2 additions & 1 deletion lib/util/package-rules/datasources.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { matchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class DatasourcesMatcher extends Matcher {
Expand All @@ -13,6 +14,6 @@ export class DatasourcesMatcher extends Matcher {
if (is.undefined(datasource)) {
return false;
}
return matchDatasources.includes(datasource);
return matchRegexOrGlobList(datasource, matchDatasources);
}
}
14 changes: 10 additions & 4 deletions lib/util/package-rules/dep-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { anyMatchRegexOrGlobList, matchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class DepTypesMatcher extends Matcher {
Expand All @@ -11,9 +12,14 @@ export class DepTypesMatcher extends Matcher {
return null;
}

const result =
(is.string(depType) && matchDepTypes.includes(depType)) ||
depTypes?.some((dt) => matchDepTypes.includes(dt));
return result ?? false;
if (depType) {
return matchRegexOrGlobList(depType, matchDepTypes);
}

if (depTypes) {
return anyMatchRegexOrGlobList(depTypes, matchDepTypes);
}

return false;
}
}
19 changes: 10 additions & 9 deletions lib/util/package-rules/files.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { minimatch } from '../minimatch';
import { anyMatchRegexOrGlobList, matchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class FileNamesMatcher extends Matcher {
Expand All @@ -15,13 +15,14 @@ export class FileNamesMatcher extends Matcher {
return false;
}

return matchFileNames.some(
(matchFileName) =>
minimatch(matchFileName, { dot: true }).match(packageFile) ||
(is.array(lockFiles) &&
lockFiles.some((lockFile) =>
minimatch(matchFileName, { dot: true }).match(lockFile),
)),
);
if (matchRegexOrGlobList(packageFile, matchFileNames)) {
return true;
}

if (is.array(lockFiles)) {
return anyMatchRegexOrGlobList(lockFiles, matchFileNames);
}

return false;
}
}
18 changes: 18 additions & 0 deletions lib/util/package-rules/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('util/package-rules/index', () => {
it('applies', () => {
const config: PackageRuleInputConfig = {
packageName: 'a',
updateType: 'minor',
isBump: true,
currentValue: '1.0.0',
packageRules: [
Expand Down Expand Up @@ -286,6 +287,23 @@ describe('util/package-rules/index', () => {
expect(res.x).toBe(1);
});

it('returns false if no depTypes', () => {
const config: TestConfig = {
packageRules: [
{
matchDepTypes: ['test'],
matchPackageNames: ['a'],
x: 1,
},
],
};
const input = { ...config, packageName: 'a' };
delete input.depType;
delete input.depTypes;
const res = applyPackageRules(input);
expect(res).toEqual(input);
});

it('filters managers with matching manager', () => {
const config: TestConfig = {
packageRules: [
Expand Down
5 changes: 3 additions & 2 deletions lib/util/package-rules/managers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { isCustomManager } from '../../modules/manager/custom';
import { matchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class ManagersMatcher extends Matcher {
Expand All @@ -15,8 +16,8 @@ export class ManagersMatcher extends Matcher {
return false;
}
if (isCustomManager(manager)) {
return matchManagers.includes(`custom.${manager}`);
return matchRegexOrGlobList(`custom.${manager}`, matchManagers);
}
return matchManagers.includes(manager);
return matchRegexOrGlobList(manager, matchManagers);
}
}
2 changes: 1 addition & 1 deletion lib/util/package-rules/package-patterns.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('util/package-rules/package-patterns', () => {
});

it('should fall back to matching depName', () => {
const result = packageNameMatcher.matches(
const result = packagePatternsMatcher.matches(
{
depName: 'abc',
packageName: 'def',
Expand Down
8 changes: 3 additions & 5 deletions lib/util/package-rules/sourceurls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { matchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class SourceUrlsMatcher extends Matcher {
Expand All @@ -10,13 +11,10 @@ export class SourceUrlsMatcher extends Matcher {
if (is.undefined(matchSourceUrls)) {
return null;
}
if (is.undefined(sourceUrl)) {
if (!sourceUrl) {
return false;
}

const upperCaseSourceUrl = sourceUrl?.toUpperCase();
return matchSourceUrls.some(
(url) => upperCaseSourceUrl === url.toUpperCase(),
);
return matchRegexOrGlobList(sourceUrl, matchSourceUrls);
}
}
13 changes: 9 additions & 4 deletions lib/util/package-rules/update-types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { anyMatchRegexOrGlobList } from '../string-match';
import { Matcher } from './base';

export class UpdateTypesMatcher extends Matcher {
Expand All @@ -10,9 +11,13 @@ export class UpdateTypesMatcher extends Matcher {
if (is.undefined(matchUpdateTypes)) {
return null;
}
return (
(is.truthy(updateType) && matchUpdateTypes.includes(updateType)) ||
(is.truthy(isBump) && matchUpdateTypes.includes('bump'))
);
if (!updateType) {
return false;
}
const toMatch = [updateType];
if (isBump) {
toMatch.push('bump');
}
return anyMatchRegexOrGlobList(toMatch, matchUpdateTypes);
}
}
23 changes: 23 additions & 0 deletions lib/util/string-match.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
anyMatchRegexOrGlobList,
getRegexPredicate,
matchRegexOrGlob,
matchRegexOrGlobList,
Expand Down Expand Up @@ -59,6 +60,28 @@ describe('util/string-match', () => {
});
});

describe('anyMatchRegexOrGlobList()', () => {
it('returns false if empty patterns', () => {
expect(anyMatchRegexOrGlobList(['test'], [])).toBeFalse();
});

it('returns false if empty inputs', () => {
expect(anyMatchRegexOrGlobList([], ['/test2/'])).toBeFalse();
});

it('returns true if both empty', () => {
expect(anyMatchRegexOrGlobList([], [])).toBeFalse();
});

it('returns true if any match with positive', () => {
expect(anyMatchRegexOrGlobList(['a', 'b'], ['b'])).toBeTrue();
});

it('returns true if any match with negative', () => {
expect(anyMatchRegexOrGlobList(['a', 'b'], ['!b'])).toBeTrue();
});
});

describe('getRegexPredicate()', () => {
it('allows valid regex pattern', () => {
expect(getRegexPredicate('/hello/')).not.toBeNull();
Expand Down
7 changes: 7 additions & 0 deletions lib/util/string-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export function matchRegexOrGlobList(
return true;
}

export function anyMatchRegexOrGlobList(
inputs: string[],
patterns: string[],
): boolean {
return inputs.some((input) => matchRegexOrGlobList(input, patterns));
}

export const UUIDRegex = regEx(
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
);
Expand Down

0 comments on commit 00fe5d5

Please sign in to comment.