Skip to content

Commit 6136374

Browse files
authored
bumpMinSemverRange improvements (#1110)
1 parent 63b6798 commit 6136374

3 files changed

Lines changed: 76 additions & 33 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Properly handle bumping more types of semver ranges",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

src/__tests__/bump/bumpMinSemverRange.test.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from '@jest/globals';
22
import { bumpMinSemverRange } from '../../bump/bumpMinSemverRange';
33

44
describe('bumpMinSemverRange', () => {
5-
it('bumps * to *', () => {
5+
it('preserves *', () => {
66
const result = bumpMinSemverRange('1.0.0', '*');
77
expect(result).toBe('*');
88
});
@@ -17,43 +17,60 @@ describe('bumpMinSemverRange', () => {
1717
expect(result).toBe('file:/absolute/path/to/package');
1818
});
1919

20-
it('attaches ~ to semver range', () => {
21-
const result = bumpMinSemverRange('1.3.0', '~1.2.0');
22-
expect(result).toBe('~1.3.0');
20+
it('preserves catalog: protocol', () => {
21+
let result = bumpMinSemverRange('1.0.0', 'catalog:');
22+
expect(result).toBe('catalog:');
23+
result = bumpMinSemverRange('1.0.0', 'catalog:foo');
24+
expect(result).toBe('catalog:foo');
2325
});
2426

25-
it('bumps ^ to semver range', () => {
26-
const result = bumpMinSemverRange('1.3.0', '^1.2.0');
27-
expect(result).toBe('^1.3.0');
27+
it.each(['~', '^'])('preserves %s and bumps to new version', prefix => {
28+
const result = bumpMinSemverRange('1.3.0', `${prefix}1.2.0`);
29+
expect(result).toBe(`${prefix}1.3.0`);
2830
});
2931

30-
it('will return the min version if unknown format', () => {
31-
const result = bumpMinSemverRange('1.3.0', '#1.2.0');
32-
expect(result).toBe('1.3.0');
32+
it('returns range from new version to next major with >=', () => {
33+
const result = bumpMinSemverRange('1.3.0', '>=1.2.0 <2.0.0');
34+
expect(result).toBe('>=1.3.0 <2.0.0');
3335
});
3436

35-
it('will return a minor range generally if a range is specified with >= or >', () => {
36-
const result = bumpMinSemverRange('1.3.0', '>=1.2.0 <2.0.0');
37+
it('returns range from new version to next major with >', () => {
38+
const result = bumpMinSemverRange('1.3.0', '>1.2.0 <2.0.0');
3739
expect(result).toBe('>=1.3.0 <2.0.0');
3840
});
3941

40-
it('will return a minor range generally if a range is specified with x - y', () => {
42+
it('returns range from new version to next major with -', () => {
4143
const result = bumpMinSemverRange('1.3.0', '1.2.0 - 2.0.0');
4244
expect(result).toBe('1.3.0 - 2.0.0');
4345
});
4446

45-
it.each(['workspace:*', 'workspace:~', 'workspace:^'])('will preserve %s', workspaceVersion => {
47+
it.each(['workspace:*', 'workspace:~', 'workspace:^'])('preserves %s', workspaceVersion => {
4648
const result = bumpMinSemverRange('1.3.0', workspaceVersion);
4749
expect(result).toBe(workspaceVersion);
4850
});
4951

50-
it('bumps workspace:~1.2.0 to workspace semver range', () => {
52+
it('bumps workspace:~x.y.z to workspace range with new version', () => {
5153
const result = bumpMinSemverRange('1.2.1', 'workspace:~1.2.0');
5254
expect(result).toBe('workspace:~1.2.1');
5355
});
5456

55-
it('bumps workspace:^1.2.0 to workspace semver range', () => {
57+
it('bumps workspace:^x.y.z to workspace range with new version', () => {
5658
const result = bumpMinSemverRange('1.3.0', 'workspace:^1.2.0');
5759
expect(result).toBe('workspace:^1.3.0');
5860
});
61+
62+
it('uses the new version for exact version match', () => {
63+
const result = bumpMinSemverRange('1.3.0', '1.2.0');
64+
expect(result).toBe('1.3.0');
65+
});
66+
67+
it('uses the new version if unknown non-semver format', () => {
68+
const result = bumpMinSemverRange('1.3.0', '#1.2.0');
69+
expect(result).toBe('1.3.0');
70+
});
71+
72+
it('preserves unrecognized range if new version satisfies it', () => {
73+
const result = bumpMinSemverRange('1.3.0', '1');
74+
expect(result).toBe('1');
75+
});
5976
});

src/bump/bumpMinSemverRange.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,56 @@
11
import semver from 'semver';
22
import { getWorkspaceRange } from '../packageManager/getWorkspaceRange';
33

4-
export function bumpMinSemverRange(minVersion: string, semverRange: string): string {
5-
if (semverRange === '*' || semverRange.startsWith('file:')) {
6-
return semverRange;
4+
/**
5+
* Bump the semver range for a dependency to match the new version of a package.
6+
* @param newVersion The new version of the package
7+
* @param currentRange Current version range for the dependency.
8+
* @returns New semver range for the dependency
9+
*/
10+
export function bumpMinSemverRange(newVersion: string, currentRange: string): string {
11+
if (currentRange === '*' || currentRange.startsWith('file:') || currentRange.startsWith('catalog:')) {
12+
return currentRange;
713
}
814

9-
const workspaceRange = getWorkspaceRange(semverRange);
15+
if (currentRange[0] === '~' || currentRange[0] === '^') {
16+
// ~1.0.0
17+
// ^1.0.0
18+
return currentRange[0] + newVersion;
19+
}
1020

21+
const workspaceRange = getWorkspaceRange(currentRange);
1122
if (workspaceRange === '*' || workspaceRange === '~' || workspaceRange === '^') {
1223
// For basic workspace ranges we can just preserve current value and replace during publish
1324
// https://pnpm.io/workspaces#workspace-protocol-workspace
14-
return semverRange;
15-
}
16-
if (semverRange[0] === '~' || semverRange[0] === '^') {
17-
// ~1.0.0
18-
// ^1.0.0
19-
return semverRange[0] + minVersion;
25+
return currentRange;
2026
}
2127
if (workspaceRange && (workspaceRange[0] === '~' || workspaceRange[0] === '^')) {
2228
// workspace:~1.0.0
2329
// workspace:^1.0.0
24-
return `workspace:${workspaceRange[0]}${minVersion}`;
30+
return `workspace:${workspaceRange[0]}${newVersion}`;
2531
}
26-
if (semverRange.includes('>')) {
27-
// Less frequently used, but we treat any of these kinds of ranges to be within a minor band for now:
32+
33+
if (currentRange.includes('>')) {
34+
// Less frequently used, but use the new version as a minimum for this kind of range.
2835
// more complex understanding of the semver range utility is needed to do more
2936
// >=1.0.0 <2.0.0
30-
return `>=${minVersion} <${semver.inc(minVersion, 'major')}`;
37+
return `>=${newVersion} <${semver.inc(newVersion, 'major')}`;
3138
}
32-
if (semverRange.includes(' - ')) {
39+
if (currentRange.includes(' - ')) {
3340
// 1.0.0 - 2.0.0
34-
return `${minVersion} - ${semver.inc(minVersion, 'major')}`;
41+
return `${newVersion} - ${semver.inc(newVersion, 'major')}`;
42+
}
43+
44+
if (semver.valid(currentRange)) {
45+
// Exact version match, e.g. 1.0.0
46+
return newVersion;
3547
}
36-
return minVersion;
48+
49+
// For unrecognized valid semver ranges: if the new version satisfies the current range, keep it
50+
if (semver.validRange(currentRange) && semver.satisfies(newVersion, currentRange)) {
51+
return currentRange;
52+
}
53+
54+
// Fallback: return the exact new version
55+
return newVersion;
3756
}

0 commit comments

Comments
 (0)