Skip to content

Commit

Permalink
feat(packageRules): add merge confidence matcher (#21049)
Browse files Browse the repository at this point in the history
Co-authored-by: Rhys Arkins <[email protected]>
Co-authored-by: HonkingGoose <[email protected]>
Co-authored-by: Michael Kriese <[email protected]>
  • Loading branch information
4 people authored Mar 21, 2023
1 parent b250220 commit 1615d26
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 3 deletions.
33 changes: 33 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,39 @@ For example to apply a special label for Major updates:
}
```

### matchConfidence

<!-- prettier-ignore -->
!!! warning
This configuration option needs a Mend API key, and is in private beta testing only.
API keys are not available for free or via the `renovatebot/renovate` repository.

For example to group high merge confidence updates:

```json
{
"packageRules": [
{
"matchConfidence": ["high", "very high"],
"groupName": "high merge confidence"
}
]
}
```

Tokens can be configured via `hostRules` using the `"merge-confidence"` `hostType`:

```json
{
"hostRules": [
{
"hostType": "merge-confidence",
"token": "********"
}
]
}
```

### customChangelogUrl

Use this field to set the source URL for a package, including overriding an existing one.
Expand Down
15 changes: 15 additions & 0 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,21 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
{
name: 'matchConfidence',
description:
'Merge confidence levels to match against (`low`, `neutral`, `high`, `very high`). Valid only within `packageRules` object.',
type: 'array',
subType: 'string',
allowedValues: ['low', 'neutral', 'high', 'very high'],
allowString: true,
stage: 'package',
parent: 'packageRules',
mergeable: true,
cli: false,
env: false,
experimental: true,
},
{
name: 'matchUpdateTypes',
description:
Expand Down
3 changes: 3 additions & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { LogLevel } from 'bunyan';
import type { PlatformId } from '../constants';
import type { HostRule } from '../types';
import type { GitNoVerifyOption } from '../util/git/types';
import type { MergeConfidence } from '../util/merge-confidence/types';

export type RenovateConfigStage =
| 'global'
Expand Down Expand Up @@ -328,6 +329,7 @@ export interface PackageRule
matchSourceUrlPrefixes?: string[];
matchSourceUrls?: string[];
matchUpdateTypes?: UpdateType[];
matchConfidence?: MergeConfidence[];
registryUrls?: string[] | null;
}

Expand Down Expand Up @@ -458,6 +460,7 @@ export interface PackageRuleInputConfig extends Record<string, unknown> {
currentVersion?: string;
lockedVersion?: string;
updateType?: UpdateType;
mergeConfidenceLevel?: MergeConfidence | undefined;
isBump?: boolean;
sourceUrl?: string | null;
language?: string;
Expand Down
1 change: 1 addition & 0 deletions lib/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export async function validateConfig(
'matchSourceUrlPrefixes',
'matchSourceUrls',
'matchUpdateTypes',
'matchConfidence',
];
if (key === 'packageRules') {
for (const [subIndex, packageRule] of val.entries()) {
Expand Down
2 changes: 2 additions & 0 deletions lib/modules/manager/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
import type { ProgrammingLanguage } from '../../constants';
import type { ModuleApi, RangeStrategy, SkipReason } from '../../types';
import type { FileChange } from '../../util/git/types';
import type { MergeConfidence } from '../../util/merge-confidence/types';

export type Result<T> = T | Promise<T>;

Expand Down Expand Up @@ -94,6 +95,7 @@ export interface LookupUpdate {
pendingVersions?: string[];
newVersion?: string;
updateType?: UpdateType;
mergeConfidenceLevel?: MergeConfidence | undefined;
userStrings?: Record<string, string>;
checksumUrl?: string;
downloadUrl?: string;
Expand Down
55 changes: 55 additions & 0 deletions lib/util/package-rules/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PackageRuleInputConfig, UpdateType } from '../../config/types';
import { DockerDatasource } from '../../modules/datasource/docker';
import { OrbDatasource } from '../../modules/datasource/orb';
import type { MergeConfidence } from '../merge-confidence/types';
import { applyPackageRules } from './index';

type TestConfig = PackageRuleInputConfig & {
Expand Down Expand Up @@ -625,6 +626,60 @@ describe('util/package-rules/index', () => {
expect(res.x).toBeUndefined();
});

it('matches matchConfidence', () => {
const config: TestConfig = {
packageRules: [
{
matchConfidence: ['high'],
x: 1,
},
],
};
const dep = {
depType: 'dependencies',
depName: 'a',
mergeConfidenceLevel: 'high' as MergeConfidence,
};
const res = applyPackageRules({ ...config, ...dep });
expect(res.x).toBe(1);
});

it('non-matches matchConfidence', () => {
const config: TestConfig = {
packageRules: [
{
matchConfidence: ['high'],
x: 1,
},
],
};
const dep = {
depType: 'dependencies',
depName: 'a',
mergeConfidenceLevel: 'low' as MergeConfidence,
};
const res = applyPackageRules({ ...config, ...dep });
expect(res.x).toBeUndefined();
});

it('does not match matchConfidence when there is no mergeConfidenceLevel', () => {
const config: TestConfig = {
packageRules: [
{
matchConfidence: ['high'],
x: 1,
},
],
};
const dep = {
depType: 'dependencies',
depName: 'a',
mergeConfidenceLevel: undefined,
};
const res = applyPackageRules({ ...config, ...dep });
expect(res.x).toBeUndefined();
});

it('filters naked depType', () => {
const config: TestConfig = {
packageRules: [
Expand Down
2 changes: 2 additions & 0 deletions lib/util/package-rules/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DepTypesMatcher } from './dep-types';
import { FilesMatcher } from './files';
import { LanguagesMatcher } from './languages';
import { ManagersMatcher } from './managers';
import { MergeConfidenceMatcher } from './merge-confidence';
import { PackageNameMatcher } from './package-names';
import { PackagePatternsMatcher } from './package-patterns';
import { PackagePrefixesMatcher } from './package-prefixes';
Expand Down Expand Up @@ -36,6 +37,7 @@ matchers.push([new BaseBranchesMatcher()]);
matchers.push([new ManagersMatcher()]);
matchers.push([new DatasourcesMatcher()]);
matchers.push([new UpdateTypesMatcher()]);
matchers.push([new MergeConfidenceMatcher()]);
matchers.push([new SourceUrlsMatcher(), new SourceUrlPrefixesMatcher()]);
matchers.push([new CurrentValueMatcher()]);
matchers.push([new CurrentVersionMatcher()]);
19 changes: 19 additions & 0 deletions lib/util/package-rules/merge-confidence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import is from '@sindresorhus/is';
import type { PackageRule, PackageRuleInputConfig } from '../../config/types';
import { Matcher } from './base';

export class MergeConfidenceMatcher extends Matcher {
override matches(
{ mergeConfidenceLevel }: PackageRuleInputConfig,
{ matchConfidence }: PackageRule
): boolean | null {
if (is.nullOrUndefined(matchConfidence)) {
return null;
}
return (
is.array(matchConfidence) &&
is.nonEmptyString(mergeConfidenceLevel) &&
matchConfidence.includes(mergeConfidenceLevel)
);
}
}
16 changes: 14 additions & 2 deletions lib/workers/repository/process/lookup/generate.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import is from '@sindresorhus/is';
import { logger } from '../../../../logger';
import type { Release } from '../../../../modules/datasource';
import type { LookupUpdate } from '../../../../modules/manager/types';
import type { VersioningApi } from '../../../../modules/versioning';
import type { RangeStrategy } from '../../../../types';
import { getMergeConfidenceLevel } from '../../../../util/merge-confidence';
import type { LookupUpdateConfig } from './types';
import { getUpdateType } from './update-type';

export function generateUpdate(
export async function generateUpdate(
config: LookupUpdateConfig,
versioning: VersioningApi,
rangeStrategy: RangeStrategy,
currentVersion: string,
bucket: string,
release: Release
): LookupUpdate {
): Promise<LookupUpdate> {
const newVersion = release.version;
const update: LookupUpdate = {
bucket,
Expand Down Expand Up @@ -77,6 +79,16 @@ export function generateUpdate(
update.updateType =
update.updateType ??
getUpdateType(config, versioning, currentVersion, newVersion);
const { datasource, packageName, packageRules } = config;
if (packageRules?.some((pr) => is.nonEmptyArray(pr.matchConfidence))) {
update.mergeConfidenceLevel = await getMergeConfidenceLevel(
datasource,
packageName,
currentVersion,
newVersion,
update.updateType
);
}
if (!versioning.isVersion(update.newValue)) {
update.isRange = true;
}
Expand Down
98 changes: 98 additions & 0 deletions lib/workers/repository/process/lookup/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as hostRules from '../../../../../lib/util/host-rules';
import { Fixtures } from '../../../../../test/fixtures';
import * as httpMock from '../../../../../test/http-mock';
import { getConfig, partial } from '../../../../../test/util';
Expand All @@ -14,7 +15,11 @@ import { id as gitVersioningId } from '../../../../modules/versioning/git';
import { id as npmVersioningId } from '../../../../modules/versioning/npm';
import { id as pep440VersioningId } from '../../../../modules/versioning/pep440';
import { id as poetryVersioningId } from '../../../../modules/versioning/poetry';
import type { HostRule } from '../../../../types';
import * as memCache from '../../../../util/cache/memory';
import * as githubGraphql from '../../../../util/github/graphql';
import { initConfig, resetConfig } from '../../../../util/merge-confidence';
import * as McApi from '../../../../util/merge-confidence';
import type { LookupUpdateConfig } from './types';
import * as lookup from '.';

Expand Down Expand Up @@ -67,6 +72,7 @@ describe('workers/repository/process/lookup/index', () => {
// TODO: fix mocks
afterEach(() => {
httpMock.clear(false);
hostRules.clear();
});

describe('.lookupUpdates()', () => {
Expand Down Expand Up @@ -2082,5 +2088,97 @@ describe('workers/repository/process/lookup/index', () => {
},
]);
});

describe('handles merge confidence', () => {
const defaultApiBaseUrl = 'https://badges.renovateapi.com/';
const getMergeConfidenceSpy = jest.spyOn(
McApi,
'getMergeConfidenceLevel'
);
const hostRule: HostRule = {
hostType: 'merge-confidence',
token: 'some-token',
};

beforeEach(() => {
hostRules.add(hostRule);
initConfig();
memCache.reset();
});

afterEach(() => {
resetConfig();
});

it('gets a merge confidence level for a given update when corresponding packageRule is in use', async () => {
const datasource = NpmDatasource.id;
const packageName = 'webpack';
const newVersion = '3.8.1';
const currentValue = '3.7.0';
config.packageRules = [{ matchConfidence: ['high'] }];
config.currentValue = currentValue;
config.packageName = packageName;
config.datasource = datasource;
httpMock
.scope('https://registry.npmjs.org')
.get('/webpack')
.reply(200, webpackJson);
httpMock
.scope(defaultApiBaseUrl)
.get(
`/api/mc/json/${datasource}/${packageName}/${currentValue}/${newVersion}`
)
.reply(200, { confidence: 'high' });

const lookupUpdates = (await lookup.lookupUpdates(config)).updates;

expect(lookupUpdates).toMatchObject([
{
mergeConfidenceLevel: `high`,
},
]);
});

it('does not get a merge confidence level when no packageRule is set', async () => {
config.currentValue = '3.7.0';
config.packageName = 'webpack';
config.datasource = NpmDatasource.id;
httpMock
.scope('https://registry.npmjs.org')
.get('/webpack')
.reply(200, webpackJson);

const lookupUpdates = (await lookup.lookupUpdates(config)).updates;

expect(getMergeConfidenceSpy).toHaveBeenCalledTimes(0);
expect(lookupUpdates).not.toMatchObject([
{
mergeConfidenceLevel: expect.anything(),
},
]);
});

it('does not set merge confidence value when API is not in use', async () => {
const datasource = NpmDatasource.id;
config.packageRules = [{ matchConfidence: ['high'] }];
config.currentValue = '3.7.0';
config.packageName = 'webpack';
config.datasource = datasource;
hostRules.clear(); // reset merge confidence
initConfig();
httpMock
.scope('https://registry.npmjs.org')
.get('/webpack')
.reply(200, webpackJson);

const lookupUpdates = (await lookup.lookupUpdates(config)).updates;

expect(lookupUpdates).not.toMatchObject([
{
mergeConfidenceLevel: expect.anything(),
},
]);
});
});
});
});
2 changes: 1 addition & 1 deletion lib/workers/repository/process/lookup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export async function lookupUpdates(
return res;
}
const newVersion = release.version;
const update = generateUpdate(
const update = await generateUpdate(
config,
versioning,
// TODO #7154
Expand Down

0 comments on commit 1615d26

Please sign in to comment.