添加稳定版开发版渠道和构建资源管理器
This commit is contained in:
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user