mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
feat(manager/npm): add support for x-range "all" - "*"
range (#18251)
This commit is contained in:
parent
7c06287c12
commit
6fef1d1650
8 changed files with 140 additions and 6 deletions
|
@ -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",
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
16
lib/modules/versioning/semver/common.spec.ts
Normal file
16
lib/modules/versioning/semver/common.spec.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
10
lib/modules/versioning/semver/common.ts
Normal file
10
lib/modules/versioning/semver/common.ts
Normal 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);
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
Loading…
Reference in a new issue