From 368903ab814f5b19117eaab6c2cb33382be6cad6 Mon Sep 17 00:00:00 2001 From: Oleg Krivtsov Date: Sun, 30 Jan 2022 10:46:22 +0700 Subject: [PATCH] feat: support new rangeStrategy=in-range-only (#13257) Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: Rhys Arkins --- docs/usage/configuration-options.md | 6 ++++ lib/config/options/index.ts | 1 + lib/manager/index.spec.ts | 28 +++++++++++++++++++ lib/manager/index.ts | 10 ++++++- lib/types/versioning.ts | 1 + lib/workers/branch/reuse.spec.ts | 5 ++++ .../lookup/__snapshots__/index.spec.ts.snap | 16 +++++++++++ .../repository/process/lookup/index.spec.ts | 25 +++++++++++++++++ .../repository/process/lookup/index.ts | 7 +++++ 9 files changed, 98 insertions(+), 1 deletion(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 6dad2b35b8f500..a0d41726516fe0 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -2055,6 +2055,7 @@ Behavior: - `replace` = Replace the range with a newer one if the new version falls outside it, and update nothing otherwise - `widen` = Widen the range with newer one, e.g. `^1.0.0` -> `^1.0.0 || ^2.0.0` - `update-lockfile` = Update the lock file when in-range updates are available, otherwise `replace` for updates out of range. Works for `bundler`, `composer`, `npm`, `yarn`, `terraform` and `poetry` so far +- `in-range-only` = Update the lock file when in-range updates are available, ignore package file updates Renovate's `"auto"` strategy works like this for npm: @@ -2072,6 +2073,11 @@ If instead you'd prefer to be updated to `^1.2.0` in cases like this, then confi This feature supports simple caret (`^`) and tilde (`~`) ranges only, like `^1.0.0` and `~1.0.0`. +The `in-range-only` strategy may be useful if you want to leave the package file unchanged and only do `update-lockfile` within the existing range. +The `in-range-only` strategy behaves like `update-lockfile`, but discards any updates where the new version of the dependency is not equal to the current version. +We recommend you avoid using the `in-range-only` strategy unless you strictly need it. +Using the `in-range-only` strategy may result in you being multiple releases behind without knowing it. + ## rebaseLabel On supported platforms it is possible to add a label to a PR to manually request Renovate to recreate/rebase it. diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index dc8b337a6b0a0a..1816a3e154255e 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1169,6 +1169,7 @@ const options: RenovateOptions[] = [ 'replace', 'widen', 'update-lockfile', + 'in-range-only', ], cli: false, env: false, diff --git a/lib/manager/index.spec.ts b/lib/manager/index.spec.ts index 009979be19054f..3202d8569700c0 100644 --- a/lib/manager/index.spec.ts +++ b/lib/manager/index.spec.ts @@ -152,6 +152,34 @@ describe('manager/index', () => { manager.getRangeStrategy({ manager: 'dummy', rangeStrategy: 'bump' }) ).not.toBeNull(); }); + + it('returns update-lockfile for in-range-only', () => { + manager.getManagers().set('dummy', { + defaultConfig: {}, + supportedDatasources: [], + }); + expect( + manager.getRangeStrategy({ + manager: 'dummy', + rangeStrategy: 'in-range-only', + }) + ).toBe('update-lockfile'); + }); + + it('returns update-lockfile for in-range-only if it is proposed my manager', () => { + manager.getManagers().set('dummy', { + defaultConfig: {}, + supportedDatasources: [], + getRangeStrategy: () => 'in-range-only', + }); + expect( + manager.getRangeStrategy({ + manager: 'dummy', + rangeStrategy: 'in-range-only', + }) + ).toBe('update-lockfile'); + }); + afterEach(() => { manager.getManagers().delete('dummy'); }); diff --git a/lib/manager/index.ts b/lib/manager/index.ts index 754d9048af2e1f..2f9cf3e29af219 100644 --- a/lib/manager/index.ts +++ b/lib/manager/index.ts @@ -79,11 +79,19 @@ export function getRangeStrategy(config: RangeConfig): RangeStrategy { const m = managers.get(manager); if (m.getRangeStrategy) { // Use manager's own function if it exists - return m.getRangeStrategy(config); + const managerRangeStrategy = m.getRangeStrategy(config); + if (managerRangeStrategy === 'in-range-only') { + return 'update-lockfile'; + } + return managerRangeStrategy; } if (rangeStrategy === 'auto') { // default to 'replace' for auto return 'replace'; } + if (rangeStrategy === 'in-range-only') { + return 'update-lockfile'; + } + return config.rangeStrategy; } diff --git a/lib/types/versioning.ts b/lib/types/versioning.ts index 9a5288231ff2a8..cc8382d1d645c3 100644 --- a/lib/types/versioning.ts +++ b/lib/types/versioning.ts @@ -5,4 +5,5 @@ export type RangeStrategy = | 'pin' | 'replace' | 'update-lockfile' + | 'in-range-only' | 'widen'; diff --git a/lib/workers/branch/reuse.spec.ts b/lib/workers/branch/reuse.spec.ts index 0cdacc6aa881e2..cb7d463c566fa4 100644 --- a/lib/workers/branch/reuse.spec.ts +++ b/lib/workers/branch/reuse.spec.ts @@ -54,6 +54,11 @@ describe('workers/branch/reuse', () => { rangeStrategy: 'update-lockfile', branchName: 'current', }, + { + packageFile: 'package.json', + rangeStrategy: 'in-range-only', + branchName: 'current', + }, ]; git.branchExists.mockReturnValueOnce(true); git.isBranchConflicted.mockResolvedValueOnce(false); diff --git a/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap b/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap index 47c4490be5da2c..21be5395b76a71 100644 --- a/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap +++ b/lib/workers/repository/process/lookup/__snapshots__/index.spec.ts.snap @@ -238,6 +238,22 @@ Object { } `; +exports[`workers/repository/process/lookup/index .lookupUpdates() handles the in-range-only strategy and updates lockfile within range 1`] = ` +Array [ + Object { + "bucket": "non-major", + "isLockfileUpdate": true, + "isRange": true, + "newMajor": 1, + "newMinor": 4, + "newValue": "^1.2.1", + "newVersion": "1.4.1", + "releaseTimestamp": "2015-05-17T04:25:07.299Z", + "updateType": "minor", + }, +] +`; + exports[`workers/repository/process/lookup/index .lookupUpdates() handles unknown datasource 1`] = `Array []`; exports[`workers/repository/process/lookup/index .lookupUpdates() handles update-lockfile 1`] = ` diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index f4e748e7e110f1..4b174f05515eb2 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -31,6 +31,7 @@ const qJson = { ...loadJsonFixture('01.json', fixtureRoot), latestVersion: '1.4.1', }; + const helmetJson = loadJsonFixture('02.json', fixtureRoot); const coffeelintJson = loadJsonFixture('coffeelint.json', fixtureRoot); const nextJson = loadJsonFixture('next.json', fixtureRoot); @@ -341,6 +342,30 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates[0].updateType).toBe('minor'); }); + + it('handles the in-range-only strategy and updates lockfile within range', async () => { + config.currentValue = '^1.2.1'; + config.lockedVersion = '1.2.1'; + config.rangeStrategy = 'in-range-only'; + config.depName = 'q'; + config.datasource = datasourceNpmId; + httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); + const res = await lookup.lookupUpdates(config); + expect(res.updates).toMatchSnapshot(); + expect(res.updates[0].updateType).toBe('minor'); + }); + + it('handles the in-range-only strategy and discards changes not within range', async () => { + config.currentValue = '~1.2.0'; + config.lockedVersion = '1.2.0'; + config.rangeStrategy = 'in-range-only'; + config.depName = 'q'; + config.datasource = datasourceNpmId; + httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); + const res = await lookup.lookupUpdates(config); + expect(res.updates).toBeEmptyArray(); + }); + it('handles unconstrainedValue values', async () => { config.lockedVersion = '1.2.1'; config.rangeStrategy = 'update-lockfile'; diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index f7d22b2b6e143d..42aaff38418c07 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -103,6 +103,7 @@ export async function lookupUpdates( let allVersions = dependency.releases.filter((release) => versioning.isVersion(release.version) ); + // istanbul ignore if if (allVersions.length === 0) { const message = `Found no results from datasource that look like a version`; @@ -364,6 +365,12 @@ export async function lookupUpdates( update.isLockfileUpdate || (update.newDigest && !update.newDigest.startsWith(currentDigest)) ); + // If range strategy specified in config is 'in-range-only', also strip out updates where currentValue !== newValue + if (config.rangeStrategy === 'in-range-only') { + res.updates = res.updates.filter( + (update) => update.newValue === currentValue + ); + } } catch (err) /* istanbul ignore next */ { if (err instanceof ExternalHostError || err.message === CONFIG_VALIDATION) { throw err;