Skip to content

Commit 896f0a0

Browse files
authored
Extract semver utils to separate file (#357)
1 parent e3d2ed6 commit 896f0a0

File tree

5 files changed

+188
-179
lines changed

5 files changed

+188
-179
lines changed

lib/dependency-versions.ts

Lines changed: 8 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import semver from 'semver';
21
import editJsonFile from 'edit-json-file';
32
import { Package } from './package.js';
3+
import {
4+
compareVersionRanges,
5+
compareVersionRangesSafe,
6+
versionRangeToRange,
7+
getLatestVersion,
8+
getHighestRangeType,
9+
} from './semver.js';
10+
import semver from 'semver';
411

512
export type DependenciesToVersionsSeen = Map<
613
string,
@@ -341,66 +348,3 @@ export function fixMismatchingVersions(
341348
notFixed,
342349
};
343350
}
344-
345-
// This version doesn't throw for when we want to ignore invalid versions that might be present.
346-
export function compareVersionRangesSafe(a: string, b: string): 0 | -1 | 1 {
347-
try {
348-
return compareVersionRanges(a, b);
349-
} catch {
350-
return 0;
351-
}
352-
}
353-
354-
// Compare semver version ranges like ^1.0.0, ~2.5.0, 3.0.0, etc.
355-
export function compareVersionRanges(a: string, b: string): 0 | -1 | 1 {
356-
// Coerce to normalized version without any range prefix.
357-
const aVersion = semver.coerce(a);
358-
const bVersion = semver.coerce(b);
359-
if (!aVersion) {
360-
throw new Error(`Invalid Version: ${a}`);
361-
}
362-
if (!bVersion) {
363-
throw new Error(`Invalid Version: ${b}`);
364-
}
365-
366-
if (semver.eq(aVersion, bVersion)) {
367-
// Same version, so decide which range is considered higher.
368-
const aRange = versionRangeToRange(a);
369-
const bRange = versionRangeToRange(b);
370-
return compareRanges(aRange, bRange);
371-
}
372-
373-
// Greater version considered higher.
374-
return semver.gt(aVersion, bVersion) ? 1 : -1;
375-
}
376-
377-
const RANGE_PRECEDENCE = ['~', '^']; // Lowest to highest.
378-
379-
// Compare semver ranges like ^, ~, etc.
380-
export function compareRanges(aRange: string, bRange: string): 0 | -1 | 1 {
381-
const aRangePrecedence = RANGE_PRECEDENCE.indexOf(aRange);
382-
const bRangePrecedence = RANGE_PRECEDENCE.indexOf(bRange);
383-
if (aRangePrecedence > bRangePrecedence) {
384-
return 1;
385-
} else if (aRangePrecedence < bRangePrecedence) {
386-
return -1;
387-
}
388-
return 0;
389-
}
390-
391-
// Example input: ^1.0.0, output: ^
392-
export function versionRangeToRange(versionRange: string): string {
393-
const match = versionRange.match(/^\D+/);
394-
return match ? match[0] : '';
395-
}
396-
397-
export function getLatestVersion(versions: string[]): string {
398-
const sortedVersions = versions.sort(compareVersionRanges);
399-
return sortedVersions[sortedVersions.length - 1]; // Latest version will be sorted to end of list.
400-
}
401-
402-
// Example input: ['~', '^'], output: '^'
403-
export function getHighestRangeType(ranges: string[]): string {
404-
const sorted = ranges.sort(compareRanges);
405-
return sorted[sorted.length - 1]; // Range with highest precedence will be sorted to end of list.
406-
}

lib/output.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import chalk from 'chalk';
22
import type { MismatchingDependencyVersions } from './dependency-versions.js';
3-
import {
4-
compareVersionRangesSafe,
5-
getLatestVersion,
6-
} from './dependency-versions.js';
3+
import { compareVersionRangesSafe, getLatestVersion } from './semver.js';
74
import { table } from 'table';
85

