Files
koring-space/app/api/download-version/route.ts
T
dream_pep dcae468055 feat: 下载中心对接 GitHub Releases 并适配 latest.yml
- 新增 /download 下载页:正式版/预览版双渠道、latest.yml/latest-beta.yml 数据源、镜像下载源切换、SHA512 校验展示
- 新增 /api/download-version:GitHub API 自动识别 + 镜像兜底 + yml 解析 + 旧源兜底
- /launcher/join-beta 改为自动识别 GitHub 预览版,支持镜像下载源切换
- 新增 markdown-lite 轻量渲染器、lib/download-sources 与 lib/github-releases 共享模块
- 头部导航新增下载中心入口
2026-08-31 12:01:41 +08:00

145 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import {
fetchReleases,
pickBetaRelease,
pickStableRelease,
pickAsset,
buildDownloadUrl,
fetchYmlForRelease,
RELEASES_PAGE,
} from "@/lib/github-releases";
/**
* 下载页版本信息接口
* 同时返回 正式版(latest.yml)与 预览版(latest-beta.yml / latest.yml)两个渠道,
* 版本 / 安装包文件名 / 大小 / SHA512 / 发布时间 均取自 electron-builder 发布的 yml
* 与客户端自动更新机制(electron-updater)保持一致。
*/
const CACHE_TTL = 15 * 60 * 1000;
const cache = new Map<string, { time: number; value: unknown }>();
const LEGACY_URL =
"https://koring-launcher-file-api.lenjing.cloud/launcher/beta/version.json";
async function buildChannelAsync(
release: any,
ymlNames: string[],
fallbackExePatterns: RegExp[]
) {
if (!release) return null;
const tag = String(release.tag_name || "");
const version = tag.replace(/^v/i, "");
// 主源:latest.yml / latest-beta.ymlelectron-updater 权威数据)
const yml = await fetchYmlForRelease(release, ymlNames);
const ymlFile = yml?.files?.[0];
const fileName = ymlFile?.url || yml?.path || "";
const windowsFile = fileName
? { name: fileName }
: pickAsset(release, fallbackExePatterns);
const mac = pickAsset(release, [/\.dmg$/i, /\.zip$/i, /\.pkg$/i]);
const lin = pickAsset(release, [
/\.appimage$/i,
/\.deb$/i,
/\.rpm$/i,
/\.tar\.gz$/i,
]);
return {
version,
tag,
releaseDate: yml?.releaseDate || release.published_at || release.created_at || "",
releaseNotes: release.body || "",
htmlUrl: release.html_url || `${RELEASES_PAGE}/tag/${tag}`,
windows: windowsFile
? {
name: windowsFile.name,
url: buildDownloadUrl(tag, windowsFile.name),
size: ymlFile?.size ?? undefined,
sha512: yml?.sha512 || ymlFile?.sha512 || "",
}
: null,
macos: mac
? { name: mac.name, url: buildDownloadUrl(tag, mac.name) }
: null,
linux: lin
? { name: lin.name, url: buildDownloadUrl(tag, lin.name) }
: null,
};
}
function buildLegacyPayload(legacy: any) {
const win = legacy?.app?.windows;
const preview: any = {
version: String(legacy?.version || ""),
tag: "",
releaseDate: "",
releaseNotes: legacy?.aboutversion?.about || "",
htmlUrl: RELEASES_PAGE,
windows: win?.DownlodeURL
? {
name: String(win.DownlodeURL).split("/").pop() || "",
url: win.DownlodeURL,
size: undefined,
sha512: "",
}
: null,
macos: null,
linux: null,
};
return { source: "legacy", stable: null, preview };
}
export async function GET() {
const now = Date.now();
const cached = cache.get("download");
if (cached && now - cached.time < CACHE_TTL) {
return NextResponse.json(cached.value);
}
let payload: unknown;
const releases = await fetchReleases();
if (Array.isArray(releases) && releases.length > 0) {
const stableRelease = pickStableRelease(releases);
const betaRelease = pickBetaRelease(releases);
const [stable, preview] = await Promise.all([
buildChannelAsync(stableRelease, ["latest.yml"], [/setup\.exe$/i, /\.exe$/i]),
buildChannelAsync(
betaRelease,
["latest-beta.yml", "latest.yml"],
[/setup\.exe$/i, /\.exe$/i]
),
]);
payload = { source: "github", stable, preview };
} else {
// GitHub 不可达 -> 旧静态源兜底(仅预览渠道)
let legacy: any = null;
try {
const res = await fetch(LEGACY_URL, {
cache: "no-store",
signal: AbortSignal.timeout(15_000),
});
if (res.ok) legacy = await res.json();
} catch {
legacy = null;
}
if (legacy) {
payload = buildLegacyPayload(legacy);
} else {
return NextResponse.json(
{ error: "无法获取版本信息,请稍后重试" },
{ status: 502 }
);
}
}
cache.set("download", { time: now, value: payload });
return NextResponse.json(payload);
}