From 029c372c6fcdeb1a49f7e1eeda8252f159bb46a7 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Thu, 11 Apr 2024 17:16:49 +0545 Subject: [PATCH 01/26] init same-major versioning --- .../versioning/same-major/index.spec.ts | 0 lib/modules/versioning/same-major/index.ts | 60 +++++++++++++++++++ lib/modules/versioning/same-major/readme.md | 0 3 files changed, 60 insertions(+) create mode 100644 lib/modules/versioning/same-major/index.spec.ts create mode 100644 lib/modules/versioning/same-major/index.ts create mode 100644 lib/modules/versioning/same-major/readme.md diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts new file mode 100644 index 00000000000000..6923d77fc9e511 --- /dev/null +++ b/lib/modules/versioning/same-major/index.ts @@ -0,0 +1,60 @@ +import is from '@sindresorhus/is'; +import semver from 'semver'; +import { api as semverCoerced } from '../semver-coerced'; +import type { VersioningApi } from '../types'; + +export const id = 'same-major'; +export const displayName = 'Same Major Versioning'; +// export const urls = ['https://semver.org/']; +export const supportsRanges = true; + +function matches(version: string, range: string): boolean { + const coercedVersion = semver.coerce(version); + let newRange = range; + if (!semverCoerced.isSingleVersion(range)) { + const major = semverCoerced.getMajor(range) ?? 0; + const min = semver.coerce(major)?.version; + const max = semver.coerce(major + 1)?.version; + newRange = `>=${min} <${max}`; + } + + return coercedVersion ? semver.satisfies(coercedVersion, newRange) : false; +} + +function getSatisfyingVersion( + versions: string[], + range: string, +): string | null { + const coercedVersions = versions + .map((version) => + semver.valid(version) ? version : semver.coerce(version)?.version, + ) + .filter(is.string); + + return semver.maxSatisfying(coercedVersions, range); +} + +function minSatisfyingVersion( + versions: string[], + range: string, +): string | null { + const coercedVersions = versions + .map((version) => semver.coerce(version)?.version) + .filter(is.string); + + return semver.minSatisfying(coercedVersions, range); +} + +function isLessThanRange(version: string, range: string): boolean { + const coercedVersion = semver.coerce(version); + return coercedVersion ? semver.ltr(coercedVersion, range) : false; +} + +export const api: VersioningApi = { + ...semverCoerced, + matches, + getSatisfyingVersion, + minSatisfyingVersion, + isLessThanRange, +}; +export default api; diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md new file mode 100644 index 00000000000000..e69de29bb2d1d6 From bb9dfeb5ded3cb9602130258e924554c40a7c8dc Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Fri, 12 Apr 2024 00:50:33 +0545 Subject: [PATCH 02/26] implementation --- .../versioning/same-major/index.spec.ts | 311 ++++++++++++++++++ lib/modules/versioning/same-major/index.ts | 48 +-- 2 files changed, 335 insertions(+), 24 deletions(-) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index e69de29bb2d1d6..21aa2823e73b42 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -0,0 +1,311 @@ +import sameMajor from '.'; + +describe('modules/versioning/same-major/index', () => { + describe('.equals(a, b)', () => { + it('should return true for strictly equal versions', () => { + expect(sameMajor.equals('1.0.0', '1.0.0')).toBeTrue(); + }); + + it('should return true for non-strictly equal versions', () => { + expect(sameMajor.equals('v1.0', '1.0.0')).toBeTrue(); + expect(sameMajor.equals('v1.0', 'v1.x')).toBeTrue(); + }); + + it('should return false for non-equal versions', () => { + expect(sameMajor.equals('2.0.1', '2.3.0')).toBeFalse(); + }); + + it('invalid version', () => { + expect(sameMajor.equals('xxx', '1.2.3')).toBeFalse(); + }); + }); + + describe('.getMajor(input)', () => { + it('should return major version number for strict semver', () => { + expect(sameMajor.getMajor('1.0.2')).toBe(1); + }); + + it('should return major version number for non-strict semver', () => { + expect(sameMajor.getMajor('v3.1')).toBe(3); + }); + + it('invalid version', () => { + expect(sameMajor.getMajor('xxx')).toBeNull(); + }); + }); + + describe('.getMinor(input)', () => { + it('should return minor version number for strict semver', () => { + expect(sameMajor.getMinor('1.0.2')).toBe(0); + }); + + it('should return minor version number for non-strict semver', () => { + expect(sameMajor.getMinor('v3.1')).toBe(1); + }); + + it('invalid version', () => { + expect(sameMajor.getMinor('xxx')).toBeNull(); + }); + }); + + describe('.getPatch(input)', () => { + it.each` + version | expected + ${'1.0.2'} | ${2} + ${'v3.1.2-foo'} | ${2} + ${'v1.3.5'} | ${5} + ${'v2.1'} | ${0} + ${'3.4'} | ${0} + ${'v2'} | ${0} + ${'2'} | ${0} + ${'v1.0.4-alpha'} | ${4} + ${'1.0.3-Beta.1'} | ${3} + ${'1.0.0-rc2'} | ${0} + ${'v1.0.8-rc2'} | ${8} + ${'1.0-Beta.0'} | ${0} + ${'two1.0'} | ${0} + ${'ver1.2.3'} | ${3} + ${'r3.0'} | ${0} + ${'abc'} | ${null} + `('getPatch("$version") === $expected', ({ version, expected }) => { + expect(sameMajor.getPatch(version)).toBe(expected); + }); + }); + + describe('.isCompatible(input)', () => { + it('should return true for strict semver', () => { + expect(sameMajor.isCompatible('1.0.2')).toBeTrue(); + }); + + it('should return true for non-strict semver', () => { + expect(sameMajor.isCompatible('v3.1.2-foo')).toBeTrue(); + }); + + it('should return false for non-semver', () => { + expect(sameMajor.isCompatible('foo')).toBeFalse(); + }); + }); + + describe('.isGreaterThan(a, b)', () => { + it('should return true for a greater version in strict semver', () => { + expect(sameMajor.isGreaterThan('1.0.2', '1.0.0')).toBeTrue(); + }); + + it('should return false for lower version in strict semver', () => { + expect(sameMajor.isGreaterThan('3.1.2', '4.1.0')).toBeFalse(); + }); + + it('should return false if version cannot be coerced', () => { + expect(sameMajor.isGreaterThan('e.e.e', '4.1.0')).toBeFalse(); + }); + }); + + describe('.isLessThanRange(version, range)', () => { + it('should return true for a lower version in strict semver', () => { + expect(sameMajor.isLessThanRange?.('1.0.2', '~2.0')).toBeTrue(); + }); + + it('should return false for in-range version in strict semver', () => { + expect(sameMajor.isLessThanRange?.('3.0.2', '~3.0')).toBeFalse(); + expect(sameMajor.isLessThanRange?.('3.0.2', '3.0.0')).toBeFalse(); + }); + + it('invalid version', () => { + expect(sameMajor.isLessThanRange?.('xxx', '1.2.3')).toBeFalse(); + }); + }); + + describe('.isSingleVersion()', () => { + it('returns true if naked version', () => { + expect(sameMajor.isSingleVersion('1.2.3')).toBeTrue(); + expect(sameMajor.isSingleVersion('1.2.3-alpha.1')).toBeTrue(); + }); + + it('returns false if equals', () => { + expect(sameMajor.isSingleVersion('=1.2.3')).toBeFalse(); + expect(sameMajor.isSingleVersion('= 1.2.3')).toBeFalse(); + }); + + it('returns false when not version', () => { + expect(sameMajor.isSingleVersion('~1.0')).toBeFalse(); + }); + }); + + describe('.isStable(input)', () => { + it.each` + version | expected + ${'1.0.0'} | ${true} + ${'v1.3.5'} | ${true} + ${'v2.1'} | ${true} + ${'3.4'} | ${true} + ${'v2'} | ${true} + ${'2'} | ${true} + ${'v1.0.0-alpha'} | ${false} + ${'1.0.0-Beta.1'} | ${false} + ${'1.0.0-rc2'} | ${false} + ${'v1.0.0-rc2'} | ${false} + ${'1.0-Beta.0'} | ${false} + ${'v1.0-alpha'} | ${false} + ${'two1.0'} | ${false} + ${'ver1.2.3'} | ${false} + ${'r3.0'} | ${false} + `('isStable("$version") === $expected', ({ version, expected }) => { + expect(sameMajor.isStable(version)).toBe(expected); + }); + }); + + describe('.isValid(input)', () => { + it('should return null for non-digit version strings', () => { + expect(sameMajor.isValid('version two')).toBeFalse(); + }); + + it('should return null for irregular version strings', () => { + expect(sameMajor.isValid('17.04.0')).toBeFalse(); + }); + + it('should support strict semver', () => { + expect(sameMajor.isValid('1.2.3')).toBeTrue(); + }); + + it('should treat semver with dash as a valid version', () => { + expect(sameMajor.isValid('1.2.3-foo')).toBeTrue(); + }); + + it('should treat semver without dash as a valid version', () => { + expect(sameMajor.isValid('1.2.3foo')).toBeTrue(); + }); + + it('should treat ranges as valid versions', () => { + expect(sameMajor.isValid('~1.2.3')).toBeTrue(); + expect(sameMajor.isValid('^1.2.3')).toBeTrue(); + expect(sameMajor.isValid('>1.2.3')).toBeTrue(); + }); + + it('should reject github repositories', () => { + expect(sameMajor.isValid('renovatebot/renovate')).toBeFalse(); + expect(sameMajor.isValid('renovatebot/renovate#master')).toBeFalse(); + expect( + sameMajor.isValid('https://github.com/renovatebot/renovate.git'), + ).toBeFalse(); + }); + }); + + describe('.isVersion(input)', () => { + it('should return null for non-digit versions', () => { + expect(sameMajor.isValid('version one')).toBeFalse(); + }); + + it('should support strict semver versions', () => { + expect(sameMajor.isValid('1.2.3')).toBeTrue(); + }); + + it('should support non-strict versions', () => { + expect(sameMajor.isValid('v1.2')).toBeTrue(); + }); + }); + + describe('.matches(version, range)', () => { + it('should return true when version is in range', () => { + expect(sameMajor.matches('1.0.0', '1.0.0 || 1.0.1')).toBeTrue(); + }); + + it('should return true with non-strict version in range', () => { + expect(sameMajor.matches('v1.0', '1.0.0 || 1.0.1')).toBeTrue(); + expect(sameMajor.matches('v1.0.1', '1.0.0')).toBeTrue(); + }); + + it('should return false when version is not in range', () => { + expect(sameMajor.matches('1.2.3', '1.4.1 || 1.4.2')).toBeFalse(); + expect(sameMajor.matches('1.2.3', '0.1.0')).toBeFalse(); + }); + + it('invalid version', () => { + expect(sameMajor.matches('xxx', '1.2.3')).toBe(false); + }); + }); + + describe('.getSatisfyingVersion(versions, range)', () => { + it('should return max satisfying version in range', () => { + expect(sameMajor.getSatisfyingVersion(['1.0.0', '1.0.4'], '^1.0')).toBe( + '1.0.4', + ); + expect(sameMajor.getSatisfyingVersion(['1.0.0', '1.0.4'], '1.0.3')).toBe( + '1.0.4', + ); + }); + + it('should support coercion', () => { + expect( + sameMajor.getSatisfyingVersion(['v1.0', '1.0.4-foo'], '^1.0'), + ).toBe('1.0.0'); + }); + }); + + describe('.minSatisfyingVersion(versions, range)', () => { + it('should return min satisfying version in range', () => { + expect(sameMajor.minSatisfyingVersion(['1.0.0', '1.0.4'], '^1.0')).toBe( + '1.0.0', + ); + expect(sameMajor.minSatisfyingVersion(['1.0.0', '1.0.4'], '1.0.0')).toBe( + '1.0.0', + ); + }); + + it('should support coercion', () => { + expect( + sameMajor.minSatisfyingVersion(['v1.0', '1.0.4-foo'], '^1.0'), + ).toBe('1.0.0'); + }); + }); + + describe('getNewValue()', () => { + it('uses newVersion', () => { + expect( + sameMajor.getNewValue({ + currentValue: '=1.0.0', + rangeStrategy: 'bump', + currentVersion: '1.0.0', + newVersion: '1.1.0', + }), + ).toBe('1.1.0'); + expect( + sameMajor.getNewValue({ + currentValue: '1.0.0', + rangeStrategy: 'auto', + currentVersion: 'v1.0.0', + newVersion: 'v1.1.0', + }), + ).toBe('1.1.0'); + expect( + sameMajor.getNewValue({ + currentValue: '1.0.0', + rangeStrategy: 'auto', + currentVersion: 'v1.0.0', + newVersion: '1.1.0', + }), + ).toBe('1.1.0'); + }); + }); + + describe('.sortVersions(a, b)', () => { + it('should return zero for equal versions', () => { + expect(sameMajor.sortVersions('1.0.0', '1.0.0')).toBe(0); + }); + + it('should return -1 for a < b', () => { + expect(sameMajor.sortVersions('1.0.0', '1.0.1')).toBe(-1); + }); + + it('should return 1 for a > b', () => { + expect(sameMajor.sortVersions('1.0.1', '1.0.0')).toBe(1); + }); + + it('should return zero for equal non-strict versions', () => { + expect(sameMajor.sortVersions('v1.0', '1.x')).toBe(0); + }); + + it('works with invalid version', () => { + expect(sameMajor.sortVersions('v1.0', 'xx')).toBe(0); + }); + }); +}); diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 6923d77fc9e511..1070eb8a94ed22 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -1,5 +1,5 @@ -import is from '@sindresorhus/is'; import semver from 'semver'; +import semverVersioning from '../semver'; import { api as semverCoerced } from '../semver-coerced'; import type { VersioningApi } from '../types'; @@ -8,46 +8,46 @@ export const displayName = 'Same Major Versioning'; // export const urls = ['https://semver.org/']; export const supportsRanges = true; -function matches(version: string, range: string): boolean { - const coercedVersion = semver.coerce(version); - let newRange = range; - if (!semverCoerced.isSingleVersion(range)) { - const major = semverCoerced.getMajor(range) ?? 0; - const min = semver.coerce(major)?.version; - const max = semver.coerce(major + 1)?.version; - newRange = `>=${min} <${max}`; +/** + * + * Converts input to range if it's a version + * eg. 1.0.0 -> >=1.0.0 <2.0.0 + * If range is input no change is made + */ +function massageVersion(input: string): string { + let res = input; + const major = semverCoerced.getMajor(res); + if (semverVersioning.isValid(res) && major !== null) { + const upperLimit = semver.coerce(major + 1); + const max = upperLimit ? upperLimit.version : `${major + 1}.0.0`; + res = `>=${input} <${max}`; } - return coercedVersion ? semver.satisfies(coercedVersion, newRange) : false; + return res; +} + +function matches(version: string, range: string): boolean { + return semverCoerced.matches(version, massageVersion(range)); } function getSatisfyingVersion( versions: string[], range: string, ): string | null { - const coercedVersions = versions - .map((version) => - semver.valid(version) ? version : semver.coerce(version)?.version, - ) - .filter(is.string); - - return semver.maxSatisfying(coercedVersions, range); + return semverCoerced.getSatisfyingVersion(versions, massageVersion(range)); } function minSatisfyingVersion( versions: string[], range: string, ): string | null { - const coercedVersions = versions - .map((version) => semver.coerce(version)?.version) - .filter(is.string); - - return semver.minSatisfying(coercedVersions, range); + return semverCoerced.minSatisfyingVersion(versions, massageVersion(range)); } function isLessThanRange(version: string, range: string): boolean { - const coercedVersion = semver.coerce(version); - return coercedVersion ? semver.ltr(coercedVersion, range) : false; + return semverCoerced.isLessThanRange + ? semverCoerced.isLessThanRange(version, massageVersion(range)) + : false; } export const api: VersioningApi = { From 9f0749d8d050e705ec1154af905b32b15284e288 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 15 Apr 2024 19:26:31 +0545 Subject: [PATCH 03/26] add tests and docs --- lib/modules/versioning/api.ts | 2 + .../versioning/same-major/index.spec.ts | 305 ++---------------- lib/modules/versioning/same-major/index.ts | 12 +- lib/modules/versioning/same-major/readme.md | 3 + 4 files changed, 32 insertions(+), 290 deletions(-) diff --git a/lib/modules/versioning/api.ts b/lib/modules/versioning/api.ts index a2f52a8a738243..b0353866760c2b 100644 --- a/lib/modules/versioning/api.ts +++ b/lib/modules/versioning/api.ts @@ -31,6 +31,7 @@ import * as regex from './regex'; import * as rez from './rez'; import * as rpm from './rpm'; import * as ruby from './ruby'; +import * as sameMajor from './same-major'; import * as semver from './semver'; import * as semverCoerced from './semver-coerced'; import * as swift from './swift'; @@ -74,6 +75,7 @@ api.set(regex.id, regex.api); api.set(rez.id, rez.api); api.set(rpm.id, rpm.api); api.set(ruby.id, ruby.api); +api.set(sameMajor.id, sameMajor.api); api.set(semver.id, semver.api); api.set(semverCoerced.id, semverCoerced.api); api.set(swift.id, swift.api); diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 21aa2823e73b42..1983bdd3d4c970 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -1,311 +1,50 @@ import sameMajor from '.'; describe('modules/versioning/same-major/index', () => { - describe('.equals(a, b)', () => { - it('should return true for strictly equal versions', () => { - expect(sameMajor.equals('1.0.0', '1.0.0')).toBeTrue(); - }); - - it('should return true for non-strictly equal versions', () => { - expect(sameMajor.equals('v1.0', '1.0.0')).toBeTrue(); - expect(sameMajor.equals('v1.0', 'v1.x')).toBeTrue(); - }); - - it('should return false for non-equal versions', () => { - expect(sameMajor.equals('2.0.1', '2.3.0')).toBeFalse(); - }); - - it('invalid version', () => { - expect(sameMajor.equals('xxx', '1.2.3')).toBeFalse(); - }); - }); - - describe('.getMajor(input)', () => { - it('should return major version number for strict semver', () => { - expect(sameMajor.getMajor('1.0.2')).toBe(1); - }); - - it('should return major version number for non-strict semver', () => { - expect(sameMajor.getMajor('v3.1')).toBe(3); - }); - - it('invalid version', () => { - expect(sameMajor.getMajor('xxx')).toBeNull(); - }); - }); - - describe('.getMinor(input)', () => { - it('should return minor version number for strict semver', () => { - expect(sameMajor.getMinor('1.0.2')).toBe(0); - }); - - it('should return minor version number for non-strict semver', () => { - expect(sameMajor.getMinor('v3.1')).toBe(1); - }); - - it('invalid version', () => { - expect(sameMajor.getMinor('xxx')).toBeNull(); - }); - }); - - describe('.getPatch(input)', () => { - it.each` - version | expected - ${'1.0.2'} | ${2} - ${'v3.1.2-foo'} | ${2} - ${'v1.3.5'} | ${5} - ${'v2.1'} | ${0} - ${'3.4'} | ${0} - ${'v2'} | ${0} - ${'2'} | ${0} - ${'v1.0.4-alpha'} | ${4} - ${'1.0.3-Beta.1'} | ${3} - ${'1.0.0-rc2'} | ${0} - ${'v1.0.8-rc2'} | ${8} - ${'1.0-Beta.0'} | ${0} - ${'two1.0'} | ${0} - ${'ver1.2.3'} | ${3} - ${'r3.0'} | ${0} - ${'abc'} | ${null} - `('getPatch("$version") === $expected', ({ version, expected }) => { - expect(sameMajor.getPatch(version)).toBe(expected); - }); - }); - - describe('.isCompatible(input)', () => { - it('should return true for strict semver', () => { - expect(sameMajor.isCompatible('1.0.2')).toBeTrue(); - }); - - it('should return true for non-strict semver', () => { - expect(sameMajor.isCompatible('v3.1.2-foo')).toBeTrue(); - }); - - it('should return false for non-semver', () => { - expect(sameMajor.isCompatible('foo')).toBeFalse(); - }); - }); - - describe('.isGreaterThan(a, b)', () => { - it('should return true for a greater version in strict semver', () => { - expect(sameMajor.isGreaterThan('1.0.2', '1.0.0')).toBeTrue(); - }); - - it('should return false for lower version in strict semver', () => { - expect(sameMajor.isGreaterThan('3.1.2', '4.1.0')).toBeFalse(); - }); - - it('should return false if version cannot be coerced', () => { - expect(sameMajor.isGreaterThan('e.e.e', '4.1.0')).toBeFalse(); - }); - }); - - describe('.isLessThanRange(version, range)', () => { - it('should return true for a lower version in strict semver', () => { - expect(sameMajor.isLessThanRange?.('1.0.2', '~2.0')).toBeTrue(); - }); - - it('should return false for in-range version in strict semver', () => { - expect(sameMajor.isLessThanRange?.('3.0.2', '~3.0')).toBeFalse(); - expect(sameMajor.isLessThanRange?.('3.0.2', '3.0.0')).toBeFalse(); - }); - - it('invalid version', () => { - expect(sameMajor.isLessThanRange?.('xxx', '1.2.3')).toBeFalse(); - }); - }); - - describe('.isSingleVersion()', () => { - it('returns true if naked version', () => { - expect(sameMajor.isSingleVersion('1.2.3')).toBeTrue(); - expect(sameMajor.isSingleVersion('1.2.3-alpha.1')).toBeTrue(); - }); - - it('returns false if equals', () => { - expect(sameMajor.isSingleVersion('=1.2.3')).toBeFalse(); - expect(sameMajor.isSingleVersion('= 1.2.3')).toBeFalse(); - }); - - it('returns false when not version', () => { - expect(sameMajor.isSingleVersion('~1.0')).toBeFalse(); - }); - }); - - describe('.isStable(input)', () => { - it.each` - version | expected - ${'1.0.0'} | ${true} - ${'v1.3.5'} | ${true} - ${'v2.1'} | ${true} - ${'3.4'} | ${true} - ${'v2'} | ${true} - ${'2'} | ${true} - ${'v1.0.0-alpha'} | ${false} - ${'1.0.0-Beta.1'} | ${false} - ${'1.0.0-rc2'} | ${false} - ${'v1.0.0-rc2'} | ${false} - ${'1.0-Beta.0'} | ${false} - ${'v1.0-alpha'} | ${false} - ${'two1.0'} | ${false} - ${'ver1.2.3'} | ${false} - ${'r3.0'} | ${false} - `('isStable("$version") === $expected', ({ version, expected }) => { - expect(sameMajor.isStable(version)).toBe(expected); - }); - }); - - describe('.isValid(input)', () => { - it('should return null for non-digit version strings', () => { - expect(sameMajor.isValid('version two')).toBeFalse(); - }); - - it('should return null for irregular version strings', () => { - expect(sameMajor.isValid('17.04.0')).toBeFalse(); - }); - - it('should support strict semver', () => { - expect(sameMajor.isValid('1.2.3')).toBeTrue(); - }); - - it('should treat semver with dash as a valid version', () => { - expect(sameMajor.isValid('1.2.3-foo')).toBeTrue(); - }); - - it('should treat semver without dash as a valid version', () => { - expect(sameMajor.isValid('1.2.3foo')).toBeTrue(); - }); - - it('should treat ranges as valid versions', () => { - expect(sameMajor.isValid('~1.2.3')).toBeTrue(); - expect(sameMajor.isValid('^1.2.3')).toBeTrue(); - expect(sameMajor.isValid('>1.2.3')).toBeTrue(); - }); - - it('should reject github repositories', () => { - expect(sameMajor.isValid('renovatebot/renovate')).toBeFalse(); - expect(sameMajor.isValid('renovatebot/renovate#master')).toBeFalse(); - expect( - sameMajor.isValid('https://github.com/renovatebot/renovate.git'), - ).toBeFalse(); - }); - }); - - describe('.isVersion(input)', () => { - it('should return null for non-digit versions', () => { - expect(sameMajor.isValid('version one')).toBeFalse(); - }); - - it('should support strict semver versions', () => { - expect(sameMajor.isValid('1.2.3')).toBeTrue(); - }); - - it('should support non-strict versions', () => { - expect(sameMajor.isValid('v1.2')).toBeTrue(); - }); - }); - describe('.matches(version, range)', () => { - it('should return true when version is in range', () => { - expect(sameMajor.matches('1.0.0', '1.0.0 || 1.0.1')).toBeTrue(); + it('should return true when version is has same major', () => { + expect(sameMajor.matches('1.0.1', '1.0.0')).toBeTrue(); }); - it('should return true with non-strict version in range', () => { - expect(sameMajor.matches('v1.0', '1.0.0 || 1.0.1')).toBeTrue(); - expect(sameMajor.matches('v1.0.1', '1.0.0')).toBeTrue(); + it('should return false when version has different major', () => { + expect(sameMajor.matches('2.0.1', '1.0.0')).toBeFalse(); }); - it('should return false when version is not in range', () => { - expect(sameMajor.matches('1.2.3', '1.4.1 || 1.4.2')).toBeFalse(); - expect(sameMajor.matches('1.2.3', '0.1.0')).toBeFalse(); - }); - - it('invalid version', () => { - expect(sameMajor.matches('xxx', '1.2.3')).toBe(false); + it('should return false when version is lesser than the current version', () => { + expect(sameMajor.matches('1.2.3', '1.2.4')).toBeFalse(); }); }); describe('.getSatisfyingVersion(versions, range)', () => { it('should return max satisfying version in range', () => { - expect(sameMajor.getSatisfyingVersion(['1.0.0', '1.0.4'], '^1.0')).toBe( - '1.0.4', - ); - expect(sameMajor.getSatisfyingVersion(['1.0.0', '1.0.4'], '1.0.3')).toBe( - '1.0.4', - ); - }); - - it('should support coercion', () => { expect( - sameMajor.getSatisfyingVersion(['v1.0', '1.0.4-foo'], '^1.0'), - ).toBe('1.0.0'); + sameMajor.getSatisfyingVersion( + ['1.0.0', '1.0.4', '1.3.0', '2.0.0'], + '1.0.3', + ), + ).toBe('1.3.0'); }); }); describe('.minSatisfyingVersion(versions, range)', () => { it('should return min satisfying version in range', () => { - expect(sameMajor.minSatisfyingVersion(['1.0.0', '1.0.4'], '^1.0')).toBe( - '1.0.0', - ); - expect(sameMajor.minSatisfyingVersion(['1.0.0', '1.0.4'], '1.0.0')).toBe( - '1.0.0', - ); - }); - - it('should support coercion', () => { expect( - sameMajor.minSatisfyingVersion(['v1.0', '1.0.4-foo'], '^1.0'), - ).toBe('1.0.0'); + sameMajor.minSatisfyingVersion( + ['1.0.0', '1.0.4', '1.3.0', '2.0.0'], + '1.0.3', + ), + ).toBe('1.0.4'); }); }); - describe('getNewValue()', () => { - it('uses newVersion', () => { - expect( - sameMajor.getNewValue({ - currentValue: '=1.0.0', - rangeStrategy: 'bump', - currentVersion: '1.0.0', - newVersion: '1.1.0', - }), - ).toBe('1.1.0'); - expect( - sameMajor.getNewValue({ - currentValue: '1.0.0', - rangeStrategy: 'auto', - currentVersion: 'v1.0.0', - newVersion: 'v1.1.0', - }), - ).toBe('1.1.0'); - expect( - sameMajor.getNewValue({ - currentValue: '1.0.0', - rangeStrategy: 'auto', - currentVersion: 'v1.0.0', - newVersion: '1.1.0', - }), - ).toBe('1.1.0'); - }); - }); - - describe('.sortVersions(a, b)', () => { - it('should return zero for equal versions', () => { - expect(sameMajor.sortVersions('1.0.0', '1.0.0')).toBe(0); - }); - - it('should return -1 for a < b', () => { - expect(sameMajor.sortVersions('1.0.0', '1.0.1')).toBe(-1); - }); - - it('should return 1 for a > b', () => { - expect(sameMajor.sortVersions('1.0.1', '1.0.0')).toBe(1); - }); - - it('should return zero for equal non-strict versions', () => { - expect(sameMajor.sortVersions('v1.0', '1.x')).toBe(0); + describe('.isLessThanRange(version, range)', () => { + it('should return true', () => { + expect(sameMajor.isLessThanRange?.('2.0.2', '3.0.0')).toBeTrue(); }); - it('works with invalid version', () => { - expect(sameMajor.sortVersions('v1.0', 'xx')).toBe(0); + it('should return false', () => { + expect(sameMajor.isLessThanRange?.('4.0.0', '3.0.0')).toBeFalse(); + expect(sameMajor.isLessThanRange?.('3.1.0', '3.0.0')).toBeFalse(); }); }); }); diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 1070eb8a94ed22..54c362593fcdaf 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -5,22 +5,20 @@ import type { VersioningApi } from '../types'; export const id = 'same-major'; export const displayName = 'Same Major Versioning'; -// export const urls = ['https://semver.org/']; export const supportsRanges = true; /** * - * Converts input to range if it's a version - * eg. 1.0.0 -> >=1.0.0 <2.0.0 - * If range is input no change is made + * Converts input to range if it's a version. eg. X.Y.Z -> '>=X.Y.Z =${input} <${max}`; + const nextMajor = semver.coerce(major + 1); + const nextMajorVersion = nextMajor ? nextMajor.version : `${major + 1}.0.0`; + res = `>=${input} <${nextMajorVersion}`; } return res; diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md index e69de29bb2d1d6..639e4e57c978fd 100644 --- a/lib/modules/versioning/same-major/readme.md +++ b/lib/modules/versioning/same-major/readme.md @@ -0,0 +1,3 @@ +Renovate's Same Major versioning is specifically designed to address scenarios where version specifications, denoted as X.Y.Z, signify a range of compatibility from greater than or equal to X.Y.Z to less than X+1.Y.Z. In essence, each individual version is to be treated as a constraint. + +This method is handy when managing dependencies like dotnet-sdk's rollForward settings. Let's say a project uses dotnet-sdk version 3.1.0. It needs to be compatible with any version in the 3.x.x range but not with versions in the next major version, like 4.x.x. From 8db1874d9eff870d0989c2514a890738372dd4bf Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 15 Apr 2024 19:34:07 +0545 Subject: [PATCH 04/26] add urls --- lib/modules/versioning/same-major/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 54c362593fcdaf..b71c269c38e303 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -5,6 +5,7 @@ import type { VersioningApi } from '../types'; export const id = 'same-major'; export const displayName = 'Same Major Versioning'; +export const urls = []; export const supportsRanges = true; /** From 0abd3e7e3c24677f46d63b73369981b537a98835 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 15 Apr 2024 19:36:55 +0545 Subject: [PATCH 05/26] fix coverage --- lib/modules/versioning/same-major/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index b71c269c38e303..9fadf3673c3fa5 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -44,9 +44,7 @@ function minSatisfyingVersion( } function isLessThanRange(version: string, range: string): boolean { - return semverCoerced.isLessThanRange - ? semverCoerced.isLessThanRange(version, massageVersion(range)) - : false; + return !!semverCoerced.isLessThanRange?.(version, massageVersion(range)); } export const api: VersioningApi = { From b6c1b67348e1d3de58cdf9380fbf8fcba54726b8 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 15 Apr 2024 19:40:25 +0545 Subject: [PATCH 06/26] fix test --- lib/modules/versioning/same-major/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 9fadf3673c3fa5..3fdb875a216328 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -6,7 +6,7 @@ import type { VersioningApi } from '../types'; export const id = 'same-major'; export const displayName = 'Same Major Versioning'; export const urls = []; -export const supportsRanges = true; +export const supportsRanges = false; /** * From d5a2b1fbebb2928d9cd90cc691d28b855ba5dce9 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Tue, 16 Apr 2024 21:28:31 +0545 Subject: [PATCH 07/26] refactor --- lib/modules/versioning/same-major/index.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 3fdb875a216328..841f468e41c6d8 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -1,5 +1,4 @@ -import semver from 'semver'; -import semverVersioning from '../semver'; +import is from '@sindresorhus/is'; import { api as semverCoerced } from '../semver-coerced'; import type { VersioningApi } from '../types'; @@ -14,15 +13,18 @@ export const supportsRanges = false; * If the input is already a range, it returns the input. */ function massageVersion(input: string): string { - let res = input; - const major = semverCoerced.getMajor(res); - if (semverVersioning.isValid(res) && major !== null) { - const nextMajor = semver.coerce(major + 1); - const nextMajorVersion = nextMajor ? nextMajor.version : `${major + 1}.0.0`; - res = `>=${input} <${nextMajorVersion}`; + // return the input if it is a range + if (!semverCoerced.isSingleVersion(input)) { + return input; } - return res; + const major = semverCoerced.getMajor(input); + // should not happen since we have confirmed that version is valid + if (is.null_(major)) { + return input; + } + + return `>=${input} <${major + 1}.0.0`; } function matches(version: string, range: string): boolean { From 8ec34aa4e993b02bc10128a05a10e09397653f2a Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Tue, 16 Apr 2024 21:33:24 +0545 Subject: [PATCH 08/26] fix tests --- lib/modules/versioning/same-major/index.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 1983bdd3d4c970..705edd97eaaa9d 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -2,7 +2,7 @@ import sameMajor from '.'; describe('modules/versioning/same-major/index', () => { describe('.matches(version, range)', () => { - it('should return true when version is has same major', () => { + it('should return true when version has same major', () => { expect(sameMajor.matches('1.0.1', '1.0.0')).toBeTrue(); }); @@ -10,8 +10,10 @@ describe('modules/versioning/same-major/index', () => { expect(sameMajor.matches('2.0.1', '1.0.0')).toBeFalse(); }); - it('should return false when version is lesser than the current version', () => { + it('should return false when version is out of range', () => { expect(sameMajor.matches('1.2.3', '1.2.4')).toBeFalse(); + expect(sameMajor.matches('2.0.0', '1.2.4')).toBeFalse(); + expect(sameMajor.matches('3.2.4', '1.2.4')).toBeFalse(); }); }); From 3008147cbbfde9522faaace34db96f9cbb35e57f Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Tue, 16 Apr 2024 21:42:52 +0545 Subject: [PATCH 09/26] fix coverage --- lib/modules/versioning/same-major/index.spec.ts | 5 +++++ lib/modules/versioning/same-major/index.ts | 9 ++------- lib/modules/versioning/semver-coerced/index.spec.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 705edd97eaaa9d..00058c34c71596 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -4,6 +4,7 @@ describe('modules/versioning/same-major/index', () => { describe('.matches(version, range)', () => { it('should return true when version has same major', () => { expect(sameMajor.matches('1.0.1', '1.0.0')).toBeTrue(); + expect(sameMajor.matches('1.0.1', '^1.0.0')).toBeTrue(); // coverage for when ranges are passed to the function }); it('should return false when version has different major', () => { @@ -15,6 +16,10 @@ describe('modules/versioning/same-major/index', () => { expect(sameMajor.matches('2.0.0', '1.2.4')).toBeFalse(); expect(sameMajor.matches('3.2.4', '1.2.4')).toBeFalse(); }); + + it('should return false when version is invalid', () => { + expect(sameMajor.matches('1.0.0', 'xxx')).toBeFalse(); + }); }); describe('.getSatisfyingVersion(versions, range)', () => { diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 841f468e41c6d8..af44c484994442 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -1,4 +1,3 @@ -import is from '@sindresorhus/is'; import { api as semverCoerced } from '../semver-coerced'; import type { VersioningApi } from '../types'; @@ -18,12 +17,8 @@ function massageVersion(input: string): string { return input; } - const major = semverCoerced.getMajor(input); - // should not happen since we have confirmed that version is valid - if (is.null_(major)) { - return input; - } - + // we are sure to get a majot because of the isSingleVersion check + const major = semverCoerced.getMajor(input)!; return `>=${input} <${major + 1}.0.0`; } diff --git a/lib/modules/versioning/semver-coerced/index.spec.ts b/lib/modules/versioning/semver-coerced/index.spec.ts index 0b4940d0bf1795..d1fa4b9839a764 100644 --- a/lib/modules/versioning/semver-coerced/index.spec.ts +++ b/lib/modules/versioning/semver-coerced/index.spec.ts @@ -30,7 +30,7 @@ describe('modules/versioning/semver-coerced/index', () => { }); it('invalid version', () => { - expect(semverCoerced.getMajor('xxx')).toBeNull(); + expect(semverCoerced.getMajor('')).toBeNull(); }); }); From cfb8edd79cd4c7988d98377fb2648f7777bf10ae Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Thu, 18 Apr 2024 23:24:47 +0545 Subject: [PATCH 10/26] apply suggestions --- lib/modules/versioning/same-major/index.ts | 6 +++--- lib/modules/versioning/same-major/readme.md | 3 +++ lib/modules/versioning/semver-coerced/index.spec.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index af44c484994442..a24bd49dfaf671 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -17,9 +17,9 @@ function massageVersion(input: string): string { return input; } - // we are sure to get a majot because of the isSingleVersion check + // we are sure to get a major because of the isSingleVersion check const major = semverCoerced.getMajor(input)!; - return `>=${input} <${major + 1}.0.0`; + return `>=${input} <${major + 1}`; } function matches(version: string, range: string): boolean { @@ -41,7 +41,7 @@ function minSatisfyingVersion( } function isLessThanRange(version: string, range: string): boolean { - return !!semverCoerced.isLessThanRange?.(version, massageVersion(range)); + return semverCoerced.isLessThanRange!(version, massageVersion(range)); } export const api: VersioningApi = { diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md index 639e4e57c978fd..938b35356dc8a6 100644 --- a/lib/modules/versioning/same-major/readme.md +++ b/lib/modules/versioning/same-major/readme.md @@ -1,3 +1,6 @@ Renovate's Same Major versioning is specifically designed to address scenarios where version specifications, denoted as X.Y.Z, signify a range of compatibility from greater than or equal to X.Y.Z to less than X+1.Y.Z. In essence, each individual version is to be treated as a constraint. This method is handy when managing dependencies like dotnet-sdk's rollForward settings. Let's say a project uses dotnet-sdk version 3.1.0. It needs to be compatible with any version in the 3.x.x range but not with versions in the next major version, like 4.x.x. + +This process employs Semver-Coerced versioning beneath the surface, wherein single versions (e.g., `X.Y.Z`) are converted to a range like +`X+1.0.0` and then passed to the corresponding semver-coerced method. diff --git a/lib/modules/versioning/semver-coerced/index.spec.ts b/lib/modules/versioning/semver-coerced/index.spec.ts index d1fa4b9839a764..0b4940d0bf1795 100644 --- a/lib/modules/versioning/semver-coerced/index.spec.ts +++ b/lib/modules/versioning/semver-coerced/index.spec.ts @@ -30,7 +30,7 @@ describe('modules/versioning/semver-coerced/index', () => { }); it('invalid version', () => { - expect(semverCoerced.getMajor('')).toBeNull(); + expect(semverCoerced.getMajor('xxx')).toBeNull(); }); }); From 9b299d546e3aa6f8c33975974c259d87bf9fde7a Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Sun, 21 Apr 2024 15:40:27 +0545 Subject: [PATCH 11/26] Update lib/modules/versioning/same-major/readme.md Co-authored-by: Sebastian Poxhofer --- lib/modules/versioning/same-major/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md index 938b35356dc8a6..c5638426eb444c 100644 --- a/lib/modules/versioning/same-major/readme.md +++ b/lib/modules/versioning/same-major/readme.md @@ -1,4 +1,4 @@ -Renovate's Same Major versioning is specifically designed to address scenarios where version specifications, denoted as X.Y.Z, signify a range of compatibility from greater than or equal to X.Y.Z to less than X+1.Y.Z. In essence, each individual version is to be treated as a constraint. +Renovate's 'Same Major' versioning is specifically designed to address scenarios where version specifications, denoted as X.Y.Z, signify a range of compatibility from greater than or equal to X.Y.Z to less than X+1.Y.Z. In essence, each individual version is to be treated as a constraint. This method is handy when managing dependencies like dotnet-sdk's rollForward settings. Let's say a project uses dotnet-sdk version 3.1.0. It needs to be compatible with any version in the 3.x.x range but not with versions in the next major version, like 4.x.x. From f92d2e0a57d9a85752cdfa8048aa4e029cf91fa7 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Wed, 24 Apr 2024 17:24:18 +0545 Subject: [PATCH 12/26] Apply suggestions from code review Co-authored-by: Michael Kriese --- lib/modules/versioning/same-major/index.spec.ts | 6 +++--- lib/modules/versioning/same-major/index.ts | 2 +- lib/modules/versioning/same-major/readme.md | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 00058c34c71596..d85a4d93341e74 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -46,12 +46,12 @@ describe('modules/versioning/same-major/index', () => { describe('.isLessThanRange(version, range)', () => { it('should return true', () => { - expect(sameMajor.isLessThanRange?.('2.0.2', '3.0.0')).toBeTrue(); + expect(sameMajor.isLessThanRange('2.0.2', '3.0.0')).toBeTrue(); }); it('should return false', () => { - expect(sameMajor.isLessThanRange?.('4.0.0', '3.0.0')).toBeFalse(); - expect(sameMajor.isLessThanRange?.('3.1.0', '3.0.0')).toBeFalse(); + expect(sameMajor.isLessThanRange('4.0.0', '3.0.0')).toBeFalse(); + expect(sameMajor.isLessThanRange('3.1.0', '3.0.0')).toBeFalse(); }); }); }); diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index a24bd49dfaf671..807d002e6a3e37 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -8,7 +8,7 @@ export const supportsRanges = false; /** * - * Converts input to range if it's a version. eg. X.Y.Z -> '>=X.Y.Z '>=X.Y.Z Date: Wed, 1 May 2024 07:15:53 +0545 Subject: [PATCH 13/26] Apply suggestions from code review Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> --- lib/modules/versioning/same-major/readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md index 77035252479328..fa551748b30d01 100644 --- a/lib/modules/versioning/same-major/readme.md +++ b/lib/modules/versioning/same-major/readme.md @@ -1,8 +1,8 @@ -Renovate's 'Same Major' versioning is specifically designed to address scenarios where version specifications, denoted as X.Y.Z, signify a range of compatibility from greater than or equal to X.Y.Z to less than X+1. +Renovate's 'Same Major' versioning is designed to address scenarios where version specifications, denoted as `X.Y.Z`, signify a range of compatibility from greater than or equal to `X.Y.Z` to less than `X+1`. In essence, each individual version is to be treated as a constraint. This method is handy when managing dependencies like dotnet-sdk's rollForward settings. -Let's say a project uses dotnet-sdk version 3.1.0. -It needs to be compatible with any version in the 3.x.x range but not with versions in the next major version, like 4.x.x. +Let's say a project uses dotnet-sdk version `3.1.0`. +It needs to be compatible with any version in the `3.x.x` range but _not_ with versions in the next major version, like `4.x.x`. -This process employs Semver-Coerced versioning beneath the surface, wherein single versions (e.g., `X.Y.Z`) are converted to a range like `X+1` and then passed to the corresponding semver-coerced method. +This process uses Semver-Coerced versioning beneath the surface, single versions (e.g., `X.Y.Z`) are converted to a range like `X+1` and then passed to the corresponding semver-coerced method. From e2600ec573241d2228a1b96add33d0a96913caca Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 18:56:17 +0545 Subject: [PATCH 14/26] modify isGreaterThan --- lib/modules/versioning/same-major/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 807d002e6a3e37..941c63d3ba8b0b 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -22,6 +22,12 @@ function massageVersion(input: string): string { return `>=${input} <${major + 1}`; } +function isGreaterThan(version: string, other: string): boolean { + const major = semverCoerced.getMajor(other)!; + + return semverCoerced.isGreaterThan(version, `${major + 1}.0.0`); +} + function matches(version: string, range: string): boolean { return semverCoerced.matches(version, massageVersion(range)); } @@ -50,5 +56,6 @@ export const api: VersioningApi = { getSatisfyingVersion, minSatisfyingVersion, isLessThanRange, + isGreaterThan, }; export default api; From 6c1ad3cbf29e45ad2daee790045525db29a1b565 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 19:10:19 +0545 Subject: [PATCH 15/26] update documentation --- lib/modules/versioning/same-major/readme.md | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md index fa551748b30d01..a317cce886c422 100644 --- a/lib/modules/versioning/same-major/readme.md +++ b/lib/modules/versioning/same-major/readme.md @@ -1,8 +1,28 @@ -Renovate's 'Same Major' versioning is designed to address scenarios where version specifications, denoted as `X.Y.Z`, signify a range of compatibility from greater than or equal to `X.Y.Z` to less than `X+1`. -In essence, each individual version is to be treated as a constraint. +The 'Same Major' versioning is a `unique` type of versioning that Renovate provides, which is not implemented t handle a manager or datasource. +But instead to provide a way for users to handle the case where a version needs to treated as a constraint. +Specifically, the case where the version say, `X.Y.Z` signifies a range of compatibility from greater than or equal to `X.Y.Z` to less than `X+1`. + +This process uses Semver-Coerced versioning beneath the surface, single versions (e.g., `X.Y.Z`) are converted to a range like `X+1` and then passed to the corresponding semver-coerced method. This method is handy when managing dependencies like dotnet-sdk's rollForward settings. Let's say a project uses dotnet-sdk version `3.1.0`. It needs to be compatible with any version in the `3.x.x` range but _not_ with versions in the next major version, like `4.x.x`. -This process uses Semver-Coerced versioning beneath the surface, single versions (e.g., `X.Y.Z`) are converted to a range like `X+1` and then passed to the corresponding semver-coerced method. +For example: + +```json +{ + "sdk": { + "version": "6.0.300", + "rollForward": "major" + } +} +``` + +The roll-forward policy to use when selecting an SDK version, either as a fallback when a specific SDK version is missing or as a directive to use a higher version. In this case with `major` it means that select the latest version with the same major. +ie. `>= 6.0.300 < 7.0.0` + +For such cases, the users would not want Renovate to create an update PR for any version within the range `>= 6.0.300 < 7.0.0` as it would not change the behaviour on their end, since it is handled by the manager already. + +Note: +You should create a discussion before using this versioning as this is an experimental support and might have some edge cases unhandled. From 0dda6c80f606ca92e8094b9619b8b801660ee427 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 19:22:50 +0545 Subject: [PATCH 16/26] add tests --- .../package-rules/current-version.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/util/package-rules/current-version.spec.ts b/lib/util/package-rules/current-version.spec.ts index d8b8ee034df1a4..f2102b4540f23f 100644 --- a/lib/util/package-rules/current-version.spec.ts +++ b/lib/util/package-rules/current-version.spec.ts @@ -103,5 +103,31 @@ describe('util/package-rules/current-version', () => { ); expect(result).toBeFalse(); }); + + it('return true for same-major verisioning if version lies in expected range', () => { + const result = matcher.matches( + { + versioning: 'same-major', + currentValue: '6.0.300', + }, + { + matchCurrentVersion: '6.0.400', + }, + ); + expect(result).toBeTrue(); + }); + + it('return false for same-major verisioning if version lies outside of expected range', () => { + const result = matcher.matches( + { + versioning: 'same-major', + currentValue: '6.0.300', + }, + { + matchCurrentVersion: '6.0.100', + }, + ); + expect(result).toBeFalse(); + }); }); }); From 05341de3a4289e22534a021b0f87c1c617a141c7 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 19:22:56 +0545 Subject: [PATCH 17/26] Revert "modify isGreaterThan" This reverts commit e2600ec573241d2228a1b96add33d0a96913caca. --- lib/modules/versioning/same-major/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 941c63d3ba8b0b..807d002e6a3e37 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -22,12 +22,6 @@ function massageVersion(input: string): string { return `>=${input} <${major + 1}`; } -function isGreaterThan(version: string, other: string): boolean { - const major = semverCoerced.getMajor(other)!; - - return semverCoerced.isGreaterThan(version, `${major + 1}.0.0`); -} - function matches(version: string, range: string): boolean { return semverCoerced.matches(version, massageVersion(range)); } @@ -56,6 +50,5 @@ export const api: VersioningApi = { getSatisfyingVersion, minSatisfyingVersion, isLessThanRange, - isGreaterThan, }; export default api; From d762ba495546dab6650189c449a0aa56f6304a5f Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 19:57:59 +0545 Subject: [PATCH 18/26] modify isGreaterThan --- lib/modules/versioning/same-major/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 807d002e6a3e37..dedc29c7efa06f 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -4,7 +4,6 @@ import type { VersioningApi } from '../types'; export const id = 'same-major'; export const displayName = 'Same Major Versioning'; export const urls = []; -export const supportsRanges = false; /** * @@ -22,6 +21,13 @@ function massageVersion(input: string): string { return `>=${input} <${major + 1}`; } +function isGreaterThan(version: string, other: string): boolean { + const versionMajor = semverCoerced.getMajor(version)!; + const otherMajor = semverCoerced.getMajor(other)!; + + return versionMajor > otherMajor; +} + function matches(version: string, range: string): boolean { return semverCoerced.matches(version, massageVersion(range)); } @@ -50,5 +56,6 @@ export const api: VersioningApi = { getSatisfyingVersion, minSatisfyingVersion, isLessThanRange, + isGreaterThan, }; export default api; From 367b2bdddb37f49318286c333434edf123747325 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 21:11:36 +0545 Subject: [PATCH 19/26] fix failing test --- lib/modules/versioning/same-major/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index dedc29c7efa06f..a393128d15105f 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -4,6 +4,7 @@ import type { VersioningApi } from '../types'; export const id = 'same-major'; export const displayName = 'Same Major Versioning'; export const urls = []; +export const supportsRanges = false; /** * From 05049605903c71b61277211168fc5e40126e6b0c Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 21:18:17 +0545 Subject: [PATCH 20/26] fix failing test --- lib/modules/versioning/same-major/index.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index d85a4d93341e74..00058c34c71596 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -46,12 +46,12 @@ describe('modules/versioning/same-major/index', () => { describe('.isLessThanRange(version, range)', () => { it('should return true', () => { - expect(sameMajor.isLessThanRange('2.0.2', '3.0.0')).toBeTrue(); + expect(sameMajor.isLessThanRange?.('2.0.2', '3.0.0')).toBeTrue(); }); it('should return false', () => { - expect(sameMajor.isLessThanRange('4.0.0', '3.0.0')).toBeFalse(); - expect(sameMajor.isLessThanRange('3.1.0', '3.0.0')).toBeFalse(); + expect(sameMajor.isLessThanRange?.('4.0.0', '3.0.0')).toBeFalse(); + expect(sameMajor.isLessThanRange?.('3.1.0', '3.0.0')).toBeFalse(); }); }); }); From 150320f9966beaab36ee1a4c32c1f551ad0e93f9 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 3 Jun 2024 21:31:55 +0545 Subject: [PATCH 21/26] fix coverage --- lib/modules/versioning/same-major/index.spec.ts | 12 ++++++++++++ lib/modules/versioning/same-major/index.ts | 1 + 2 files changed, 13 insertions(+) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 00058c34c71596..02c506ef74afb0 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -1,6 +1,18 @@ import sameMajor from '.'; describe('modules/versioning/same-major/index', () => { + describe('.isGreaterThan(version, other)', () => { + it('should return true', () => { + expect(sameMajor.isGreaterThan('4.0.0', '3.0.0')).toBeTrue(); // greater + }); + + it('should return false', () => { + expect(sameMajor.isGreaterThan('2.0.2', '3.1.0')).toBeFalse(); // less + expect(sameMajor.isGreaterThan('3.1.0', '3.0.0')).toBeFalse(); // same major -> equal + expect(sameMajor.isGreaterThan('3.0.0', '3.0.0')).toBeFalse(); // equal + }); + }); + describe('.matches(version, range)', () => { it('should return true when version has same major', () => { expect(sameMajor.matches('1.0.1', '1.0.0')).toBeTrue(); diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index a393128d15105f..dcbafe64bf1970 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -22,6 +22,7 @@ function massageVersion(input: string): string { return `>=${input} <${major + 1}`; } +// for same major versioning one version is greater than the other if its major is greater function isGreaterThan(version: string, other: string): boolean { const versionMajor = semverCoerced.getMajor(version)!; const otherMajor = semverCoerced.getMajor(other)!; From 7eaa7162d6db595e97eb1805fb9b1f3dc22b2a00 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Wed, 5 Jun 2024 17:09:50 +0545 Subject: [PATCH 22/26] apply suggestions --- lib/modules/versioning/same-major/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index dcbafe64bf1970..30c7aa0d70a8d6 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -27,6 +27,10 @@ function isGreaterThan(version: string, other: string): boolean { const versionMajor = semverCoerced.getMajor(version)!; const otherMajor = semverCoerced.getMajor(other)!; + if (!versionMajor || !otherMajor) { + return false; + } + return versionMajor > otherMajor; } From 3c7cb0404922f02b31b31f9c5c16c682c9c00556 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Wed, 5 Jun 2024 17:53:05 +0545 Subject: [PATCH 23/26] fix coverage --- lib/modules/versioning/same-major/index.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 02c506ef74afb0..2facf4898a51ee 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -10,6 +10,7 @@ describe('modules/versioning/same-major/index', () => { expect(sameMajor.isGreaterThan('2.0.2', '3.1.0')).toBeFalse(); // less expect(sameMajor.isGreaterThan('3.1.0', '3.0.0')).toBeFalse(); // same major -> equal expect(sameMajor.isGreaterThan('3.0.0', '3.0.0')).toBeFalse(); // equal + expect(sameMajor.isGreaterThan('a', '3.0.0')).toBeFalse(); // invalid versions }); }); From 992223745da9081d59f0b23b213346c27c691159 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Mon, 24 Jun 2024 20:07:30 +0530 Subject: [PATCH 24/26] Update lib/modules/versioning/same-major/readme.md Co-authored-by: Rhys Arkins --- lib/modules/versioning/same-major/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/modules/versioning/same-major/readme.md b/lib/modules/versioning/same-major/readme.md index a317cce886c422..5a95fe4634d917 100644 --- a/lib/modules/versioning/same-major/readme.md +++ b/lib/modules/versioning/same-major/readme.md @@ -1,5 +1,4 @@ -The 'Same Major' versioning is a `unique` type of versioning that Renovate provides, which is not implemented t handle a manager or datasource. -But instead to provide a way for users to handle the case where a version needs to treated as a constraint. +The 'Same Major' versioning is designed to handle the case where a version needs to treated as a "greate than or equal to" constraint. Specifically, the case where the version say, `X.Y.Z` signifies a range of compatibility from greater than or equal to `X.Y.Z` to less than `X+1`. This process uses Semver-Coerced versioning beneath the surface, single versions (e.g., `X.Y.Z`) are converted to a range like `X+1` and then passed to the corresponding semver-coerced method. From a90a6da8256e45095604e393ab5f5b56b77c2f70 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Mon, 24 Jun 2024 20:27:43 +0530 Subject: [PATCH 25/26] add warning --- lib/modules/versioning/same-major/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index 30c7aa0d70a8d6..d2735fa677d52d 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -1,3 +1,4 @@ +import { logger } from '../../../logger'; import { api as semverCoerced } from '../semver-coerced'; import type { VersioningApi } from '../types'; @@ -14,6 +15,10 @@ export const supportsRanges = false; function massageVersion(input: string): string { // return the input if it is a range if (!semverCoerced.isSingleVersion(input)) { + logger.warn( + { version: input }, + 'Same major versioning expects a single version but got a range. Please switch to a different versioning as this may lead to unexpected behaviour.', + ); return input; } From dc3808229363961992ad0439e88df3223a569db6 Mon Sep 17 00:00:00 2001 From: Rahul Gautam Singh Date: Wed, 26 Jun 2024 12:49:15 +0530 Subject: [PATCH 26/26] replace test with istanbul ignore statement --- lib/modules/versioning/same-major/index.spec.ts | 2 +- lib/modules/versioning/same-major/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/versioning/same-major/index.spec.ts b/lib/modules/versioning/same-major/index.spec.ts index 2facf4898a51ee..b84791e5d33eab 100644 --- a/lib/modules/versioning/same-major/index.spec.ts +++ b/lib/modules/versioning/same-major/index.spec.ts @@ -17,7 +17,7 @@ describe('modules/versioning/same-major/index', () => { describe('.matches(version, range)', () => { it('should return true when version has same major', () => { expect(sameMajor.matches('1.0.1', '1.0.0')).toBeTrue(); - expect(sameMajor.matches('1.0.1', '^1.0.0')).toBeTrue(); // coverage for when ranges are passed to the function + expect(sameMajor.matches('1.0.0', '1.0.0')).toBeTrue(); }); it('should return false when version has different major', () => { diff --git a/lib/modules/versioning/same-major/index.ts b/lib/modules/versioning/same-major/index.ts index d2735fa677d52d..95d287f6b3d5cd 100644 --- a/lib/modules/versioning/same-major/index.ts +++ b/lib/modules/versioning/same-major/index.ts @@ -13,7 +13,7 @@ export const supportsRanges = false; * If the input is already a range, it returns the input. */ function massageVersion(input: string): string { - // return the input if it is a range + // istanbul ignore if: same-major versioning should not be used with ranges as it defeats the purpose if (!semverCoerced.isSingleVersion(input)) { logger.warn( { version: input },