96
export function mismatchingVersionsToOutput(

lib/semver.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import semver from 'semver';
2+
3+
// This version doesn't throw for when we want to ignore invalid versions that might be present.
4+
export function compareVersionRangesSafe(a: string, b: string): 0 | -1 | 1 {
5+
try {
6+
return compareVersionRanges(a, b);
7+
} catch {
8+
return 0;
9+
}
10+
}
11+
12+
// Compare semver version ranges like ^1.0.0, ~2.5.0, 3.0.0, etc.
13+
export function compareVersionRanges(a: string, b: string): 0 | -1 | 1 {
14+
// Coerce to normalized version without any range prefix.
15+
const aVersion = semver.coerce(a);
16+
const bVersion = semver.coerce(b);
17+
if (!aVersion) {
18+
throw new Error(`Invalid Version: ${a}`);
19+
}
20+
if (!bVersion) {
21+
throw new Error(`Invalid Version: ${b}`);
22+
}
23+
24+
if (semver.eq(aVersion, bVersion)) {
25+
// Same version, so decide which range is considered higher.
26+
const aRange = versionRangeToRange(a);
27+
const bRange = versionRangeToRange(b);
28+
return compareRanges(aRange, bRange);
29+
}
30+
31+
// Greater version considered higher.
32+
return semver.gt(aVersion, bVersion) ? 1 : -1;
33+
}
34+
35+
const RANGE_PRECEDENCE = ['~', '^']; // Lowest to highest.
36+
37+
// Compare semver ranges like ^, ~, etc.
38+
export function compareRanges(aRange: string, bRange: string): 0 | -1 | 1 {
39+
const aRangePrecedence = RANGE_PRECEDENCE.indexOf(aRange);
40+
const bRangePrecedence = RANGE_PRECEDENCE.indexOf(bRange);
41+
if (aRangePrecedence > bRangePrecedence) {
42+
return 1;
43+
} else if (aRangePrecedence < bRangePrecedence) {
44+
return -1;
45+
}
46+
return 0;
47+
}
48+
49+
// Example input: ^1.0.0, output: ^
50+
export function versionRangeToRange(versionRange: string): string {
51+
const match = versionRange.match(/^\D+/);
52+
return match ? match[0] : '';
53+
}
54+
55+
export function getLatestVersion(versions: string[]): string {
56+
const sortedVersions = versions.sort(compareVersionRanges);
57+
return sortedVersions[sortedVersions.length - 1]; // Latest version will be sorted to end of list.
58+
}
59+
60+
// Example input: ['~', '^'], output: '^'
61+
export function getHighestRangeType(ranges: string[]): string {
62+
const sorted = ranges.sort(compareRanges);
63+
return sorted[sorted.length - 1]; // Range with highest precedence will be sorted to end of list.
64+
}

test/lib/dependency-versions-test.ts

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ import {
33
calculateMismatchingVersions,
44
filterOutIgnoredDependencies,
55
fixMismatchingVersions,
6-
compareVersionRanges,
7-
compareVersionRangesSafe,
8-
compareRanges,
9-
versionRangeToRange,
10-
getLatestVersion,
11-
getHighestRangeType,
126
} from '../../lib/dependency-versions.js';
137
import { getPackages } from '../../lib/workspace.js';
148
import {
@@ -691,109 +685,4 @@ describe('Utils | dependency-versions', function () {
691685
});
692686
});
693687
});
694-
695-
describe('#compareVersionRanges', function () {
696-
it('correctly chooses the higher range', function () {
697-
// 1 (greater than)
698-
expect(compareVersionRanges('1.2.3', '1.2.2')).toStrictEqual(1);
699-
expect(compareVersionRanges('5.0.0', '4.0.0')).toStrictEqual(1);
700-
expect(compareVersionRanges('8.0.0-beta.1', '^7')).toStrictEqual(1);
701-
expect(compareVersionRanges('^5.0.0', '4.0.0')).toStrictEqual(1);
702-
expect(compareVersionRanges('^5.0.0', '^4.0.0')).toStrictEqual(1);
703-
expect(compareVersionRanges('^5.0.0', '~4.0.0')).toStrictEqual(1);
704-
expect(compareVersionRanges('^5.0.0', '~5.0.0')).toStrictEqual(1);
705-
expect(compareVersionRanges('~5.0.0', '5.0.0')).toStrictEqual(1);
706-
expect(compareVersionRanges('~5.0.0', '~4.0.0')).toStrictEqual(1);
707-
708-
// -1 (less than)
709-
expect(compareVersionRanges('4.0.0', '5.0.0')).toStrictEqual(-1);
710-
expect(compareVersionRanges('5.0.0', '~5.0.0')).toStrictEqual(-1);
711-
expect(compareVersionRanges('^4.0.0', '^5.0.0')).toStrictEqual(-1);
712-
expect(compareVersionRanges('~4.0.0', '~5.0.0')).toStrictEqual(-1);
713-
expect(compareVersionRanges('~5.0.0', '^5.0.0')).toStrictEqual(-1);
714-
715-
// 0 (equal)
716-
expect(compareVersionRanges('6', '6')).toStrictEqual(0);
717-
expect(compareVersionRanges('6.0', '6.0')).toStrictEqual(0);
718-
expect(compareVersionRanges('6.0.0', '6.0.0')).toStrictEqual(0);
719-
expect(compareVersionRanges('^6.0.0', '^6.0.0')).toStrictEqual(0);
720-
expect(compareVersionRanges('v6', '6')).toStrictEqual(0);
721-
expect(compareVersionRanges('~6.0.0', '~6.0.0')).toStrictEqual(0);
722-
});
723-
724-
it('throws with invalid ranges', function () {
725-
expect(() =>
726-
compareVersionRanges('foo', '~6.0.0')
727-
).toThrowErrorMatchingInlineSnapshot('"Invalid Version: foo"');
728-
expect(() =>
729-
compareVersionRanges('~6.0.0', 'foo')
730-
).toThrowErrorMatchingInlineSnapshot('"Invalid Version: foo"');
731-
});
732-
});
733-
734-
describe('#compareRanges', function () {
735-
it('behaves correctly', function () {
736-
// gt
737-
expect(compareRanges('^', '~')).toStrictEqual(1);
738-
expect(compareRanges('^', '')).toStrictEqual(1);
739-
expect(compareRanges('~', '')).toStrictEqual(1);
740-
741-
// eq
742-
expect(compareRanges('', '')).toStrictEqual(0);
743-
expect(compareRanges('~', '~')).toStrictEqual(0);
744-
expect(compareRanges('^', '^')).toStrictEqual(0);
745-
746-
// lt
747-
expect(compareRanges('', '~')).toStrictEqual(-1);
748-
expect(compareRanges('', '^')).toStrictEqual(-1);
749-
expect(compareRanges('~', '^')).toStrictEqual(-1);
750-
});
751-
});
752-
753-
describe('#versionRangeToRange', function () {
754-
it('behaves correctly', function () {
755-
expect(versionRangeToRange('>1.0.0')).toStrictEqual('>');
756-
expect(versionRangeToRange('>=1.0.0')).toStrictEqual('>=');
757-
expect(versionRangeToRange('^1.0.0')).toStrictEqual('^');
758-
expect(versionRangeToRange('~1.0.0')).toStrictEqual('~');
759-
expect(versionRangeToRange('1.0.0')).toStrictEqual('');
760-
});
761-
});
762-
763-
describe('#compareVersionRangesSafe', function () {
764-
it('behaves correctly', function () {
765-
expect(compareVersionRangesSafe('1.2.3', '1.2.2')).toStrictEqual(1);
766-
expect(compareVersionRangesSafe('4.0.0', '5.0.0')).toStrictEqual(-1);
767-
expect(compareVersionRangesSafe('6', '6')).toStrictEqual(0);
768-
});
769-
770-
it('does not throw with invalid ranges', function () {
771-
expect(compareVersionRangesSafe('foo', '~6.0.0')).toStrictEqual(0);
772-
expect(compareVersionRangesSafe('~6.0.0', 'foo')).toStrictEqual(0);
773-
});
774-
});
775-
776-
describe('#getLatestVersion', function () {
777-
it('correctly chooses the higher range', function () {
778-
// Just basic sanity checks to ensure the data is passed through to `compareVersionRanges` which has extensive tests.
779-
expect(getLatestVersion(['1.2.3', '1.2.2'])).toStrictEqual('1.2.3');
780-
expect(getLatestVersion(['1.2.2', '1.2.3'])).toStrictEqual('1.2.3');
781-
});
782-
783-
it('throws with invalid version', function () {
784-
expect(() =>
785-
getLatestVersion(['1.2.3', 'foo'])
786-
).toThrowErrorMatchingInlineSnapshot('"Invalid Version: foo"');
787-
});
788-
});
789-
790-
describe('#getHighestRangeType', function () {
791-
it('behaves correctly', function () {
792-
expect(getHighestRangeType(['', ''])).toStrictEqual('');
793-
expect(getHighestRangeType(['^', ''])).toStrictEqual('^');
794-
expect(getHighestRangeType(['~', ''])).toStrictEqual('~');
795-
expect(getHighestRangeType(['~', '^'])).toStrictEqual('^');
796-
expect(getHighestRangeType(['^', '~'])).toStrictEqual('^');
797-
});
798-
});
799688
});

