Add version range computation utility for MC versions
This commit is contained in:
parent
f7e468f28f
commit
e321ecfd70
127
src/lib/utils/mcVersion.test.ts
Normal file
127
src/lib/utils/mcVersion.test.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { computeVersionRanges } from "./mcVersion";
|
||||
|
||||
const ALL_AVAILABLE = [
|
||||
"1.19",
|
||||
"1.19.1",
|
||||
"1.19.2",
|
||||
"1.20",
|
||||
"1.20.1",
|
||||
"1.20.2",
|
||||
"1.20.3",
|
||||
"1.20.4",
|
||||
"1.21",
|
||||
"1.21.1",
|
||||
"1.21.2",
|
||||
"1.21.3",
|
||||
"1.21.4",
|
||||
"26.1",
|
||||
"26.2",
|
||||
"26.3",
|
||||
"27.0",
|
||||
"27.1",
|
||||
];
|
||||
|
||||
describe("computeVersionRanges", () => {
|
||||
test("非最后版本:相邻范围", () => {
|
||||
const result = computeVersionRanges(
|
||||
["1.20", "1.20.3", "1.21"],
|
||||
ALL_AVAILABLE,
|
||||
);
|
||||
// [1.20, 1.20.3) → 1.20, 1.20.1, 1.20.2,不含 1.20.3/1.20.4,未覆盖完整 1.20.x
|
||||
expect(result[0]).toMatchObject({
|
||||
version: "1.20",
|
||||
range: ["1.20", "1.20.1", "1.20.2"],
|
||||
wildcard: "1.20-1.20.2",
|
||||
explicit: "1.20-1.20.2",
|
||||
});
|
||||
// [1.20.3, 1.21) → 1.20.3, 1.20.4
|
||||
expect(result[1]).toMatchObject({
|
||||
version: "1.20.3",
|
||||
range: ["1.20.3", "1.20.4"],
|
||||
wildcard: "1.20.3-1.20.4",
|
||||
explicit: "1.20.3-1.20.4",
|
||||
});
|
||||
});
|
||||
|
||||
test("非最后版本:紧邻无间隙", () => {
|
||||
const result = computeVersionRanges(
|
||||
["1.20.3", "1.20.4"],
|
||||
ALL_AVAILABLE,
|
||||
);
|
||||
expect(result[0].range).toEqual(["1.20.3"]);
|
||||
expect(result[0].explicit).toBe("1.20.3-1.20.3");
|
||||
});
|
||||
|
||||
test("最后版本:自动泛匹配 1.x", () => {
|
||||
const result = computeVersionRanges(
|
||||
["1.20.3", "1.21"],
|
||||
ALL_AVAILABLE,
|
||||
);
|
||||
const last = result[1];
|
||||
expect(last.version).toBe("1.21");
|
||||
expect(last.range).toEqual(["1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4"]);
|
||||
expect(last.wildcard).toBe("1.21.x");
|
||||
expect(last.explicit).toBe("1.21-1.21.4");
|
||||
});
|
||||
|
||||
test("最后版本:自动泛匹配 >=26", () => {
|
||||
const result = computeVersionRanges(
|
||||
["26.1", "27.0"],
|
||||
ALL_AVAILABLE,
|
||||
);
|
||||
// [26.1, 27.0) 包含 26.1~26.3 — 完整 26.x 系列
|
||||
expect(result[0]).toMatchObject({
|
||||
version: "26.1",
|
||||
range: ["26.1", "26.2", "26.3"],
|
||||
wildcard: "26.x",
|
||||
explicit: "26.1-26.3",
|
||||
});
|
||||
const last = result[1];
|
||||
expect(last.version).toBe("27.0");
|
||||
expect(last.range).toEqual(["27.0", "27.1"]);
|
||||
expect(last.wildcard).toBe("27.x");
|
||||
expect(last.explicit).toBe("27.0-27.1");
|
||||
});
|
||||
|
||||
test("最后版本:手动指定截止版本", () => {
|
||||
const result = computeVersionRanges(
|
||||
["1.20", "1.20.3", "1.21"],
|
||||
ALL_AVAILABLE,
|
||||
"1.21.3",
|
||||
);
|
||||
const last = result[2];
|
||||
// [1.21, 1.21.3) → 1.21, 1.21.1, 1.21.2
|
||||
expect(last.range).toEqual(["1.21", "1.21.1", "1.21.2"]);
|
||||
expect(last.wildcard).toBe("1.21-1.21.2");
|
||||
expect(last.explicit).toBe("1.21-1.21.2");
|
||||
});
|
||||
|
||||
test("单个版本:自动泛匹配", () => {
|
||||
const result = computeVersionRanges(
|
||||
["1.21"],
|
||||
ALL_AVAILABLE,
|
||||
);
|
||||
expect(result[0]).toMatchObject({
|
||||
version: "1.21",
|
||||
range: ["1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4"],
|
||||
wildcard: "1.21.x",
|
||||
explicit: "1.21-1.21.4",
|
||||
});
|
||||
});
|
||||
|
||||
test("最后版本手动选完整系列:显示通配符", () => {
|
||||
const result = computeVersionRanges(
|
||||
["26.1"],
|
||||
ALL_AVAILABLE,
|
||||
"27.0",
|
||||
);
|
||||
// [26.1, 27.0) 包含 26.1~26.3 — 完整 26.x 系列
|
||||
expect(result[0]).toMatchObject({
|
||||
version: "26.1",
|
||||
range: ["26.1", "26.2", "26.3"],
|
||||
wildcard: "26.x",
|
||||
explicit: "26.1-26.3",
|
||||
});
|
||||
});
|
||||
});
|
||||
118
src/lib/utils/mcVersion.ts
Normal file
118
src/lib/utils/mcVersion.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { SemVer } from "./templates/semver";
|
||||
|
||||
/** 单个 MC 版本及其兼容范围信息 */
|
||||
export interface McVersionEntry {
|
||||
/** MC 版本号,如 "1.20" */
|
||||
version: string;
|
||||
/** 区间内所有版本,如 ["1.20", "1.20.1", "1.20.2"] */
|
||||
range: string[];
|
||||
/** 通配符显示,如 "1.20.x";无法通配时回退为显式范围 */
|
||||
wildcard: string;
|
||||
/** 显式范围显示,如 "1.20-1.20.2"、"1.20.1-1.20.3" */
|
||||
explicit: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为已排序的项目版本列表计算兼容范围。
|
||||
*
|
||||
* @param projectVersions 项目 MC 版本列表(已排序,从小到大)
|
||||
* @param allAvailable 全平台可用 MC 版本(已排序,从小到大)
|
||||
* @param lastVersionEnd 最后一个版本的截止版本,不传则自动泛匹配
|
||||
*/
|
||||
export function computeVersionRanges(
|
||||
projectVersions: string[],
|
||||
allAvailable: string[],
|
||||
lastVersionEnd?: string,
|
||||
): McVersionEntry[] {
|
||||
return projectVersions.map((version, i) => {
|
||||
const isLast = i === projectVersions.length - 1;
|
||||
const end = isLast ? (lastVersionEnd ?? null) : projectVersions[i + 1];
|
||||
return computeEntry(version, end, allAvailable);
|
||||
});
|
||||
}
|
||||
|
||||
// ── 内部实现 ──────────────────────────────────────────
|
||||
|
||||
function computeEntry(
|
||||
start: string,
|
||||
end: string | null,
|
||||
allAvailable: string[],
|
||||
): McVersionEntry {
|
||||
const range = end !== null
|
||||
? collectRange(start, end, allAvailable)
|
||||
: collectAutoRange(start, allAvailable);
|
||||
|
||||
const lastInRange = range[range.length - 1] ?? start;
|
||||
const explicit = `${start}-${lastInRange}`;
|
||||
const wildcard = computeWildcard(start, range, allAvailable, explicit);
|
||||
|
||||
return { version: start, range, wildcard, explicit };
|
||||
}
|
||||
|
||||
/** 收集 [start, end) 区间内的所有版本 */
|
||||
function collectRange(
|
||||
start: string,
|
||||
end: string,
|
||||
allAvailable: string[],
|
||||
): string[] {
|
||||
const result = allAvailable.filter(
|
||||
(v) =>
|
||||
SemVer.compare(v, start) >= 0 &&
|
||||
SemVer.compare(v, end) < 0,
|
||||
);
|
||||
if (!result.some((v) => SemVer.compare(v, start) === 0)) {
|
||||
result.unshift(start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 自动匹配:收集与 start 同系列的所有版本 */
|
||||
function collectAutoRange(
|
||||
start: string,
|
||||
allAvailable: string[],
|
||||
): string[] {
|
||||
const startSv = SemVer.parse(start);
|
||||
if (startSv.major < 26) {
|
||||
// 1.x:匹配相同 major.minor
|
||||
return allAvailable.filter((v) => {
|
||||
const sv = SemVer.parse(v);
|
||||
return sv.major === startSv.major && sv.minor === startSv.minor;
|
||||
});
|
||||
} else {
|
||||
// >= 26:匹配相同 major
|
||||
return allAvailable.filter((v) => {
|
||||
const sv = SemVer.parse(v);
|
||||
return sv.major === startSv.major;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 确定通配符显示:覆盖完整系列时用 .x,否则回退为 explicit */
|
||||
function computeWildcard(
|
||||
start: string,
|
||||
range: string[],
|
||||
allAvailable: string[],
|
||||
explicit: string,
|
||||
): string {
|
||||
const startSv = SemVer.parse(start);
|
||||
const completeSeries = startSv.major < 26
|
||||
? allAvailable.filter((v) => {
|
||||
const sv = SemVer.parse(v);
|
||||
return sv.major === startSv.major && sv.minor === startSv.minor;
|
||||
})
|
||||
: allAvailable.filter((v) => {
|
||||
const sv = SemVer.parse(v);
|
||||
return sv.major === startSv.major;
|
||||
});
|
||||
|
||||
const rangeSet = new Set(range);
|
||||
if (completeSeries.every((v) => rangeSet.has(v))) {
|
||||
if (startSv.major < 26) {
|
||||
return `${startSv.major}.${startSv.minor}.x`;
|
||||
} else {
|
||||
return `${startSv.major}.x`;
|
||||
}
|
||||
}
|
||||
|
||||
return explicit;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user