generated from garronej/ts-ci
-
-
Notifications
You must be signed in to change notification settings - Fork 38
Expand file tree
/
Copy pathSemVer.ts
More file actions
119 lines (99 loc) · 3.49 KB
/
SemVer.ts
File metadata and controls
119 lines (99 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
export type SemVer = {
major: number;
minor: number;
patch: number;
rc?: number;
parsedFrom: string;
};
export namespace SemVer {
const bumpTypes = ["major", "minor", "patch", "rc", "no bump"] as const;
export type BumpType = typeof bumpTypes[number];
export function parse(versionStr: string): SemVer {
const match = versionStr.match(
/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/
);
if (!match) {
throw new Error(`${versionStr} is not a valid semantic version`);
}
const semVer: Omit<SemVer, "parsedFrom"> = {
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: (() => {
const str = match[3];
return str === undefined ? 0 : parseInt(str);
})(),
...(() => {
const str = match[4];
return str === undefined ? {} : { rc: parseInt(str) };
})()
};
const initialStr = stringify(semVer);
Object.defineProperty(semVer, "parsedFrom", {
enumerable: true,
get: function () {
const currentStr = stringify(this);
if (currentStr !== initialStr) {
throw new Error(
`SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`
);
}
return versionStr;
}
});
return semVer as any;
}
export function stringify(v: Omit<SemVer, "parsedFrom">): string {
return `${v.major}.${v.minor}.${v.patch}${
v.rc === undefined ? "" : `-rc.${v.rc}`
}`;
}
/**
*
* v1 < v2 => -1
* v1 === v2 => 0
* v1 > v2 => 1
*
*/
export function compare(v1: SemVer, v2: SemVer): -1 | 0 | 1 {
const sign = (diff: number): -1 | 0 | 1 =>
diff === 0 ? 0 : diff < 0 ? -1 : 1;
const noUndefined = (n: number | undefined) => n ?? Infinity;
for (const level of ["major", "minor", "patch", "rc"] as const) {
if (noUndefined(v1[level]) !== noUndefined(v2[level])) {
return sign(noUndefined(v1[level]) - noUndefined(v2[level]));
}
}
return 0;
}
/*
console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0")) === -1 )
console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0-rc.4")) === -1 )
console.log(compare(parse("3.0.0-rc.3"), parse("4.0.0")) === -1 )
*/
export function bumpType(params: {
versionBehind: string | SemVer;
versionAhead: string | SemVer;
}): BumpType | "no bump" {
const versionAhead =
typeof params.versionAhead === "string"
? parse(params.versionAhead)
: params.versionAhead;
const versionBehind =
typeof params.versionBehind === "string"
? parse(params.versionBehind)
: params.versionBehind;
if (compare(versionBehind, versionAhead) === 1) {
throw new Error(
`Version regression ${stringify(versionBehind)} -> ${stringify(
versionAhead
)}`
);
}
for (const level of ["major", "minor", "patch", "rc"] as const) {
if (versionBehind[level] !== versionAhead[level]) {
return level;
}
}
return "no bump";
}
}