diff --git a/lib/versioning/composer/index.spec.ts b/lib/versioning/composer/index.spec.ts index 95e746584f8e9fed0cbdc5abfece20ad2790ed37..62848c38bec2fa3d746971d2d71aeea8813d9263 100644 --- a/lib/versioning/composer/index.spec.ts +++ b/lib/versioning/composer/index.spec.ts @@ -12,6 +12,11 @@ describe('semver.equals(a, b)', () => { it('should pad really short version', () => { expect(semver.equals('v1.0.0', '1')).toBe(true); }); + it('should translate stability modifier', () => { + expect(semver.equals('1.0@alpha3', '1.0.0-alpha.3')).toBe(true); + expect(semver.equals('1.0@beta', '1.0.0-beta')).toBe(true); + expect(semver.equals('1.0@rc2', '1.0.0-rc.2')).toBe(true); + }); }); describe('semver.isGreaterThan(a, b)', () => { it('should pad short version', () => { @@ -49,6 +54,11 @@ describe('semver.isValid(input)', () => { expect(semver.isValid('^1.2.3')).toBeTruthy(); expect(semver.isValid('>1.2.3')).toBeTruthy(); }); + it('should support ranges with stability modifiers', () => { + expect(semver.isValid('~1.2.3@beta1')).toBeTruthy(); + expect(semver.isValid('^1.2.3@alpha')).toBeTruthy(); + expect(semver.isValid('>1.2.3@rc2')).toBeTruthy(); + }); }); describe('semver.isVersion(input)', () => { it('should support simple semver', () => { @@ -88,6 +98,22 @@ describe('semver.maxSatisfyingVersion()', () => { ) ).toBe('0.5.0'); }); + it('handles prereleases', () => { + expect( + semver.maxSatisfyingVersion( + [ + '0.4.0', + '0.5.0', + '4.0.0-beta1', + '4.0.0-beta2', + '4.2.0-beta1', + '4.2.0-beta2', + '5.0.0', + ], + '~4@beta' + ) + ).toBe('4.0.0-beta2'); + }); }); describe('semver.minSatisfyingVersion()', () => { it('handles massaged tilde', () => { @@ -110,6 +136,22 @@ describe('semver.minSatisfyingVersion()', () => { ) ).toBe('0.4.0'); }); + it('handles prereleases', () => { + expect( + semver.minSatisfyingVersion( + [ + '0.4.0', + '0.5.0', + '4.0.0-beta1', + '4.0.0', + '4.2.0-beta1', + '4.2.0-beta2', + '5.0.0', + ], + '~4@beta' + ) + ).toBe('4.0.0-beta1'); + }); }); describe('semver.matches()', () => { it('handles massaged tilde', () => { @@ -286,6 +328,26 @@ describe('semver.getNewValue()', () => { }) ).toEqual('^v1.1'); }); + it('bumps short caret with stability modifiers', () => { + expect( + semver.getNewValue({ + currentValue: '^v1.0@beta', + rangeStrategy: 'bump', + fromVersion: '1.0.0-beta3', + toVersion: '1.0.0-beta5', + }) + ).toEqual('^v1.0.0@beta5'); + }); + it('replaces short caret with stability modifiers', () => { + expect( + semver.getNewValue({ + currentValue: '^v1.0@beta', + rangeStrategy: 'replace', + fromVersion: '1.0.0-beta3', + toVersion: '2.0.0-beta5', + }) + ).toEqual('^v2.0.0@beta5'); + }); it('handles differing lengths', () => { expect( semver.getNewValue({ diff --git a/lib/versioning/composer/index.ts b/lib/versioning/composer/index.ts index c66b70561ab55d91d5b2f135d0b0853fd63443f1..e58003e2c991f5db10e91c3e2c5a4d5c54a205ec 100644 --- a/lib/versioning/composer/index.ts +++ b/lib/versioning/composer/index.ts @@ -14,32 +14,66 @@ export const urls = [ export const supportsRanges = true; export const supportedRangeStrategies = ['bump', 'extend', 'pin', 'replace']; +function getVersionParts(input: string): [string, string] { + const versionParts = input.split('-'); + if (versionParts.length === 1) { + return [input, '']; + } + + return [versionParts[0], '-' + versionParts[1]]; +} + function padZeroes(input: string): string { - const sections = input.split('.'); + const [output, stability] = getVersionParts(input); + + const sections = output.split('.'); while (sections.length < 3) { sections.push('0'); } - return sections.join('.'); + return sections.join('.') + stability; } -function removeLeadingV(input: string): string { - return input.replace(/^v/i, ''); +function convertStabilitiyModifier(input: string): string { + // Handle stability modifiers. + const versionParts = input.split('@'); + if (versionParts.length === 1) { + return input; + } + + // 1.0@beta2 to 1.0-beta.2 + const stability = versionParts[1].replace( + /(?:^|\s)(beta|alpha|rc)([1-9][0-9]*)(?: |$)/gi, + '$1.$2' + ); + + // If there is a stability part, npm semver expects the version + // to be full + return padZeroes(versionParts[0]) + '-' + stability; +} + +function normalizeVersion(input: string): string { + let output = input; + output = output.replace(/(^|>|>=|\^|~)v/i, '$1'); + return convertStabilitiyModifier(output); } function composer2npm(input: string): string { - const cleanInput = removeLeadingV(input); + const cleanInput = normalizeVersion(input); if (npm.isVersion(cleanInput)) { return cleanInput; } if (npm.isVersion(padZeroes(cleanInput))) { return padZeroes(cleanInput); } - let output = cleanInput; + const [versionId, stability] = getVersionParts(cleanInput); + let output = versionId; + // ~4 to ^4 and ~4.1 to ^4.1 output = output.replace(/(?:^|\s)~([1-9][0-9]*(?:\.[0-9]*)?)(?: |$)/g, '^$1'); // ~0.4 to >=0.4 <1 output = output.replace(/(?:^|\s)~(0\.[1-9][0-9]*)(?: |$)/g, '>=$1 <1'); - return output; + + return output + stability; } const equals = (a: string, b: string): boolean => @@ -116,15 +150,15 @@ function getNewValue({ newValue = `${operator}${toMajor}.${toMinor}`; } } else if ( - npm.isVersion(padZeroes(toVersion)) && - npm.isValid(currentValue) && - composer2npm(currentValue) === removeLeadingV(currentValue) + npm.isVersion(padZeroes(normalizeVersion(toVersion))) && + npm.isValid(normalizeVersion(currentValue)) && + composer2npm(currentValue) === normalizeVersion(currentValue) ) { newValue = npm.getNewValue({ - currentValue, + currentValue: normalizeVersion(currentValue), rangeStrategy, - fromVersion, - toVersion: padZeroes(toVersion), + fromVersion: normalizeVersion(fromVersion), + toVersion: padZeroes(normalizeVersion(toVersion)), }); } if (currentValue.includes(' || ')) { @@ -151,7 +185,7 @@ function getNewValue({ if (currentValue.split('.')[0].includes('v')) { newValue = newValue.replace(/([0-9])/, 'v$1'); } - return newValue; + return newValue.replace('-', '@'); } function sortVersions(a: string, b: string): number {