1 | import debugFactory from 'debug';
|
2 | import semver from 'semver';
|
3 |
|
4 | const debug = debugFactory('clark:lib:version');
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | enum RangeOperator {
|
10 | |
11 |
|
12 |
|
13 | Caret = '^',
|
14 | |
15 |
|
16 |
|
17 | Tilde = '~',
|
18 | |
19 |
|
20 |
|
21 | Exact = '',
|
22 | }
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | function extractExactVersion(version: string): string {
|
29 | const exact = semver.clean(
|
30 | version.replace(RangeOperator.Caret, '').replace(RangeOperator.Tilde, ''),
|
31 | );
|
32 |
|
33 | if (!exact) {
|
34 | throw new Error(`"${version}" is not a valid semver`);
|
35 | }
|
36 |
|
37 | return exact;
|
38 | }
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | function extractRangeOperator(version: string): RangeOperator {
|
45 | if (version.startsWith('^')) {
|
46 | return RangeOperator.Caret;
|
47 | }
|
48 | if (version.startsWith('~')) {
|
49 | return RangeOperator.Tilde;
|
50 | }
|
51 |
|
52 | return RangeOperator.Exact;
|
53 | }
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | function extractMostPermissiveOperator(
|
61 | left: string,
|
62 | right: string,
|
63 | ): RangeOperator {
|
64 | const leftType = extractRangeOperator(left);
|
65 | const rightType = extractRangeOperator(right);
|
66 |
|
67 | if (leftType === RangeOperator.Caret || rightType === RangeOperator.Caret) {
|
68 | return RangeOperator.Caret;
|
69 | }
|
70 |
|
71 | if (leftType === RangeOperator.Tilde || rightType === RangeOperator.Tilde) {
|
72 | return RangeOperator.Tilde;
|
73 | }
|
74 |
|
75 | return RangeOperator.Exact;
|
76 | }
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | function hasSameOperator(left: string, right: string): boolean {
|
84 | const leftType = extractRangeOperator(left);
|
85 | const rightType = extractRangeOperator(right);
|
86 | return leftType === rightType;
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | export function select(left: string | null, right: string | null): string {
|
96 | debug(`checking if "${left}" and "${right}" are compatible`);
|
97 |
|
98 |
|
99 |
|
100 | if (left === null) {
|
101 | if (right) {
|
102 | return right;
|
103 | }
|
104 | throw new Error('Cannot select a version from "null" and "null"');
|
105 | }
|
106 |
|
107 | if (right === null) {
|
108 | if (left) {
|
109 | return left;
|
110 | }
|
111 | throw new Error('Cannot select a version from "null" and "null"');
|
112 | }
|
113 |
|
114 | if (!semver.intersects(left, right)) {
|
115 | debug(`"${left}" and "${right}" are not compatible`);
|
116 | throw new Error(`"${left}" and "${right}" are not compatible`);
|
117 | }
|
118 |
|
119 | debug(`"${left}" and "${right}" are compatible`);
|
120 |
|
121 | const leftExact = extractExactVersion(left);
|
122 | const rightExact = extractExactVersion(right);
|
123 |
|
124 | debug(`checking if "${left}" and "${right}" have the same range operator`);
|
125 | if (hasSameOperator(left, right)) {
|
126 | debug(`"${left}" and "${right}" have the same range operator`);
|
127 | if (semver.gt(leftExact, rightExact)) {
|
128 | return left;
|
129 | } else {
|
130 | return right;
|
131 | }
|
132 | }
|
133 |
|
134 | debug(`"${left}" and "${right}" do not have the same range operator`);
|
135 |
|
136 | const operator = extractMostPermissiveOperator(left, right);
|
137 |
|
138 | return `${operator}${
|
139 | semver.gt(leftExact, rightExact) ? leftExact : rightExact
|
140 | }`;
|
141 | }
|