Skip to content

Commit

Permalink
feat(package-rules): matchNewValue (#27374)
Browse files Browse the repository at this point in the history
  • Loading branch information
RahulGautamSingh committed Feb 27, 2024
1 parent 5585818 commit 7e7124e
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 0 deletions.
34 changes: 34 additions & 0 deletions docs/usage/configuration-options.md
Expand Up @@ -2821,6 +2821,40 @@ It is recommended that you avoid using "negative" globs, like `**/!(package.json

### matchDepPatterns

### matchNewValue

This option is matched against the `newValue` field of a dependency.

`matchNewValue` supports Regular Expressions which must begin and end with `/`.
For example, the following enforces that only `1.*` versions will be used:

```json
{
"packageRules": [
{
"matchPackagePatterns": ["io.github.resilience4j"],
"matchNewValue": "/^1\\./"
}
]
}
```

This field also supports a special negated regex syntax to ignore certain versions.
Use the syntax `!/ /` like this:

```json
{
"packageRules": [
{
"matchPackagePatterns": ["io.github.resilience4j"],
"matchNewValue": "!/^0\\./"
}
]
}
```

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

### matchPackageNames

Use this field if you want to have one or more exact name matches in your package rule.
Expand Down
11 changes: 11 additions & 0 deletions lib/config/options/index.ts
Expand Up @@ -1374,6 +1374,17 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
{
name: 'matchNewValue',
description:
'A regex to match against the raw `newValue` string of a dependency. Valid only within a `packageRules` object.',
type: 'string',
stage: 'package',
parents: ['packageRules'],
mergeable: true,
cli: false,
env: false,
},
{
name: 'matchSourceUrlPrefixes',
description:
Expand Down
2 changes: 2 additions & 0 deletions lib/config/types.ts
Expand Up @@ -359,6 +359,7 @@ export interface PackageRule
excludePackagePatterns?: string[];
excludePackagePrefixes?: string[];
excludeRepositories?: string[];
matchNewValue?: string;
matchCurrentValue?: string;
matchCurrentVersion?: string;
matchSourceUrlPrefixes?: string[];
Expand Down Expand Up @@ -498,6 +499,7 @@ export interface PackageRuleInputConfig extends Record<string, unknown> {
depTypes?: string[];
depName?: string;
packageName?: string | null;
newValue?: string | null;
currentValue?: string | null;
currentVersion?: string;
lockedVersion?: string;
Expand Down
29 changes: 29 additions & 0 deletions lib/config/validation.spec.ts
Expand Up @@ -129,6 +129,35 @@ describe('config/validation', () => {
expect(errors).toHaveLength(2);
});

it('catches invalid matchNewValue', async () => {
const config = {
packageRules: [
{
matchPackageNames: ['foo'],
matchNewValue: '/^2/',
enabled: true,
},
{
matchPackageNames: ['bar'],
matchNewValue: '^1',
enabled: true,
},
{
matchPackageNames: ['quack'],
matchNewValue: '<1.0.0',
enabled: true,
},
{
matchPackageNames: ['foo'],
matchNewValue: '/^2/i',
enabled: true,
},
],
};
const { errors } = await configValidation.validateConfig(false, config);
expect(errors).toHaveLength(2);
});

it('catches invalid matchCurrentVersion regex', async () => {
const config = {
packageRules: [
Expand Down
10 changes: 10 additions & 0 deletions lib/config/validation.ts
Expand Up @@ -285,6 +285,15 @@ export async function validateConfig(
topic: 'Configuration Error',
message: `Invalid regExp for ${currentPath}: \`${val}\``,
});
} else if (
key === 'matchNewValue' &&
is.string(val) &&
!getRegexPredicate(val)
) {
errors.push({
topic: 'Configuration Error',
message: `Invalid regExp for ${currentPath}: \`${val}\``,
});
} else if (key === 'timezone' && val !== null) {
const [validTimezone, errorMessage] = hasValidTimezone(val as string);
if (!validTimezone) {
Expand Down Expand Up @@ -386,6 +395,7 @@ export async function validateConfig(
'matchConfidence',
'matchCurrentAge',
'matchRepositories',
'matchNewValue',
];
if (key === 'packageRules') {
for (const [subIndex, packageRule] of val.entries()) {
Expand Down
2 changes: 2 additions & 0 deletions lib/util/package-rules/matchers.ts
Expand Up @@ -10,6 +10,7 @@ import { DepTypesMatcher } from './dep-types';
import { FileNamesMatcher } from './files';
import { ManagersMatcher } from './managers';
import { MergeConfidenceMatcher } from './merge-confidence';
import { NewValueMatcher } from './new-value';
import { PackageNameMatcher } from './package-names';
import { PackagePatternsMatcher } from './package-patterns';
import { PackagePrefixesMatcher } from './package-prefixes';
Expand Down Expand Up @@ -43,6 +44,7 @@ matchers.push([new DatasourcesMatcher()]);
matchers.push([new UpdateTypesMatcher()]);
matchers.push([new SourceUrlsMatcher(), new SourceUrlPrefixesMatcher()]);
matchers.push([new CurrentValueMatcher()]);
matchers.push([new NewValueMatcher()]);
matchers.push([new CurrentVersionMatcher()]);
matchers.push([new RepositoriesMatcher()]);
matchers.push([new CategoriesMatcher()]);
Expand Down
65 changes: 65 additions & 0 deletions lib/util/package-rules/new-value.spec.ts
@@ -0,0 +1,65 @@
import { NewValueMatcher } from './new-value';

describe('util/package-rules/new-value', () => {
const matcher = new NewValueMatcher();

describe('match', () => {
it('return null if non-regex', () => {
const result = matcher.matches(
{
newValue: '"~> 1.1.0"',
},
{
matchNewValue: '^v',
},
);
expect(result).toBeFalse();
});

it('return false for regex version non match', () => {
const result = matcher.matches(
{
newValue: '"~> 1.1.0"',
},
{
matchNewValue: '/^v/',
},
);
expect(result).toBeFalse();
});

it('case insensitive match', () => {
const result = matcher.matches(
{
newValue: '"V1.1.0"',
},
{
matchNewValue: '/^"v/i',
},
);
expect(result).toBeTrue();
});

it('return true for regex version match', () => {
const result = matcher.matches(
{
newValue: '"~> 0.1.0"',
},
{
matchNewValue: '/^"/',
},
);
expect(result).toBeTrue();
});

it('return false for now value', () => {
const result = matcher.matches(
{},
{
matchNewValue: '/^v?[~ -]?0/',
},
);
expect(result).toBeFalse();
});
});
});
31 changes: 31 additions & 0 deletions lib/util/package-rules/new-value.ts
@@ -0,0 +1,31 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { logger } from '../../logger';
import { getRegexPredicate } from '../string-match';
import { Matcher } from './base';

export class NewValueMatcher extends Matcher {
override matches(
{ newValue }: PackageRuleInputConfig,
{ matchNewValue }: PackageRule,
): boolean | null {
if (is.undefined(matchNewValue)) {
return null;
}
const matchNewValuePred = getRegexPredicate(matchNewValue);

if (!matchNewValuePred) {
logger.debug(
{ matchNewValue },
'matchNewValue should be a regex, starting and ending with `/`',
);
return false;
}

if (!newValue) {
return false;
}

return matchNewValuePred(newValue);
}
}

0 comments on commit 7e7124e

Please sign in to comment.