添加稳定版开发版渠道和构建资源管理器

This commit is contained in:
Coldsmile_7
2026-07-20 02:09:11 +08:00
parent 970c8b2c3a
commit 0a58f1c226
8 changed files with 439 additions and 87 deletions
+184
View File
@@ -0,0 +1,184 @@
<script lang="ts">
import { onMount } from "svelte";
import {
artifactForRun,
fetchBuildCatalog,
formatDate,
formatSize,
preferredAsset,
releaseSummary,
releaseTitle,
type BuildCatalog,
} from "@/utils/builds";
type Channel = "stable" | "development";
let catalog: BuildCatalog = { releases: [], runs: [], artifacts: [] };
let channel: Channel = "stable";
let loading = true;
let error = "";
onMount(async () => {
if (new URLSearchParams(window.location.search).get("channel") === "development") channel = "development";
try {
catalog = await fetchBuildCatalog();
} catch (reason) {
error = reason instanceof Error ? reason.message : "无法读取构建数据";
} finally {
loading = false;
}
});
$: stableReleases = catalog.releases.filter((release) => !release.prerelease);
$: developmentReleases = catalog.releases.filter((release) => release.prerelease);
function setChannel(value: Channel) {
channel = value;
const url = new URL(window.location.href);
if (value === "development") url.searchParams.set("channel", "development");
else url.searchParams.delete("channel");
window.history.replaceState({}, "", url);
}
</script>
<header class="mx-auto max-w-7xl px-4 pt-32 pb-12 lg:pt-28">
<div class="mb-6 flex flex-row items-center gap-4">
<div class="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-800 p-1">
<img src="/uranium-logo.png" alt="Uranium logo" class="h-full w-full rounded-md object-cover" />
</div>
<h1 class="text-xl font-medium">UraniumMC</h1>
</div>
<h2 class="text-4xl leading-normal font-medium lg:text-5xl lg:leading-normal">构建资源管理器</h2>
<p class="mt-4 max-w-4xl text-xl">查看 Uranium 稳定发行版、开发构建、关联提交和可用的 GitHub Actions Artifact。</p>
</header>
<section class="mx-auto max-w-7xl px-4 pb-20">
<div class="mb-8 grid w-full grid-cols-2 rounded-md border p-1 md:w-100">
<button
class={`rounded-sm px-4 py-2 font-medium transition-colors ${channel === "stable" ? "bg-green-500 text-green-950" : "hover:bg-gray-200 dark:hover:bg-gray-800"}`}
onclick={() => setChannel("stable")}>稳定版</button
>
<button
class={`rounded-sm px-4 py-2 font-medium transition-colors ${channel === "development" ? "bg-channel-alpha-primary text-white" : "hover:bg-gray-200 dark:hover:bg-gray-800"}`}
onclick={() => setChannel("development")}>开发版</button
>
</div>
{#if error}
<div class="rounded-md border border-red-500/40 p-6 text-red-500">{error}</div>
{:else if loading}
<div class="grid gap-4">
{#each Array(6) as _}<div class="h-28 animate-pulse rounded-md bg-gray-200/60 dark:bg-gray-800"></div>{/each}
</div>
{:else if channel === "stable"}
<div class="mb-6 flex items-end justify-between gap-4">
<div>
<h3 class="text-2xl font-medium">稳定发行版</h3>
<p class="mt-1 text-gray-600 dark:text-gray-300">由维护者发布到 GitHub Releases 的生产构建。</p>
</div>
<span class="rounded-full bg-green-950 px-3 py-1 text-sm text-green-300">{stableReleases.length} 个构建</span>
</div>
{#if stableReleases.length}
<div class="grid gap-4">
{#each stableReleases as release}
{@const asset = preferredAsset(release)}
<article class="rounded-md border bg-white p-5 dark:bg-gray-900">
<div class="flex flex-col justify-between gap-4 md:flex-row md:items-start">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-3">
<span class="btn btn-stable rounded-sm px-3 py-1 text-sm font-medium">{release.tag_name}</span>
<h4 class="text-lg font-medium">{releaseTitle(release)}</h4>
</div>
<p class="mt-3 text-gray-700 dark:text-gray-300">{releaseSummary(release)}</p>
<div class="mt-3 flex flex-wrap gap-x-6 gap-y-1 text-sm text-gray-500 dark:text-gray-400">
<span>{formatDate(release.published_at)}</span><span
>{asset ? `${asset.name} · ${formatSize(asset.size)}` : "未附带下载文件"}</span
>
</div>
</div>
<a
class="btn btn-stable inline-flex shrink-0 items-center justify-center rounded-sm px-5 py-2 font-medium"
href={asset?.browser_download_url ?? release.html_url}
target="_blank"
rel="noreferrer">{asset ? "下载" : "查看发行版"}</a
>
</div>
</article>
{/each}
</div>
{:else}
<div class="rounded-md border px-6 py-12 text-center text-gray-700 dark:text-gray-300">
当前还没有公开稳定版。发布第一个 GitHub Release 后会自动出现在这里。
</div>
{/if}
{:else}
<div class="mb-6 flex items-end justify-between gap-4">
<div>
<h3 class="text-2xl font-medium">开发构建</h3>
<p class="mt-1 text-gray-600 dark:text-gray-300">来自 <code>dev</code> 分支成功完成的持续集成构建。</p>
</div>
<span class="rounded-full bg-red-950 px-3 py-1 text-sm text-red-300">{catalog.runs.length + developmentReleases.length} 个构建</span>
</div>
{#if developmentReleases.length || catalog.runs.length}
<div class="grid gap-4">
{#each developmentReleases as release}
{@const asset = preferredAsset(release)}
<article class="rounded-md border bg-white p-5 dark:bg-gray-900">
<div class="flex flex-col justify-between gap-4 md:flex-row md:items-start">
<div>
<div class="flex flex-wrap items-center gap-3">
<span class="btn btn-alpha rounded-sm px-3 py-1 text-sm font-medium">预发布</span>
<h4 class="text-lg font-medium">{releaseTitle(release)}</h4>
</div>
<p class="mt-3 text-gray-700 dark:text-gray-300">{releaseSummary(release)}</p>
<div class="mt-3 text-sm text-gray-500 dark:text-gray-400">
{formatDate(release.published_at)} · {asset ? `${asset.name} · ${formatSize(asset.size)}` : "未附带下载文件"}
</div>
</div>
<a
class="btn btn-alpha inline-flex shrink-0 items-center justify-center rounded-sm px-5 py-2 font-medium"
href={asset?.browser_download_url ?? release.html_url}
target="_blank"
rel="noreferrer">{asset ? "下载" : "查看预发布版"}</a
>
</div>
</article>
{/each}
{#each catalog.runs as run}
{@const artifact = artifactForRun(catalog.artifacts, run.id)}
<article class="rounded-md border bg-white p-5 dark:bg-gray-900">
<div class="flex flex-col justify-between gap-4 md:flex-row md:items-start">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-3">
<span class="btn btn-alpha rounded-sm px-3 py-1 text-sm font-medium">Build #{run.run_number}</span>
<h4 class="min-w-0 truncate text-lg font-medium">{run.display_title}</h4>
</div>
<div class="mt-3 flex flex-wrap gap-x-6 gap-y-2 text-sm">
<a
class="font-mono text-red-500 hover:text-red-400"
href={`https://github.com/Coldsmiles/Uranium/commit/${run.head_sha}`}
target="_blank"
rel="noreferrer">{run.head_sha.slice(0, 7)}</a
>
<span class="text-gray-500 dark:text-gray-400">{formatDate(run.created_at)}</span>
<span class={artifact ? "text-green-600 dark:text-green-400" : "text-amber-600 dark:text-amber-400"}
>{artifact ? `${artifact.name} · ${formatSize(artifact.size_in_bytes)}` : "没有可下载 Artifact"}</span
>
</div>
</div>
<a
class="btn btn-alpha inline-flex shrink-0 items-center justify-center rounded-sm px-5 py-2 font-medium"
href={run.html_url}
target="_blank"
rel="noreferrer">{artifact ? "打开并下载" : "查看构建"}</a
>
</div>
</article>
{/each}
</div>
{:else}
<div class="rounded-md border px-6 py-12 text-center text-gray-700 dark:text-gray-300">当前还没有成功的开发构建。</div>
{/if}
{/if}
</section>
+1
View File
@@ -13,6 +13,7 @@ const hash = import.meta.env.GIT_COMMIT_HASH;
<ul class="mt-4 space-y-2 leading-5 text-gray-400"> <ul class="mt-4 space-y-2 leading-5 text-gray-400">
<li><a href="/downloads">下载</a></li> <li><a href="/downloads">下载</a></li>
<li><a href="/guide">快速开始</a></li> <li><a href="/guide">快速开始</a></li>
<li><a href="/downloads/uranium/builds">构建资源管理器</a></li>
</ul> </ul>
</div> </div>
<div> <div>
+1 -1
View File
@@ -9,7 +9,7 @@ const { size = 42, showName = true } = Astro.props;
<span class="brand" aria-label="Uranium"> <span class="brand" aria-label="Uranium">
<img src="/uranium-logo.png" width={size} height={size} alt="" class="brand-icon" aria-hidden="true" /> <img src="/uranium-logo.png" width={size} height={size} alt="" class="brand-icon" aria-hidden="true" />
{showName && <span class="brand-name">URANIUM</span>} {showName && <span class="brand-name">UraniumMC</span>}
</span> </span>
<style> <style>
+111 -86
View File
@@ -1,73 +1,65 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import {
artifactForRun,
fetchBuildCatalog,
formatDate,
formatSize,
preferredAsset,
releaseSummary,
releaseTitle,
type BuildCatalog,
type Release,
type WorkflowRun,
} from "@/utils/builds";
interface ReleaseAsset { type Channel = "stable" | "development";
id: number;
name: string;
browser_download_url: string;
size: number;
}
interface Release {
id: number;
tag_name: string;
name: string | null;
html_url: string;
published_at: string;
prerelease: boolean;
draft: boolean;
body: string | null;
assets: ReleaseAsset[];
}
const releasesUrl = "https://github.com/Coldsmiles/Uranium/releases"; const releasesUrl = "https://github.com/Coldsmiles/Uranium/releases";
const actionsUrl = "https://github.com/Coldsmiles/Uranium/actions"; const actionsUrl = "https://github.com/Coldsmiles/Uranium/actions";
let releases: Release[] = []; let catalog: BuildCatalog = { releases: [], runs: [], artifacts: [] };
let channel: Channel = "stable";
let loading = true; let loading = true;
let error = ""; let error = "";
onMount(async () => { onMount(async () => {
if (new URLSearchParams(window.location.search).get("channel") === "development") channel = "development";
try { try {
const response = await fetch("https://api.github.com/repos/Coldsmiles/Uranium/releases?per_page=10", { catalog = await fetchBuildCatalog();
headers: { Accept: "application/vnd.github+json" },
});
if (!response.ok) throw new Error(`GitHub API 返回 ${response.status}`);
releases = ((await response.json()) as Release[]).filter((release) => !release.draft);
} catch (reason) { } catch (reason) {
error = reason instanceof Error ? reason.message : "无法读取发行版"; error = reason instanceof Error ? reason.message : "无法读取构建数据";
} finally { } finally {
loading = false; loading = false;
} }
}); });
function preferredAsset(release?: Release) { $: stableReleases = catalog.releases.filter((release) => !release.prerelease);
if (!release) return undefined; $: developmentReleases = catalog.releases.filter((release) => release.prerelease);
return ( $: latestStable = stableReleases[0];
release.assets.find((asset) => /server.*\.jar$/i.test(asset.name)) ?? $: latestDevelopmentRelease = developmentReleases[0];
release.assets.find((asset) => /\.(jar|zip)$/i.test(asset.name)) ?? $: latestDevelopmentRun = catalog.runs[0];
release.assets[0] $: latestRelease = channel === "stable" ? latestStable : latestDevelopmentRelease;
); $: latestRun = channel === "development" && !latestDevelopmentRelease ? latestDevelopmentRun : undefined;
$: latestAsset = preferredAsset(latestRelease);
function toggleChannel() {
channel = channel === "stable" ? "development" : "stable";
const url = new URL(window.location.href);
if (channel === "development") url.searchParams.set("channel", "development");
else url.searchParams.delete("channel");
window.history.replaceState({}, "", url);
} }
function releaseTitle(release: Release) { function currentHref() {
return release.name || release.tag_name; if (latestAsset) return latestAsset.browser_download_url;
if (latestRelease) return latestRelease.html_url;
if (latestRun) return latestRun.html_url;
return channel === "stable" ? releasesUrl : actionsUrl;
} }
function releaseSummary(release: Release) { function runArtifact(run: WorkflowRun) {
const line = release.body?.split(/\r?\n/).find((value) => value.trim()); return artifactForRun(catalog.artifacts, run.id);
return line?.replace(/^#+\s*/, "") || `Uranium ${release.tag_name} 发行构建`;
} }
function formatDate(value: string) {
return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "long", day: "numeric" }).format(new Date(value));
}
function formatSize(bytes: number) {
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}
$: latest = releases[0];
$: latestAsset = preferredAsset(latest);
</script> </script>
<header class="mx-auto flex max-w-7xl flex-row flex-wrap gap-16 px-4 pt-32 pb-16 lg:pt-48 lg:pb-26"> <header class="mx-auto flex max-w-7xl flex-row flex-wrap gap-16 px-4 pt-32 pb-16 lg:pt-48 lg:pb-26">
@@ -80,20 +72,21 @@
</div> </div>
<h2 class="text-4xl leading-normal font-medium lg:text-5xl lg:leading-normal"> <h2 class="text-4xl leading-normal font-medium lg:text-5xl lg:leading-normal">
获取 Uranium <span class="text-green-500">1.7.10</span> 获取 Uranium <span class={channel === "stable" ? "text-green-500" : "text-channel-alpha-primary"}>1.7.10</span>
</h2> </h2>
<p class="mt-4 max-w-4xl text-xl">下载面向 Minecraft 1.7.10 的 Forge + Bukkit 混合服务端。运行 Uranium 需要 64 位 JDK 8。</p> <p class="mt-4 max-w-4xl text-xl">
{#if channel === "stable"}
下载经过维护者确认、适合生产服务器使用的 Uranium 稳定版。
{:else}
开发版包含最新维护改动,可能未经完整兼容性验证。使用前请备份服务器。
{/if}
</p>
<div class="mt-8 flex flex-col gap-4"> <div class="mt-8 flex flex-col gap-4">
<div class="relative flex w-full flex-row md:w-100"> <div class="relative flex w-full flex-row md:w-100">
<div class={`btn flex w-full flex-row rounded-md ${latestAsset ? "btn-stable" : "btn-recommended"}`}> <div class={`btn flex w-full flex-row rounded-md ${channel === "stable" ? "btn-stable" : "btn-alpha"}`}>
<a <a class="flex flex-1 flex-row items-center gap-8 py-3 pl-5" href={currentHref()} target="_blank" rel="noreferrer">
class="flex flex-1 flex-row items-center gap-8 py-3 pl-5"
href={latestAsset?.browser_download_url ?? actionsUrl}
target="_blank"
rel="noreferrer"
>
<svg class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true"> <svg class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path <path
stroke-linecap="round" stroke-linecap="round"
@@ -103,23 +96,28 @@
</svg> </svg>
<div class="flex-1 pr-2 text-left"> <div class="flex-1 pr-2 text-left">
{#if loading} {#if loading}
<span class="text-lg font-medium">正在读取发行版</span> <span class="text-lg font-medium">正在读取构建</span>
<p>请稍候</p> <p>请稍候</p>
{:else if latest} {:else if latestRelease}
<span class="text-lg font-medium">Uranium {releaseTitle(latest)}</span> <span class="text-lg font-medium">Uranium {releaseTitle(latestRelease)}</span>
<p>{latestAsset ? `${latestAsset.name} · ${formatSize(latestAsset.size)}` : "查看发行说明"}</p> <p>
{latestAsset ? `${latestAsset.name} · ${formatSize(latestAsset.size)}` : channel === "stable" ? "稳定版" : "开发预览版"}
</p>
{:else if latestRun}
<span class="text-lg font-medium">Uranium 开发版 #{latestRun.run_number}</span>
<p>{latestRun.head_sha.slice(0, 7)} · {runArtifact(latestRun) ? "Artifact 已就绪" : "查看 Actions 构建"}</p>
{:else} {:else}
<span class="text-lg font-medium">Uranium 开发构建</span> <span class="text-lg font-medium">{channel === "stable" ? "稳定版尚未发布" : "暂无开发构建"}</span>
<p>尚无公开 Release,前往 GitHub Actions</p> <p>{channel === "stable" ? "查看 GitHub Releases" : "查看 GitHub Actions"}</p>
{/if} {/if}
</div> </div>
</a> </a>
<a <a
class="grid h-auto w-12 shrink-0 place-items-center rounded-r-lg border-l border-white/20 hover:bg-white/10 md:w-16" class="grid h-auto w-12 shrink-0 place-items-center rounded-r-lg border-l border-white/20 hover:bg-white/10 md:w-16"
href={latest?.html_url ?? releasesUrl} href={latestRelease?.html_url ?? latestRun?.html_url ?? currentHref()}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
title="查看发行说明" title="查看构建详情"
> >
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"
><path ><path
@@ -129,14 +127,21 @@
</a> </a>
</div> </div>
</div> </div>
<button
class={`transition-border btn btn-outline rounded-md py-3 transition-colors md:w-100 ${channel === "stable" ? "btn-alpha" : "btn-stable"}`}
onclick={toggleChannel}
>
{channel === "stable" ? "切换至开发版" : "返回稳定版"}
</button>
</div> </div>
<section id="builds" class="mt-20"> <section id="builds" class="mt-20">
<h2 class="text-center text-xl font-medium">历史构建</h2> <h2 class="text-center text-xl font-medium">{channel === "stable" ? "历史稳定版" : "历史开发版"}</h2>
<p class="mt-2 mb-8 px-4 text-center text-lg text-gray-800 dark:text-gray-200"> <p class="mt-2 mb-8 px-4 text-center text-lg text-gray-800 dark:text-gray-200">
需要旧版本或发行说明?可以在这里找到。<br /> 需要旧版本、提交记录或完整构建信息?<br />
<span class="text-gray-700 dark:text-gray-400" <span class="text-gray-700 dark:text-gray-400"
>更早的开发构建仍可在 <a href={actionsUrl} class="underline">GitHub Actions</a> 查看。</span >可以使用 <a href="/downloads/uranium/builds" class="underline">构建资源管理器</a> 查看全部构建。</span
> >
</p> </p>
@@ -144,13 +149,11 @@
<div class="text-center text-sm text-red-500">{error}</div> <div class="text-center text-sm text-red-500">{error}</div>
{:else if loading} {:else if loading}
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
{#each Array(5) as _} {#each Array(5) as _}<div class="h-14 animate-pulse rounded-md bg-gray-200/60 dark:bg-gray-800"></div>{/each}
<div class="h-14 animate-pulse rounded-md bg-gray-200/60 dark:bg-gray-800"></div>
{/each}
</div> </div>
{:else if releases.length} {:else if channel === "stable" && stableReleases.length}
<div class="flex min-w-0 flex-col overflow-hidden"> <div class="flex min-w-0 flex-col overflow-hidden">
{#each releases as release, index (release.id)} {#each stableReleases.slice(0, 10) as release, index (release.id)}
{@const asset = preferredAsset(release)} {@const asset = preferredAsset(release)}
<div class="min-w-0"> <div class="min-w-0">
<div <div
@@ -161,18 +164,10 @@
href={asset?.browser_download_url ?? release.html_url} href={asset?.browser_download_url ?? release.html_url}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
class={`btn mr-3 inline-flex min-w-14 shrink-0 items-center justify-center gap-1 rounded-sm p-2 text-center text-sm font-medium sm:mr-4 sm:min-w-16 ${release.prerelease ? "btn-beta" : "btn-stable"}`} class="btn btn-stable mr-3 inline-flex min-w-14 shrink-0 items-center justify-center rounded-sm p-2 text-sm font-medium sm:mr-4 sm:min-w-16"
>{release.tag_name}</a
> >
<svg class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true"> <div class="min-w-0 flex-1 overflow-hidden">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 10v6m0 0-3-3m3 3 3-3m2 8H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 0 1 .707.293l5.414 5.414A1 1 0 0 1 19 9.414V19a2 2 0 0 1-2 2Z"
/>
</svg>
{release.tag_name}
</a>
<div class="min-w-0 flex-1 overflow-hidden text-gray-900 dark:text-gray-200">
<a class="font-medium text-green-700 dark:text-green-400" href={release.html_url} target="_blank" rel="noreferrer" <a class="font-medium text-green-700 dark:text-green-400" href={release.html_url} target="_blank" rel="noreferrer"
>{releaseTitle(release)}</a >{releaseTitle(release)}</a
> >
@@ -182,13 +177,43 @@
{formatDate(release.published_at)} {formatDate(release.published_at)}
</div> </div>
</div> </div>
{#if index < releases.length - 1}<hr class="mx-0 my-0.5 bg-gray-300 dark:bg-gray-700" />{/if} {#if index < Math.min(stableReleases.length, 10) - 1}<hr class="my-0.5 bg-gray-300 dark:bg-gray-700" />{/if}
</div>
{/each}
</div>
{:else if channel === "development" && (developmentReleases.length || catalog.runs.length)}
<div class="flex min-w-0 flex-col overflow-hidden">
{#each catalog.runs.slice(0, 10) as run, index (run.id)}
<div class="min-w-0">
<div
class="flex min-w-0 flex-row items-start rounded-md px-0 py-2 transition-colors hover:bg-gray-200 sm:px-4 dark:hover:bg-gray-800"
>
<a
role="button"
href={run.html_url}
target="_blank"
rel="noreferrer"
class="btn btn-alpha mr-3 inline-flex min-w-14 shrink-0 items-center justify-center rounded-sm p-2 text-sm font-medium sm:mr-4 sm:min-w-16"
>#{run.run_number}</a
>
<div class="min-w-0 flex-1 overflow-hidden">
<a
class="font-mono text-sm text-red-500"
href={`https://github.com/Coldsmiles/Uranium/commit/${run.head_sha}`}
target="_blank"
rel="noreferrer">{run.head_sha.slice(0, 7)}</a
>
<p class="truncate text-sm">{run.display_title}</p>
</div>
<div class="mt-1 ml-2 hidden shrink-0 text-sm text-gray-500 md:block dark:text-gray-300">{formatDate(run.created_at)}</div>
</div>
{#if index < Math.min(catalog.runs.length, 10) - 1}<hr class="my-0.5 bg-gray-300 dark:bg-gray-700" />{/if}
</div> </div>
{/each} {/each}
</div> </div>
{:else} {:else}
<div class="rounded-md border px-4 py-8 text-center text-gray-700 dark:text-gray-300"> <div class="rounded-md border px-4 py-8 text-center text-gray-700 dark:text-gray-300">
当前仓库还没有公开 Release。正式发行前,请通过 GitHub Actions 获取维护分支的测试构建。 {channel === "stable" ? "当前还没有公开稳定版。你可以切换到开发版查看持续集成构建。" : "当前还没有可用的开发构建。"}
</div> </div>
{/if} {/if}
</section> </section>
+7
View File
@@ -29,7 +29,14 @@ import { siteConfig } from "@/config";
<h2 class="mt-4 text-center text-2xl font-medium">项目资源</h2> <h2 class="mt-4 text-center text-2xl font-medium">项目资源</h2>
<div class="grid gap-2 px-2 md:grid-cols-2 xl:gap-4"> <div class="grid gap-2 px-2 md:grid-cols-2 xl:gap-4">
<SoftwarePreview name="快速开始" iconText="?" href="/guide" description="准备 JDK 8、安装发行包并完成首次启动。" /> <SoftwarePreview name="快速开始" iconText="?" href="/guide" description="准备 JDK 8、安装发行包并完成首次启动。" />
<SoftwarePreview
name="构建资源管理器"
iconText="#"
href="/downloads/uranium/builds"
description="浏览稳定版、开发版、提交记录与构建产物。"
/>
<SoftwarePreview name="源代码" iconText="</>" href={siteConfig.repository} external description="查看源代码、提交记录与维护进度。" /> <SoftwarePreview name="源代码" iconText="</>" href={siteConfig.repository} external description="查看源代码、提交记录与维护进度。" />
<SoftwarePreview name="项目介绍" iconText="i" href="/about" description="了解 UraniumMC 的定位、维护重点和项目边界。" />
</div> </div>
</header> </header>
</Layout> </Layout>
+8
View File
@@ -0,0 +1,8 @@
---
import BuildExplorer from "@/components/BuildExplorer.svelte";
import Layout from "@/layouts/Layout.astro";
---
<Layout title="构建资源管理器" description="查看 Uranium 稳定版、开发版、提交记录和构建产物。" canonical="/downloads/uranium/builds/">
<BuildExplorer client:load />
</Layout>
+18
View File
@@ -151,6 +151,24 @@ button {
border-color: #4b5563; border-color: #4b5563;
background: #1f2937; background: #1f2937;
} }
.btn-outline.btn-alpha {
border-color: var(--color-channel-alpha-primary);
background: transparent;
color: var(--color-channel-alpha-primary);
}
.btn-outline.btn-stable {
border-color: var(--color-channel-stable-primary);
background: transparent;
color: var(--color-channel-stable-primary);
}
.btn-outline.btn-alpha:hover {
background: var(--color-channel-alpha-primary);
color: var(--color-channel-alpha-secondary);
}
.btn-outline.btn-stable:hover {
background: var(--color-channel-stable-primary);
color: var(--color-channel-stable-secondary);
}
.home-terminal { .home-terminal {
width: min(100%, 480px); width: min(100%, 480px);
+109
View File
@@ -0,0 +1,109 @@
export interface ReleaseAsset {
id: number;
name: string;
browser_download_url: string;
size: number;
}
export interface Release {
id: number;
tag_name: string;
name: string | null;
html_url: string;
published_at: string;
prerelease: boolean;
draft: boolean;
body: string | null;
assets: ReleaseAsset[];
}
export interface WorkflowRun {
id: number;
name: string;
display_title: string;
head_branch: string;
head_sha: string;
run_number: number;
event: string;
status: string;
conclusion: string;
created_at: string;
updated_at: string;
html_url: string;
}
export interface WorkflowArtifact {
id: number;
name: string;
size_in_bytes: number;
expired: boolean;
created_at: string;
expires_at: string;
workflow_run: {
id: number;
head_branch: string;
head_sha: string;
};
}
export interface BuildCatalog {
releases: Release[];
runs: WorkflowRun[];
artifacts: WorkflowArtifact[];
}
const apiBase = "https://api.github.com/repos/Coldsmiles/Uranium";
const headers = {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
};
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url, { headers });
if (!response.ok) throw new Error(`GitHub API 返回 ${response.status}`);
return response.json() as Promise<T>;
}
export async function fetchBuildCatalog(): Promise<BuildCatalog> {
const [releases, runsResponse, artifactsResponse] = await Promise.all([
fetchJson<Release[]>(`${apiBase}/releases?per_page=30`),
fetchJson<{ workflow_runs: WorkflowRun[] }>(`${apiBase}/actions/runs?branch=dev&status=success&per_page=30`),
fetchJson<{ artifacts: WorkflowArtifact[] }>(`${apiBase}/actions/artifacts?per_page=100`),
]);
return {
releases: releases.filter((release) => !release.draft),
runs: runsResponse.workflow_runs.filter((run) => run.conclusion === "success" && run.event !== "pull_request"),
artifacts: artifactsResponse.artifacts.filter((artifact) => !artifact.expired),
};
}
export function preferredAsset(release?: Release) {
if (!release) return undefined;
return (
release.assets.find((asset) => /server.*\.jar$/i.test(asset.name)) ??
release.assets.find((asset) => /\.(jar|zip)$/i.test(asset.name)) ??
release.assets[0]
);
}
export function artifactForRun(artifacts: WorkflowArtifact[], runId: number) {
return artifacts.find((artifact) => artifact.workflow_run.id === runId);
}
export function releaseTitle(release: Release) {
return release.name || release.tag_name;
}
export function releaseSummary(release: Release) {
const line = release.body?.split(/\r?\n/).find((value) => value.trim());
return line?.replace(/^#+\s*/, "") || `Uranium ${release.tag_name} 发行构建`;
}
export function formatDate(value: string) {
return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "long", day: "numeric" }).format(new Date(value));
}
export function formatSize(bytes: number) {
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}