119 lines
3.5 KiB
TypeScript
Raw Normal View History

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]+))?$/
);
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: (() => {
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) };
})()
};
const initialStr = stringify(semVer);
Object.defineProperty(semVer, "parsedFrom", {
2024-05-20 15:48:51 +02:00
enumerable: true,
get: function () {
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}`
);
}
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}`
}`;
}
/**
*
* 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;
if (compare(versionBehind, versionAhead) === 1) {
2024-05-20 15:48:51 +02:00
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";
}
}