feat(manager/npm): add support for x-range "all" - "*" range (#18251)

This commit is contained in:
Gabriel-Ladzaretti 2022-10-14 12:26:20 +03:00 committed by GitHub
parent 7c06287c12
commit 6fef1d1650
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 6 deletions

View file

@ -59,7 +59,6 @@ exports[`modules/manager/npm/extract/index .extractPackageFile() extracts engine
"depName": "foo", "depName": "foo",
"depType": "devDependencies", "depType": "devDependencies",
"prettyDepType": "devDependency", "prettyDepType": "devDependency",
"skipReason": "any-version",
}, },
{ {
"currentValue": "file:../foo/bar", "currentValue": "file:../foo/bar",

View file

@ -353,7 +353,7 @@ describe('modules/manager/npm/extract/index', () => {
deps: [ deps: [
{ depName: 'angular', currentValue: '1.6.0' }, { depName: 'angular', currentValue: '1.6.0' },
{ depName: '@angular/cli', currentValue: '1.6.0' }, { depName: '@angular/cli', currentValue: '1.6.0' },
{ depName: 'foo', currentValue: '*', skipReason: 'any-version' }, { depName: 'foo', currentValue: '*' },
{ {
depName: 'bar', depName: 'bar',
currentValue: 'file:../foo/bar', currentValue: 'file:../foo/bar',

View file

@ -271,9 +271,6 @@ export async function extractPackageFile(
} }
if (isValid(dep.currentValue)) { if (isValid(dep.currentValue)) {
dep.datasource = NpmDatasource.id; dep.datasource = NpmDatasource.id;
if (dep.currentValue === '*') {
dep.skipReason = 'any-version';
}
if (dep.currentValue === '') { if (dep.currentValue === '') {
dep.skipReason = 'empty'; dep.skipReason = 'empty';
} }

View file

@ -5,19 +5,44 @@ describe('modules/versioning/npm/index', () => {
version | isValid version | isValid
${'17.04.0'} | ${false} ${'17.04.0'} | ${false}
${'1.2.3'} | ${true} ${'1.2.3'} | ${true}
${'*'} | ${true}
${'x'} | ${true}
${'X'} | ${true}
${'1'} | ${true}
${'1.2.3-foo'} | ${true} ${'1.2.3-foo'} | ${true}
${'1.2.3foo'} | ${false} ${'1.2.3foo'} | ${false}
${'~1.2.3'} | ${true} ${'~1.2.3'} | ${true}
${'1.2'} | ${true}
${'1.2.x'} | ${true}
${'1.2.X'} | ${true}
${'1.2.*'} | ${true}
${'~1.2.3'} | ${true}
${'^1.2.3'} | ${true} ${'^1.2.3'} | ${true}
${'>1.2.3'} | ${true} ${'>1.2.3'} | ${true}
${'renovatebot/renovate'} | ${false} ${'renovatebot/renovate'} | ${false}
${'renovatebot/renovate#main'} | ${false} ${'renovatebot/renovate#main'} | ${false}
${'https://github.com/renovatebot/renovate.git'} | ${false} ${'https://github.com/renovatebot/renovate.git'} | ${false}
`('isValid("$version") === $isValid', ({ version, isValid }) => { `('isValid("$version") === $isValid', ({ version, isValid }) => {
const res = !!semver.isValid(version); const res = semver.isValid(version);
expect(res).toBe(isValid); expect(res).toBe(isValid);
}); });
test.each`
versions | range | maxSatisfying
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'*'} | ${'3.0.0'}
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'x'} | ${'3.0.0'}
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'X'} | ${'3.0.0'}
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2'} | ${'2.5.1'}
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2.*'} | ${'2.5.1'}
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2.3'} | ${'2.3.4'}
${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2.3.*'} | ${'2.3.4'}
`(
'getSatisfyingVersion("$versions","$range") === $maxSatisfying',
({ versions, range, maxSatisfying }) => {
expect(semver.getSatisfyingVersion(versions, range)).toBe(maxSatisfying);
}
);
test.each` test.each`
version | isSingle version | isSingle
${'1.2.3'} | ${true} ${'1.2.3'} | ${true}
@ -59,6 +84,7 @@ describe('modules/versioning/npm/index', () => {
${'>= 0.0.1 < 0.0.4'} | ${'bump'} | ${'0.0.4'} | ${'0.0.5'} | ${'>= 0.0.5 < 0.0.6'} ${'>= 0.0.1 < 0.0.4'} | ${'bump'} | ${'0.0.4'} | ${'0.0.5'} | ${'>= 0.0.5 < 0.0.6'}
${'>= 0.0.1 < 1'} | ${'bump'} | ${'1.0.0'} | ${'1.0.1'} | ${'>= 1.0.1 < 2'} ${'>= 0.0.1 < 1'} | ${'bump'} | ${'1.0.0'} | ${'1.0.1'} | ${'>= 1.0.1 < 2'}
${'>= 0.0.1 < 1'} | ${'bump'} | ${'1.0.0'} | ${'1.0.1'} | ${'>= 1.0.1 < 2'} ${'>= 0.0.1 < 1'} | ${'bump'} | ${'1.0.0'} | ${'1.0.1'} | ${'>= 1.0.1 < 2'}
${'*'} | ${'bump'} | ${'1.0.0'} | ${'1.0.1'} | ${null}
${'<=1.2.3'} | ${'widen'} | ${'1.0.0'} | ${'1.2.3'} | ${'<=1.2.3'} ${'<=1.2.3'} | ${'widen'} | ${'1.0.0'} | ${'1.2.3'} | ${'<=1.2.3'}
${'<=1.2.3'} | ${'widen'} | ${'1.0.0'} | ${'1.2.4'} | ${'<=1.2.4'} ${'<=1.2.3'} | ${'widen'} | ${'1.0.0'} | ${'1.2.4'} | ${'<=1.2.4'}
${'>=1.2.3'} | ${'widen'} | ${'1.0.0'} | ${'1.2.3'} | ${'>=1.2.3'} ${'>=1.2.3'} | ${'widen'} | ${'1.0.0'} | ${'1.2.3'} | ${'>=1.2.3'}

View file

@ -3,6 +3,7 @@ import semver from 'semver';
import semverUtils from 'semver-utils'; import semverUtils from 'semver-utils';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import { regEx } from '../../../util/regex'; import { regEx } from '../../../util/regex';
import { isSemVerXRange } from '../semver/common';
import type { NewValueConfig } from '../types'; import type { NewValueConfig } from '../types';
const { const {
@ -63,6 +64,9 @@ export function getNewValue({
currentVersion, currentVersion,
newVersion, newVersion,
}: NewValueConfig): string | null { }: NewValueConfig): string | null {
if (rangeStrategy === 'bump' && isSemVerXRange(currentValue)) {
return null;
}
if (rangeStrategy === 'pin' || isVersion(currentValue)) { if (rangeStrategy === 'pin' || isVersion(currentValue)) {
return newVersion; return newVersion;
} }

View file

@ -0,0 +1,16 @@
import { isSemVerXRange } from './common';
describe('modules/versioning/semver/common', () => {
test.each`
range | expected
${'*'} | ${true}
${'x'} | ${true}
${'X'} | ${true}
${''} | ${true}
${'1'} | ${false}
${'1.2'} | ${false}
${'1.2.3'} | ${false}
`('isSemVerXRange("range") === $expected', ({ range, expected }) => {
expect(isSemVerXRange(range)).toBe(expected);
});
});

View file

@ -0,0 +1,10 @@
const SEMVER_X_RANGE = ['*', 'x', 'X', ''] as const;
type SemVerXRangeArray = typeof SEMVER_X_RANGE;
export type SemVerXRange = SemVerXRangeArray[number];
/**
* https://docs.npmjs.com/cli/v6/using-npm/semver#x-ranges-12x-1x-12-
*/
export function isSemVerXRange(range: string): range is SemVerXRange {
return SEMVER_X_RANGE.includes(range as SemVerXRange);
}

View file

@ -334,6 +334,88 @@ describe('workers/repository/process/lookup/index', () => {
]); ]);
}); });
it.each`
strategy | updates
${'update-lockfile'} | ${[{ isLockfileUpdate: true, newValue: '*', newVersion: '0.9.7', updateType: 'minor' }, { isLockfileUpdate: true, newValue: '*', newVersion: '1.4.1', updateType: 'major' }]}
${'pin'} | ${[{ newValue: '0.4.0', updateType: 'pin' }, { newValue: '0.9.7', updateType: 'minor' }, { newValue: '1.4.1', updateType: 'major' }]}
`(
'supports for x-range-all for replaceStrategy = $strategy (with lockfile)',
async ({ strategy, updates }) => {
config.currentValue = '*';
config.rangeStrategy = strategy;
config.lockedVersion = '0.4.0';
config.depName = 'q';
config.datasource = NpmDatasource.id;
httpMock
.scope('https://registry.npmjs.org')
.get('/q')
.reply(200, qJson);
expect(await lookup.lookupUpdates(config)).toMatchObject({ updates });
}
);
it.each`
strategy
${'widen'}
${'bump'}
${'replace'}
`(
'doesnt offer updates for x-range-all (with lockfile) when replaceStrategy = $strategy',
async ({ strategy }) => {
config.currentValue = 'x';
config.rangeStrategy = strategy;
config.lockedVersion = '0.4.0';
config.depName = 'q';
config.datasource = NpmDatasource.id;
httpMock
.scope('https://registry.npmjs.org')
.get('/q')
.reply(200, qJson);
expect((await lookup.lookupUpdates(config)).updates).toEqual([]);
}
);
it('supports pinning for x-range-all (no lockfile)', async () => {
config.currentValue = '*';
config.rangeStrategy = 'pin';
config.depName = 'q';
config.datasource = NpmDatasource.id;
httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson);
expect(await lookup.lookupUpdates(config)).toMatchObject({
updates: [{ newValue: '1.4.1', updateType: 'pin' }],
});
});
it('covers pinning an unsupported x-range-all value', async () => {
config.currentValue = '';
config.rangeStrategy = 'pin';
config.depName = 'q';
config.datasource = NpmDatasource.id;
httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson);
expect((await lookup.lookupUpdates(config)).updates).toEqual([]);
});
it.each`
strategy
${'widen'}
${'bump'}
${'update-lockfile'}
${'replace'}
`(
'doesnt offer updates for x-range-all (no lockfile) when replaceStrategy = $strategy',
async ({ strategy }) => {
config.currentValue = 'X';
config.rangeStrategy = strategy;
config.depName = 'q';
config.datasource = NpmDatasource.id;
httpMock
.scope('https://registry.npmjs.org')
.get('/q')
.reply(200, qJson);
expect((await lookup.lookupUpdates(config)).updates).toEqual([]);
}
);
it('ignores pinning for ranges when other upgrade exists', async () => { it('ignores pinning for ranges when other upgrade exists', async () => {
config.currentValue = '~0.9.0'; config.currentValue = '~0.9.0';
config.rangeStrategy = 'pin'; config.rangeStrategy = 'pin';