test/lib/semver-test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
compareVersionRanges,
3+
compareVersionRangesSafe,
4+
compareRanges,
5+
versionRangeToRange,
6+
getLatestVersion,
7+
getHighestRangeType,
8+
} from '../../lib/semver.js';
9+
10+
describe('Utils | semver', function () {
11+
describe('#compareVersionRanges', function () {
12+
it('correctly chooses the higher range', function () {
13+
// 1 (greater than)
14+
expect(compareVersionRanges('1.2.3', '1.2.2')).toStrictEqual(1);
15+
expect(compareVersionRanges('5.0.0', '4.0.0')).toStrictEqual(1);
16+
expect(compareVersionRanges('8.0.0-beta.1', '^7')).toStrictEqual(1);
17+
expect(compareVersionRanges('^5.0.0', '4.0.0')).toStrictEqual(1);
18+
expect(compareVersionRanges('^5.0.0', '^4.0.0')).toStrictEqual(1);
19+
expect(compareVersionRanges('^5.0.0', '~4.0.0')).toStrictEqual(1);
20+
expect(compareVersionRanges('^5.0.0', '~5.0.0')).toStrictEqual(1);
21+
expect(compareVersionRanges('~5.0.0', '5.0.0')).toStrictEqual(1);
22+
expect(compareVersionRanges('~5.0.0', '~4.0.0')).toStrictEqual(1);
23+
24+
// -1 (less than)
25+
expect(compareVersionRanges('4.0.0', '5.0.0')).toStrictEqual(-1);
26+
expect(compareVersionRanges('5.0.0', '~5.0.0')).toStrictEqual(-1);
27+
expect(compareVersionRanges('^4.0.0', '^5.0.0')).toStrictEqual(-1);
28+
expect(compareVersionRanges('~4.0.0', '~5.0.0')).toStrictEqual(-1);
29+
expect(compareVersionRanges('~5.0.0', '^5.0.0')).toStrictEqual(-1);
30+
31+
// 0 (equal)
32+
expect(compareVersionRanges('6', '6')).toStrictEqual(0);
33+
expect(compareVersionRanges('6.0', '6.0')).toStrictEqual(0);
34+
expect(compareVersionRanges('6.0.0', '6.0.0')).toStrictEqual(0);
35+
expect(compareVersionRanges('^6.0.0', '^6.0.0')).toStrictEqual(0);
36+
expect(compareVersionRanges('v6', '6')).toStrictEqual(0);
37+
expect(compareVersionRanges('~6.0.0', '~6.0.0')).toStrictEqual(0);
38+
});
39+
40+
it('throws with invalid ranges', function () {
41+
expect(() =>
42+
compareVersionRanges('foo', '~6.0.0')
43+
).toThrowErrorMatchingInlineSnapshot('"Invalid Version: foo"');
44+
expect(() =>
45+
compareVersionRanges('~6.0.0', 'foo')
46+
).toThrowErrorMatchingInlineSnapshot('"Invalid Version: foo"');
47+
});
48+
});
49+
50+
describe('#compareRanges', function () {
51+
it('behaves correctly', function () {
52+
// gt
53+
expect(compareRanges('^', '~')).toStrictEqual(1);
54+
expect(compareRanges('^', '')).toStrictEqual(1);
55+
expect(compareRanges('~', '')).toStrictEqual(1);
56+
57+
// eq
58+
expect(compareRanges('', '')).toStrictEqual(0);
59+
expect(compareRanges('~', '~')).toStrictEqual(0);
60+
expect(compareRanges('^', '^')).toStrictEqual(0);
61+
62+
// lt
63+
expect(compareRanges('', '~')).toStrictEqual(-1);
64+
expect(compareRanges('', '^')).toStrictEqual(-1);
65+
expect(compareRanges('~', '^')).toStrictEqual(-1);
66+
});
67+
});
68+
69+
describe('#versionRangeToRange', function () {
70+
it('behaves correctly', function () {
71+
expect(versionRangeToRange('>1.0.0')).toStrictEqual('>');
72+
expect(versionRangeToRange('>=1.0.0')).toStrictEqual('>=');
73+
expect(versionRangeToRange('^1.0.0')).toStrictEqual('^');
74+
expect(versionRangeToRange('~1.0.0')).toStrictEqual('~');
75+
expect(versionRangeToRange('1.0.0')).toStrictEqual('');
76+
});
77+
});
78+
79+
describe('#compareVersionRangesSafe', function () {
80+
it('behaves correctly', function () {
81+
expect(compareVersionRangesSafe('1.2.3', '1.2.2')).toStrictEqual(1);
82+
expect(compareVersionRangesSafe('4.0.0', '5.0.0')).toStrictEqual(-1);
83+
expect(compareVersionRangesSafe('6', '6')).toStrictEqual(0);
84+
});
85+
86+
it('does not throw with invalid ranges', function () {
87+
expect(compareVersionRangesSafe('foo', '~6.0.0')).toStrictEqual(0);
88+
expect(compareVersionRangesSafe('~6.0.0', 'foo')).toStrictEqual(0);
89+
});
90+
});
91+
92+
describe('#getLatestVersion', function () {
93+
it('correctly chooses the higher range', function () {
94+
// Just basic sanity checks to ensure the data is passed through to `compareVersionRanges` which has extensive tests.
95+
expect(getLatestVersion(['1.2.3', '1.2.2'])).toStrictEqual('1.2.3');
96+
expect(getLatestVersion(['1.2.2', '1.2.3'])).toStrictEqual('1.2.3');
97+
});
98+
99+
it('throws with invalid version', function () {
100+
expect(() =>
101+
getLatestVersion(['1.2.3', 'foo'])
102+
).toThrowErrorMatchingInlineSnapshot('"Invalid Version: foo"');
103+
});
104+
});
105+
106+
describe('#getHighestRangeType', function () {
107+
it('behaves correctly', function () {
108+
expect(getHighestRangeType(['', ''])).toStrictEqual('');
109+
expect(getHighestRangeType(['^', ''])).toStrictEqual('^');
110+
expect(getHighestRangeType(['~', ''])).toStrictEqual('~');
111+
expect(getHighestRangeType(['~', '^'])).toStrictEqual('^');
112+
expect(getHighestRangeType(['^', '~'])).toStrictEqual('^');
113+
});
114+
});
115+
});

0 commit comments

Comments
 (0)