import { hostRules } from '~test/util.ts'; import { GlobalConfig } from '../../config/global.ts'; import type { PackageRuleInputConfig, UpdateType } from '../../config/types.ts'; import { MISSING_API_CREDENTIALS } from '../../constants/error-messages.ts'; import { DockerDatasource } from '../../modules/datasource/docker/index.ts'; import { OrbDatasource } from '../../modules/datasource/orb/index.ts'; import type { HostRule } from '../../types/index.ts'; import type { MergeConfidence } from '../merge-confidence/types.ts'; import { applyPackageRules } from './index.ts'; type TestConfig = PackageRuleInputConfig & { x?: number; y?: number; groupName?: string; }; describe('util/package-rules/index', () => { afterEach(() => { GlobalConfig.reset(); }); const config1: TestConfig = { foo: 'bar', packageRules: [ { matchPackageNames: ['a', 'b', 'xyz/**', '!xyz/foo**'], // @ts-expect-error -- testing x: 2, }, { matchPackageNames: ['/a/', '/b/', '!aa', '!/d/'], // @ts-expect-error -- testing y: 2, }, { matchPackageNames: ['xyz/**', '!xyz/foo'], groupName: 'xyz', }, ], }; it('applies', async () => { const config: PackageRuleInputConfig = { packageName: 'a', updateType: 'minor', isBump: true, currentValue: '1.0.0', packageRules: [ { matchPackageNames: ['*'], matchCurrentVersion: '<= 2.0.0', }, { matchPackageNames: ['b'], matchCurrentVersion: '<= 2.0.0', }, { matchUpdateTypes: ['bump'], labels: ['bump'], }, { matchPackageNames: ['b', '!a'], }, { matchCurrentVersion: '<= 2.0.0', }, ], }; await expect(applyPackageRules(config)).resolves.toEqual({ ...config, labels: ['bump'], }); }); it('applies both rules for a', async () => { const dep = { packageName: 'a', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.x).toBe(2); expect(res.y).toBe(2); expect(res.groupName).toBeUndefined(); }); it('applies both rules for b', async () => { const dep = { packageName: 'b', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.x).toBe(2); expect(res.y).toBe(2); expect(res.groupName).toBeUndefined(); }); it('applies the second rule', async () => { const dep = { packageName: 'abc', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); expect(res.y).toBe(2); expect(res.groupName).toBeUndefined(); }); it('applies matchPackageNames', async () => { const dep = { packageName: 'xyz/foo', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.groupName).toBeUndefined(); }); it('applies the second second rule', async () => { const dep = { packageName: 'bc', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); expect(res.y).toBe(2); }); it('excludes package name', async () => { const dep = { packageName: 'aa', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); expect(res.y).toBeUndefined(); }); it('excludes package pattern', async () => { const dep = { packageName: 'bcd', }; const res = await applyPackageRules({ ...config1, ...dep }); expect(res.x).toBeUndefined(); expect(res.y).toBeUndefined(); }); it('ignores patterns if lock file maintenance', async () => { const dep = { automerge: true, updateType: 'lockFileMaintenance' as UpdateType, packageRules: [ { matchPackageNames: ['!/^foo/'], automerge: false, }, ], }; // This should not match const res = await applyPackageRules(dep); expect(res.automerge).toBeTrue(); }); it('do apply rule with matchPackageName', async () => { const dep = { automerge: true, updateType: 'lockFileMaintenance' as UpdateType, packageRules: [ { matchPackageNames: ['foo'], automerge: false, }, ], }; const res = await applyPackageRules(dep); expect(res.automerge).toBeTrue(); const res2 = await applyPackageRules({ ...dep, packageName: 'foo' }); expect(res2.automerge).toBeFalse(); }); it('sets skipReason=package-rules if enabled=false', async () => { const dep: any = { depName: 'foo', packageRules: [ { enabled: false, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeFalse(); expect(res.skipReason).toBe('package-rules'); expect(res.skipStage).toBe('datasource-merge'); }); it('unsets skipReason=package-rules if enabled=true', async () => { const dep: any = { depName: 'foo', packageRules: [ { enabled: false, }, { enabled: true, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeTrue(); expect(res.skipReason).toBeUndefined(); expect(res.skipStage).toBeUndefined(); }); it('does not set skipReason=package-rules if the last packageRule has force.enabled=true', async () => { const dep: any = { depName: 'foo', packageRules: [ { enabled: false, }, { // this is a vulnerability alert force: { enabled: true, }, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeTrue(); expect(res.skipReason).toBeUndefined(); expect(res.skipStage).toBeUndefined(); }); it('does not set skipReason=package-rules if the last packageRule has force.enabled=true (if config.enabled=false)', async () => { const dep: any = { depName: 'foo', enabled: false, packageRules: [ { enabled: false, }, { // this is a vulnerability alert force: { enabled: true, }, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeTrue(); expect(res.skipReason).toBeUndefined(); expect(res.skipStage).toBeUndefined(); }); it('does not set skipReason=package-rules if the last packageRule has enabled=true (if config.force.enabled=false)', async () => { const dep: any = { depName: 'foo', enabled: false, // for instance, if we've already merged a vulnrability alert force: { enabled: true, }, packageRules: [ { enabled: false, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeTrue(); expect(res.skipReason).toBeUndefined(); expect(res.skipStage).toBeUndefined(); }); // TODO naming it('sets skipReason=package-rules if the last packageRule has force.enabled=false (if config.force.enabled=false)', async () => { const dep: any = { depName: 'foo', enabled: false, // for instance, if we've already merged a vulnerability alert force: { enabled: true, }, packageRules: [ { enabled: false, }, { force: { enabled: false, }, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeFalse(); expect(res.skipReason).toBe('package-rules'); expect(res.skipStage).toBe('datasource-merge'); }); it('sets skipReason=package-rules if the last packageRule has force.enabled=false', async () => { const dep: any = { depName: 'foo', packageRules: [ { enabled: true, }, { force: { enabled: false, }, }, ], }; const res = await applyPackageRules(dep, 'datasource-merge'); expect(res.enabled).toBeFalse(); expect(res.skipReason).toBe('package-rules'); expect(res.skipStage).toBe('datasource-merge'); }); it('skips skipReason=package-rules if enabled=true', async () => { const dep: any = { enabled: false, depName: 'foo', packageRules: [ { enabled: false, }, ], }; const res = await applyPackageRules(dep); expect(res.skipReason).toBeUndefined(); }); it('matches anything if missing inclusive rules', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['!foo'], // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'foo', }); expect(res1.x).toBeUndefined(); const res2 = await applyPackageRules({ ...config, packageName: 'bar', }); expect(res2.x).toBeDefined(); }); it('supports inclusive or', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['neutrino', '/^@neutrino\\//'], // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'neutrino', }); expect(res1.x).toBeDefined(); const res2 = await applyPackageRules({ ...config, packageName: '@neutrino/something', }); expect(res2.x).toBeDefined(); }); it('filters requested depType', async () => { const config: TestConfig = { packageRules: [ { matchDepTypes: ['dependencies', 'peerDependencies'], matchPackageNames: ['a'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('filters from list of requested depTypes', async () => { const config: TestConfig = { packageRules: [ { matchDepTypes: ['test'], matchPackageNames: ['a'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depTypes: ['build', 'test'], packageName: 'a', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('returns false if no depTypes', async () => { const config: TestConfig = { packageRules: [ { matchDepTypes: ['test'], matchPackageNames: ['a'], // @ts-expect-error -- testing x: 1, }, ], }; const input = { ...config, packageName: 'a' }; delete input.depType; delete input.depTypes; const res = await applyPackageRules(input); expect(res).toEqual(input); }); it('filters managers with matching manager', async () => { const config: TestConfig = { packageRules: [ { matchManagers: ['npm', 'meteor'], matchPackageNames: ['node'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', manager: 'meteor', packageName: 'node', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('filters managers with non-matching manager', async () => { const config: TestConfig = { packageRules: [ { matchManagers: ['dockerfile', 'npm'], matchPackageNames: ['node'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', language: 'python', categories: ['python'], manager: 'pipenv', packageName: 'node', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('filters categories with matching category', async () => { const config: TestConfig = { packageRules: [ { matchCategories: ['node'], matchPackageNames: ['node'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', categories: ['javascript', 'node'], manager: 'meteor', packageName: 'node', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('filters categories with non-matching category', async () => { const config: TestConfig = { packageRules: [ { matchCategories: ['docker'], matchPackageNames: ['node'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', categories: ['python'], manager: 'pipenv', packageName: 'node', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('filters categories with undefined category', async () => { const config: TestConfig = { packageRules: [ { matchCategories: ['docker'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', manager: 'pipenv', packageName: 'node', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('filters datasources with matching datasource', async () => { const config: TestConfig = { packageRules: [ { matchDatasources: [OrbDatasource.id, DockerDatasource.id], // @ts-expect-error -- testing x: 1, }, { matchDatasources: [DockerDatasource.id], // @ts-expect-error -- testing y: 1, }, ], }; const dep = { depType: 'dependencies', datasource: OrbDatasource.id, baseBranch: 'master', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); expect(res.y).toBeUndefined(); }); it('filters branches with matching branch', async () => { const config: TestConfig = { packageRules: [ { matchBaseBranches: ['master', 'staging'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', datasource: OrbDatasource.id, baseBranch: 'master', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('filters datasources with non-matching datasource', async () => { const config: TestConfig = { packageRules: [ { matchDatasources: [OrbDatasource.id], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', baseBranch: 'staging', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('filters branches with non-matching branch', async () => { const config: TestConfig = { packageRules: [ { matchBaseBranches: ['master'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', baseBranch: 'staging', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('filters branches with matching branch regex', async () => { const config: TestConfig = { packageRules: [ { matchBaseBranches: ['/^release\\/.*/'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', datasource: OrbDatasource.id, baseBranch: 'release/5.8', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('filters branches with non-matching branch regex', async () => { const config: TestConfig = { packageRules: [ { matchBaseBranches: ['/^release\\/.*/'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', datasource: OrbDatasource.id, baseBranch: 'master', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('filters updateType', async () => { const config: TestConfig = { packageRules: [ { matchUpdateTypes: ['minor', 'patch'], // @ts-expect-error -- testing x: 1, }, { matchUpdateTypes: ['minor'], // @ts-expect-error -- testing y: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); expect(res.y).toBeUndefined(); }); it('matches matchSourceUrls with glob', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar**', 'https://github.com/renovatebot/**', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/presets', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('non-matches matchSourceUrls with globs', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar**', 'https://github.com/renovatebot/**', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/vuejs/vue', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('handles matchSourceUrls when missing sourceUrl', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar**', 'https://github.com/renovatebot/**', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('matches matchSourceUrls', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar', 'https://github.com/renovatebot/presets', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/presets', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('non-matches matchSourceUrls', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar', 'https://github.com/react/react', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/react/react-native', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('handles matchRegistryUrls when missing registryUrls', async () => { const config: TestConfig = { packageRules: [ { matchRegistryUrls: [ 'https://registry.example.com/**', 'https://private.registry.com/**', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('matches matchRegistryUrls', async () => { const config: TestConfig = { packageRules: [ { matchRegistryUrls: [ 'https://registry.example.com', 'https://private.registry.com/npm', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, registryUrls: ['https://private.registry.com/npm'], }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('non-matches matchRegistryUrls', async () => { const config: TestConfig = { packageRules: [ { matchRegistryUrls: [ 'https://registry.example.com', 'https://private.registry.com/npm', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, registryUrls: ['https://registry.npmjs.org'], }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); describe('matchConfidence', () => { const hostRule: HostRule = { hostType: 'merge-confidence', token: 'some-token', }; beforeEach(() => { hostRules.clear(); hostRules.add(hostRule); }); afterEach(() => { GlobalConfig.reset(); }); it('matches matchConfidence', async () => { const config: TestConfig = { packageRules: [ { matchConfidence: ['high'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', mergeConfidenceLevel: 'high' as MergeConfidence, }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('non-matches matchConfidence', async () => { const config: TestConfig = { packageRules: [ { matchConfidence: ['high'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', mergeConfidenceLevel: 'low' as MergeConfidence, }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('does not match matchConfidence when there is no mergeConfidenceLevel', async () => { const config: TestConfig = { packageRules: [ { matchConfidence: ['high'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', mergeConfidenceLevel: undefined, }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('throws when unauthenticated', async () => { const config: TestConfig = { packageRules: [ { matchUpdateTypes: ['major'], matchConfidence: ['high'], // @ts-expect-error -- testing x: 1, }, ], }; hostRules.clear(); let error = new Error(); try { await applyPackageRules(config); } catch (err) { error = err; } expect(error).toMatchObject(new Error(MISSING_API_CREDENTIALS)); expect(error.validationError).toBe('Missing credentials'); expect(error.validationMessage).toBe( 'The `matchConfidence` matcher in `packageRules` requires authentication. Please refer to the [documentation](https://docs.renovatebot.com/configuration-options/#packagerulesmatchconfidence) and add the required host rule.', ); }); it('does not throw when unauthenticated on platform=local', async () => { GlobalConfig.set({ platform: 'local', }); const config: TestConfig = { packageRules: [ { matchUpdateTypes: ['major'], matchConfidence: ['high'], }, ], }; hostRules.clear(); await expect(applyPackageRules(config)).resolves.not.toThrow(); }); it('does not apply the packageRule on platform=local', async () => { GlobalConfig.set({ platform: 'local', }); const config: TestConfig = { packageRules: [ { matchUpdateTypes: ['major'], matchConfidence: ['high'], // @ts-expect-error -- testing x: 1, }, ], }; hostRules.clear(); const res = await applyPackageRules(config); expect(res.x).toBeUndefined(); }); it('uses productLinks.documentation in error message URL', async () => { GlobalConfig.set({ productLinks: { documentation: 'https://custom.example.com/' }, }); hostRules.clear(); const config: TestConfig = { packageRules: [ { matchUpdateTypes: ['major'], matchConfidence: ['high'], // @ts-expect-error -- testing x: 1, }, ], }; await expect(applyPackageRules(config)).rejects.toMatchObject({ validationMessage: 'The `matchConfidence` matcher in `packageRules` requires authentication. Please refer to the [documentation](https://custom.example.com/configuration-options/#packagerulesmatchconfidence) and add the required host rule.', }); }); }); it('filters naked depType', async () => { const config: TestConfig = { packageRules: [ { matchDepTypes: ['dependencies', 'peerDependencies'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('filters out unrequested depType', async () => { const config: TestConfig = { packageRules: [ { matchDepTypes: ['dependencies', 'peerDependencies'], matchPackageNames: ['a'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'devDependencies', packageName: 'a', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('checks if matchCurrentVersion selector is valid and satisfies the condition on range overlap', async () => { const config: TestConfig = { versioning: 'semver', packageRules: [ { matchPackageNames: ['test'], matchCurrentVersion: '<= 2.0.0', // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '^1.0.0', currentVersion: '1.0.3', }); expect(res1.x).toBeDefined(); const res2 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '^1.0.0', }); expect(res2.x).toBeUndefined(); const res3 = await applyPackageRules({ ...config, packageName: 'test', lockedVersion: '^1.0.0', }); expect(res3.x).toBeUndefined(); }); it('checks if matchCurrentVersion selector is valid and satisfies the condition on pinned to range overlap', async () => { const config: TestConfig = { versioning: 'semver', packageRules: [ { matchPackageNames: ['test'], matchCurrentVersion: '>= 2.0.0', // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '2.4.6', currentVersion: '2.4.6', }); expect(res1.x).toBeDefined(); }); it('checks if matchCurrentVersion selector is a version and matches if currentValue is a range', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['test'], matchCurrentVersion: '2.1.0', // @ts-expect-error -- testing x: 1, }, ], versioning: 'npm', }; const res1 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '^2.0.0', }); expect(res1.x).toBeDefined(); const res2 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '~2.0.0', }); expect(res2.x).toBeUndefined(); }); it('checks if matchCurrentVersion selector works with static values', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['test'], matchCurrentVersion: '4.6.0', // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '4.6.0', currentVersion: '4.6.0', }); expect(res1.x).toBeDefined(); }); it('checks if matchCurrentVersion selector works with regular expressions', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['test'], matchCurrentVersion: '/^4/', // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '4.6.0', currentVersion: '4.6.0', }); const res2 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '5.6.0', currentVersion: '5.6.0', }); expect(res1.x).toBeDefined(); expect(res2.x).toBeUndefined(); }); it('checks if matchCurrentVersion selector works with negated regular expressions', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['test'], matchCurrentVersion: '!/^4/', // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '4.6.0', currentVersion: '4.6.0', }); const res2 = await applyPackageRules({ ...config, packageName: 'test', currentValue: '5.6.0', currentVersion: '5.6.0', }); expect(res1.x).toBeUndefined(); expect(res2.x).toBeDefined(); }); it('matches packageFiles', async () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', packageRules: [ { matchFileNames: ['package.json'], // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', }); expect(res1.x).toBeUndefined(); config.packageFile = 'package.json'; const res2 = await applyPackageRules({ ...config, packageName: 'test', }); expect(res2.x).toBeDefined(); }); it('matches lock files', async () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', lockFiles: ['yarn.lock'], packageRules: [ { matchFileNames: ['yarn.lock'], // @ts-expect-error -- testing x: 1, }, ], }; const res = await applyPackageRules(config); expect(res.x).toBeDefined(); }); it('matches paths', async () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', packageRules: [ { matchFileNames: ['examples/**', 'lib/'], // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, packageName: 'test', }); expect(res1.x).toBeDefined(); config.packageFile = 'package.json'; const res2 = await applyPackageRules({ ...config, packageName: 'test', }); expect(res2.x).toBeUndefined(); config.packageFile = 'lib/a/package.json'; const res3 = await applyPackageRules({ ...config, packageName: 'test', }); expect(res3.x).toBeUndefined(); }); it('empty rules', async () => { await expect( applyPackageRules({ ...config1, packageRules: null as never }), ).resolves.toEqual({ foo: 'bar', packageRules: null, }); }); it('creates groupSlug if necessary', async () => { const config: TestConfig = { packageName: 'foo', packageRules: [ { matchPackageNames: ['*'], groupName: 'A', groupSlug: 'a', }, { matchPackageNames: ['*'], groupName: 'B', }, ], }; const res = await applyPackageRules(config); expect(res.groupSlug).toBe('b'); }); it('matches matchSourceUrls with patterns (case-insensitive)', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar**', 'https://github.com/Renovatebot/**', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/Presets', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('matches matchSourceUrls(case-insensitive)', async () => { const config: TestConfig = { packageRules: [ { matchSourceUrls: [ 'https://github.com/foo/bar', 'https://github.com/Renovatebot/renovate', ], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { depType: 'dependencies', packageName: 'a', updateType: 'patch' as UpdateType, sourceUrl: 'https://github.com/renovatebot/Renovate', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); it('needs language to match', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['abc'], matchCategories: ['js'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { packageName: 'abc', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('needs baseBranch to match', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['abc'], matchBaseBranches: ['dev'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { packageName: 'abc', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('needs manager to match', async () => { const config: TestConfig = { packageRules: [ { matchPackageNames: ['abc'], matchManagers: ['npm'], // @ts-expect-error -- testing x: 1, }, ], }; const dep = { packageName: 'abc', }; const res = await applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); it('matches matchDepNames(depName)', async () => { const config: TestConfig = { packageRules: [ { matchDepNames: ['test1'], // @ts-expect-error -- testing x: 1, }, ], }; const res1 = await applyPackageRules({ ...config, depName: 'test1', }); const res2 = await applyPackageRules({ ...config, depName: 'test2', }); await applyPackageRules(config); // coverage expect(res1.x).toBe(1); expect(res2.x).toBeUndefined(); }); it('matches if there are no matchers', async () => { const config: TestConfig = { packageRules: [ { // @ts-expect-error -- testing x: 1, }, ], }; const res = await applyPackageRules({ ...config, depName: 'test2', }); expect(res.x).toBe(1); }); it('overrides', async () => { const config: TestConfig = { datasource: 'npm', depName: 'foo', packageName: 'bar', packageRules: [ { matchDatasources: ['npm'], matchDepNames: ['foo'], overridePackageName: 'baz', }, { matchDatasources: ['npm'], matchPackageNames: ['baz'], overrideDepName: 'f', }, { matchDepNames: ['f'], overrideDatasource: 'composer', }, { matchDatasources: ['composer'], matchDepNames: ['f'], matchPackageNames: ['baz'], enabled: false, }, ], }; let res = await applyPackageRules(config); expect(res.packageName).toBe('baz'); res = await applyPackageRules(res); expect(res.depName).toBe('f'); res = await applyPackageRules(res); expect(res.datasource).toBe('composer'); res = await applyPackageRules(res); expect(res).toMatchObject({ datasource: 'composer', depName: 'f', packageName: 'baz', enabled: false, }); }); it('overrides with templates', async () => { const config: TestConfig = { datasource: 'docker', depName: 'docker.io/library/node', packageName: 'docker.io/library/node', packageRules: [ { matchDatasources: ['docker'], overrideDepName: '{{replace "docker.io/library/" "" depName}}', }, ], }; const res = await applyPackageRules(config); expect(res.depName).toBe('node'); expect(res.packageName).toBe('docker.io/library/node'); }); it('propagates fetchChangeLogs from matching packageRule', async () => { const config: TestConfig = { datasource: 'npm', depName: 'some-dep', packageRules: [ { matchDatasources: ['npm'], fetchChangeLogs: 'off', }, ], }; const res = await applyPackageRules(config); expect(res.fetchChangeLogs).toBe('off'); }); it('does not set fetchChangeLogs when packageRule does not match', async () => { const config: TestConfig = { datasource: 'npm', depName: 'some-dep', packageRules: [ { matchDatasources: ['pypi'], fetchChangeLogs: 'off', }, ], }; const res = await applyPackageRules(config); expect(res).not.toHaveProperty('fetchChangeLogs'); }); it('compiles sourceUrl with template helper functions', async () => { const config: TestConfig = { datasource: 'terraform-provider', depName: 'aws', packageName: 'hashicorp/aws', packageRules: [ { matchDatasources: ['terraform-provider'], sourceUrl: 'https://github.com/{{replace "/" "/terraform-provider-" packageName}}', }, ], }; const res = await applyPackageRules(config); expect(res.sourceUrl).toBe( 'https://github.com/hashicorp/terraform-provider-aws', ); }); it('compiles sourceUrl with template variables', async () => { const config: TestConfig = { datasource: 'terraform-provider', depName: 'aws', packageName: 'hashicorp/aws', packageRules: [ { matchDatasources: ['terraform-provider'], sourceUrl: 'https://github.com/{{packageName}}', }, ], }; const res = await applyPackageRules(config); expect(res.sourceUrl).toBe('https://github.com/hashicorp/aws'); }); describe('packageRules array handling', () => { it('returns the input packageRules array without re-cloning it', async () => { const config: TestConfig = { packageName: 'a', packageRules: [ { matchPackageNames: ['a'], // @ts-expect-error -- testing x: 2, }, ], }; const res = await applyPackageRules(config); expect(res.x).toBe(2); expect(res.packageRules).toBe(config.packageRules); }); it('does not add a packageRules key when the input has none', async () => { const config: PackageRuleInputConfig = { packageName: 'a' }; const res = await applyPackageRules(config); expect(res.packageRules).toBeUndefined(); }); it('keeps an empty packageRules array', async () => { const config: PackageRuleInputConfig = { packageName: 'a', packageRules: [], }; const res = await applyPackageRules(config); expect(res.packageRules).toBe(config.packageRules); }); it('appends nested packageRules carried by an applied rule', async () => { const nestedRule = { matchPackageNames: ['b'], y: 3, }; const config: TestConfig = { packageName: 'a', packageRules: [ { matchPackageNames: ['a'], packageRules: [nestedRule], }, ], }; const res = await applyPackageRules(config); expect(res.packageRules).toHaveLength(2); expect(res.packageRules![1]).toEqual(nestedRule); }); }); });