Compute per-platform version ranges using each platform's available MC version list and display them as tags in the publish modal.
127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
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[] {
|
||
const sortedProjectVersions = [...projectVersions].sort((a, b) => SemVer.compare(a, b));
|
||
const sortedAvailable = [...allAvailable].sort((a, b) => SemVer.compare(a, b));
|
||
return sortedProjectVersions.map((version, i) => {
|
||
const isLast = i === sortedProjectVersions.length - 1;
|
||
const end = isLast ? (lastVersionEnd ?? null) : sortedProjectVersions[i + 1];
|
||
const inclusiveEnd = isLast && lastVersionEnd != null;
|
||
return computeEntry(version, end, sortedAvailable, inclusiveEnd);
|
||
});
|
||
}
|
||
|
||
// ── 内部实现 ──────────────────────────────────────────
|
||
|
||
function computeEntry(
|
||
start: string,
|
||
end: string | null,
|
||
allAvailable: string[],
|
||
inclusiveEnd?: boolean,
|
||
): McVersionEntry {
|
||
const range = end !== null
|
||
? collectRange(start, end, allAvailable, inclusiveEnd)
|
||
: collectAutoRange(start, allAvailable);
|
||
|
||
const lastInRange = range[range.length - 1] ?? start;
|
||
const explicit = start === lastInRange ? start : `${start}-${lastInRange}`;
|
||
const wildcard = computeWildcard(start, range, allAvailable, explicit);
|
||
|
||
return { version: start, range, wildcard, explicit };
|
||
}
|
||
|
||
/** 收集区间内的所有版本。inclusiveEnd 为 true 时使用 [start, end],否则 [start, end) */
|
||
function collectRange(
|
||
start: string,
|
||
end: string,
|
||
allAvailable: string[],
|
||
inclusiveEnd?: boolean,
|
||
): string[] {
|
||
const op = inclusiveEnd
|
||
? (v: string) => SemVer.compare(v, end) <= 0
|
||
: (v: string) => SemVer.compare(v, end) < 0;
|
||
const result = allAvailable.filter(
|
||
(v) =>
|
||
SemVer.compare(v, start) >= 0 &&
|
||
op(v),
|
||
);
|
||
if (!result.some((v) => SemVer.compare(v, start) === 0)) {
|
||
result.unshift(start);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/** 自动匹配:收集与 start 同系列的所有版本 */
|
||
export 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;
|
||
}
|