mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-22 00:11:02 -05:00
Version
refactoring
This commit is contained in:
parent
17bc59ff61
commit
4bbd4cc2d9
2 changed files with 236 additions and 21 deletions
|
@ -1,31 +1,165 @@
|
|||
export default class Version {
|
||||
public readonly major: number;
|
||||
public readonly minor: number;
|
||||
public readonly build: number;
|
||||
import { SemVer, coerce, parse as parseSemVer } from "semver";
|
||||
|
||||
public constructor(major: number, minor: number, build: number);
|
||||
/**
|
||||
* Represents a version number, which is a set of three non-negative integers: major, minor, and patch.
|
||||
*
|
||||
* This interface provides methods to compare versions and format them into a string representation.
|
||||
*/
|
||||
export interface Version {
|
||||
/**
|
||||
* The major version number.
|
||||
*/
|
||||
get major(): number;
|
||||
|
||||
public constructor(version: string);
|
||||
/**
|
||||
* The minor version number.
|
||||
*/
|
||||
get minor(): number;
|
||||
|
||||
public constructor(major: number | string, minor?: number, build?: number) {
|
||||
if (typeof major === "string") {
|
||||
[this.major, this.minor, this.build] = major.split(".").map(x => isNaN(+x) ? 0 : +x).concat(0, 0);
|
||||
} else {
|
||||
this.major = major || 0;
|
||||
this.minor = minor || 0;
|
||||
this.build = build || 0;
|
||||
}
|
||||
/**
|
||||
* The patch version number.
|
||||
*/
|
||||
get patch(): number;
|
||||
|
||||
/**
|
||||
* Compares the current version to another one.
|
||||
*
|
||||
* @param other - The version to compare with.
|
||||
*
|
||||
* @returns A number indicating the comparison result:
|
||||
*
|
||||
* - 0 if both versions are equal.
|
||||
* - A positive number if the current version is greater.
|
||||
* - A negative number if the other version is greater.
|
||||
*/
|
||||
compare(other?: string | Version): number;
|
||||
|
||||
/**
|
||||
* Formats the version into a string representation.
|
||||
*
|
||||
* @returns The string representation of the version.
|
||||
*/
|
||||
format(): string;
|
||||
|
||||
/**
|
||||
* Returns the original string representation of the version.
|
||||
*
|
||||
* @returns The original string representation of the version.
|
||||
*/
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a version string into a {@link Version} instance.
|
||||
*
|
||||
* @param version - The version string to parse.
|
||||
*
|
||||
* @returns A {@link Version} instance if parsing is successful, or `undefined` if it fails.
|
||||
*/
|
||||
export function parseVersion(version: string): Version | undefined {
|
||||
return SemVerVersion.parse(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular expression for matching semver-like tags in version strings.
|
||||
*/
|
||||
const SEMVER_TAG_REGEX = /[a-z]{0,2}((\d+\.\d+)(\.\d+)?(.*))/i;
|
||||
|
||||
/**
|
||||
* Represents a version number compliant with the Semantic Versioning specification.
|
||||
*/
|
||||
class SemVerVersion implements Version {
|
||||
/**
|
||||
* The SemVer object representing the parsed semantic version.
|
||||
*/
|
||||
private readonly _semver: SemVer;
|
||||
|
||||
/**
|
||||
* The original string representation of the version.
|
||||
*/
|
||||
private readonly _version: string;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SemVerVersion} instance.
|
||||
*
|
||||
* @param semver - The SemVer object representing the parsed semantic version.
|
||||
* @param version - The original string representation of the version.
|
||||
*/
|
||||
constructor(semver: SemVer, version?: string) {
|
||||
this._semver = semver;
|
||||
this._version = version ?? semver.format();
|
||||
}
|
||||
|
||||
public equals(version: unknown): boolean {
|
||||
if (version instanceof Version) {
|
||||
return this.major === version.major && this.minor === version.minor && this.build === version.build;
|
||||
/**
|
||||
* Parses a version string into a {@link SemVerVersion} instance.
|
||||
*
|
||||
* @param version - The version string to parse.
|
||||
*
|
||||
* @returns A {@link SemVerVersion} instance if parsing is successful, or `undefined` if it fails.
|
||||
*/
|
||||
static parse(version: string): SemVerVersion | undefined {
|
||||
const semver = parseSemVer(version);
|
||||
if (semver) {
|
||||
return new SemVerVersion(semver, version);
|
||||
}
|
||||
return typeof version === "string" && this.equals(new Version(version));
|
||||
|
||||
const match = version.match(SEMVER_TAG_REGEX);
|
||||
if (match) {
|
||||
const numericVersion = match[3] ? match[1] : `${match[2]}.0${match[4]}`;
|
||||
const parsedSemVer = parseSemVer(numericVersion) || coerce(numericVersion);
|
||||
return new SemVerVersion(parsedSemVer, match[0]);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static fromName(name: string): string {
|
||||
const match = name.match(/[a-z]{0,2}\d+\.\d+.*/i);
|
||||
return match ? match[0] : name;
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
get major(): number {
|
||||
return this._semver.major;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
get minor(): number {
|
||||
return this._semver.minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
get patch(): number {
|
||||
return this._semver.patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
compare(other?: string | Version): number {
|
||||
if (other === null || other === undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (typeof other === "string") {
|
||||
other = SemVerVersion.parse(other);
|
||||
}
|
||||
|
||||
return other instanceof SemVerVersion ? this._semver.compare(other._semver) : -other.compare(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
format(): string {
|
||||
return this._semver.format();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
toString(): string {
|
||||
return this._version;
|
||||
}
|
||||
}
|
||||
|
|
81
tests/unit/utils/versioning/version.spec.ts
Normal file
81
tests/unit/utils/versioning/version.spec.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { parseVersion } from "@/utils/versioning/version";
|
||||
|
||||
describe("parseVersion", () => {
|
||||
test("returns undefined when parsing invalid string", () => {
|
||||
const version = parseVersion("abc");
|
||||
|
||||
expect(version).toBeUndefined();
|
||||
});
|
||||
|
||||
test("parses classic semver format (major.minor.patch)", () => {
|
||||
const version = parseVersion("1.2.3");
|
||||
|
||||
expect(version).toMatchObject({ major: 1, minor: 2, patch: 3 });
|
||||
});
|
||||
|
||||
test("parses classic semver format with pre-release information", () => {
|
||||
const version = parseVersion("1.2.3-alpha.1");
|
||||
|
||||
expect(version).toMatchObject({ major: 1, minor: 2, patch: 3 });
|
||||
expect(version.toString()).toBe("1.2.3-alpha.1");
|
||||
});
|
||||
|
||||
test("parses version strings with missing patch number (major.minor)", () => {
|
||||
const version = parseVersion("1.2");
|
||||
|
||||
expect(version).toMatchObject({ major: 1, minor: 2, patch: 0 });
|
||||
expect(version.format()).toBe("1.2.0");
|
||||
expect(version.toString()).toBe("1.2");
|
||||
});
|
||||
|
||||
test("parses version strings with missing patch number and pre-release information", () => {
|
||||
const version = parseVersion("1.2-alpha.1");
|
||||
|
||||
expect(version).toMatchObject({ major: 1, minor: 2, patch: 0 });
|
||||
expect(version.format()).toBe("1.2.0-alpha.1");
|
||||
expect(version.toString()).toBe("1.2-alpha.1");
|
||||
});
|
||||
|
||||
test("file version is correctly extracted from the filename", () => {
|
||||
expect(String(parseVersion("sodium-fabric-mc1.17.1-0.3.2+build.7"))).toBe("mc1.17.1-0.3.2+build.7");
|
||||
expect(String(parseVersion("fabric-api-0.40.1+1.18_experimental"))).toBe("0.40.1+1.18_experimental");
|
||||
expect(String(parseVersion("TechReborn-5.0.8-beta+build.111"))).toBe("5.0.8-beta+build.111");
|
||||
expect(String(parseVersion("TechReborn-1.17-5.0.1-beta+build.29"))).toBe("1.17-5.0.1-beta+build.29");
|
||||
expect(String(parseVersion("Terra-forge-5.3.3-BETA+ec3b0e5d"))).toBe("5.3.3-BETA+ec3b0e5d");
|
||||
expect(String(parseVersion("modmenu-2.0.12"))).toBe("2.0.12");
|
||||
expect(String(parseVersion("enhancedblockentities-0.5+1.17"))).toBe("0.5+1.17");
|
||||
expect(String(parseVersion("sync-mc1.17.x-1.2"))).toBe("mc1.17.x-1.2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Version", () => {
|
||||
test("contains valid major, minor, and patch numbers", () => {
|
||||
expect(parseVersion("1.2.3")).toMatchObject({ major: 1, minor: 2, patch: 3 });
|
||||
expect(parseVersion("1.2.3-alpha.1")).toMatchObject({ major: 1, minor: 2, patch: 3 });
|
||||
expect(parseVersion("1.2")).toMatchObject({ major: 1, minor: 2, patch: 0 });
|
||||
expect(parseVersion("1.2-alpha.1")).toMatchObject({ major: 1, minor: 2, patch: 0 });
|
||||
});
|
||||
|
||||
test("compares versions correctly", () => {
|
||||
const version1 = parseVersion("1.2.3");
|
||||
const version2 = parseVersion("2.3.4");
|
||||
|
||||
expect(version1.compare(version2)).toBeLessThan(0);
|
||||
expect(version2.compare(version1)).toBeGreaterThan(0);
|
||||
expect(version1.compare(version1)).toBe(0);
|
||||
});
|
||||
|
||||
test("formats correctly", () => {
|
||||
expect(parseVersion("1.0.0").format()).toEqual("1.0.0");
|
||||
expect(parseVersion("1.0.0-alpha.1").format()).toEqual("1.0.0-alpha.1");
|
||||
expect(parseVersion("1.0").format()).toEqual("1.0.0");
|
||||
expect(parseVersion("1.0-alpha.1").format()).toEqual("1.0.0-alpha.1");
|
||||
});
|
||||
|
||||
test("toString returns the original string representation", () => {
|
||||
expect(parseVersion("1.0.0").toString()).toEqual("1.0.0");
|
||||
expect(parseVersion("1.0").toString()).toEqual("1.0");
|
||||
expect(parseVersion("1.0.0-alpha.1").toString()).toEqual("1.0.0-alpha.1");
|
||||
expect(parseVersion("1.0-alpha.1").toString()).toEqual("1.0-alpha.1");
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue