2024-02-07 20:01:26 +01:00
|
|
|
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 {
|
2024-05-20 15:48:51 +02:00
|
|
|
const match = versionStr.match(
|
|
|
|
/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/
|
|
|
|
);
|
2024-02-07 20:01:26 +01:00
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
throw new Error(`${versionStr} is not a valid semantic version`);
|
|
|
|
}
|
|
|
|
|
|
|
|
const semVer: Omit<SemVer, "parsedFrom"> = {
|
2024-05-20 15:48:51 +02:00
|
|
|
major: parseInt(match[1]),
|
|
|
|
minor: parseInt(match[2]),
|
|
|
|
patch: (() => {
|
2024-02-07 20:01:26 +01:00
|
|
|
const str = match[3];
|
|
|
|
|
|
|
|
return str === undefined ? 0 : parseInt(str);
|
|
|
|
})(),
|
|
|
|
...(() => {
|
|
|
|
const str = match[4];
|
2024-05-20 15:48:51 +02:00
|
|
|
return str === undefined ? {} : { rc: parseInt(str) };
|
2024-02-07 20:01:26 +01:00
|
|
|
})()
|
|
|
|
};
|
|
|
|
|
|
|
|
const initialStr = stringify(semVer);
|
|
|
|
|
|
|
|
Object.defineProperty(semVer, "parsedFrom", {
|
2024-05-20 15:48:51 +02:00
|
|
|
enumerable: true,
|
|
|
|
get: function () {
|
2024-02-07 20:01:26 +01:00
|
|
|
const currentStr = stringify(this);
|
|
|
|
|
|
|
|
if (currentStr !== initialStr) {
|
2024-05-20 15:48:51 +02:00
|
|
|
throw new Error(
|
|
|
|
`SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`
|
|
|
|
);
|
2024-02-07 20:01:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return versionStr;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return semVer as any;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function stringify(v: Omit<SemVer, "parsedFrom">): string {
|
2024-05-20 15:48:51 +02:00
|
|
|
return `${v.major}.${v.minor}.${v.patch}${
|
|
|
|
v.rc === undefined ? "" : `-rc.${v.rc}`
|
|
|
|
}`;
|
2024-02-07 20:01:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* 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 )
|
|
|
|
*/
|
|
|
|
|
2024-05-20 15:48:51 +02:00
|
|
|
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;
|
2024-02-07 20:01:26 +01:00
|
|
|
|
|
|
|
if (compare(versionBehind, versionAhead) === 1) {
|
2024-05-20 15:48:51 +02:00
|
|
|
throw new Error(
|
|
|
|
`Version regression ${stringify(versionBehind)} -> ${stringify(
|
|
|
|
versionAhead
|
|
|
|
)}`
|
|
|
|
);
|
2024-02-07 20:01:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const level of ["major", "minor", "patch", "rc"] as const) {
|
|
|
|
if (versionBehind[level] !== versionAhead[level]) {
|
|
|
|
return level;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "no bump";
|
|
|
|
}
|
|
|
|
}
|