feat: T5DE's website: landing page, download, changelog and FAQ
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
8401f30f78
35 files changed
+2216
No files matched your search
@@ -0,0 +1,81 @@
|
||||
// T5DE's client releases, from git.t5de.dev's API (the t5de repository's releases, which its CI publishes). Pages
|
||||
// using them are regenerated every few minutes; if Gitea can't be reached, they link to its releases page instead.
|
||||
|
||||
const repo = "https://git.t5de.dev/api/v1/repos/t5de/t5de";
|
||||
export const releasesPage = "https://git.t5de.dev/t5de/t5de/releases";
|
||||
const revalidate = 300;
|
||||
|
||||
export type Release = {
|
||||
tag: string;
|
||||
version: string;
|
||||
date: string;
|
||||
nightly: boolean;
|
||||
url: string;
|
||||
body: string;
|
||||
setup: { url: string; size: number } | null;
|
||||
sha256: string | null;
|
||||
imvu: string | null;
|
||||
};
|
||||
|
||||
type GiteaRelease = {
|
||||
tag_name: string;
|
||||
name: string;
|
||||
body: string;
|
||||
prerelease: boolean;
|
||||
draft: boolean;
|
||||
published_at: string;
|
||||
html_url: string;
|
||||
assets: { name: string; size: number; browser_download_url: string }[];
|
||||
};
|
||||
|
||||
function parse(r: GiteaRelease): Release {
|
||||
const nightly = r.tag_name === "nightly";
|
||||
const setup = r.assets.find((a) => a.name === "T5DE-Setup.exe");
|
||||
return {
|
||||
tag: r.tag_name,
|
||||
// Releases are tagged vX.Y.Z; the nightly's version is in its name ("Nightly <version>").
|
||||
version: nightly ? r.name.replace(/^Nightly\s+/, "") : r.tag_name.replace(/^v/, ""),
|
||||
date: r.published_at,
|
||||
nightly,
|
||||
url: r.html_url,
|
||||
body: r.body,
|
||||
setup: setup ? { url: setup.browser_download_url, size: setup.size } : null,
|
||||
sha256: /`T5DE-Setup\.exe` sha256: `([0-9a-f]{64})`/.exec(r.body)?.[1] ?? null,
|
||||
imvu: /IMVU Classic \*\*([\d.]+)\*\*/.exec(r.body)?.[1] ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async function get<T>(path: string): Promise<T | null> {
|
||||
try {
|
||||
const res = await fetch(repo + path, { next: { revalidate } });
|
||||
return res.ok ? ((await res.json()) as T) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The latest release (not the nightly), or null. */
|
||||
export async function latestRelease(): Promise<Release | null> {
|
||||
const r = await get<GiteaRelease>("/releases/latest");
|
||||
return r ? parse(r) : null;
|
||||
}
|
||||
|
||||
/** The current nightly build, or null. */
|
||||
export async function nightlyRelease(): Promise<Release | null> {
|
||||
const r = await get<GiteaRelease>("/releases/tags/nightly");
|
||||
return r ? parse(r) : null;
|
||||
}
|
||||
|
||||
/** Every release, newest first, without the nightly; null if Gitea can't be reached. */
|
||||
export async function allReleases(): Promise<Release[] | null> {
|
||||
const rs = await get<GiteaRelease[]>("/releases?limit=50&pre-release=false&draft=false");
|
||||
return rs ? rs.filter((r) => r.tag_name !== "nightly" && !r.draft).map(parse) : null;
|
||||
}
|
||||
|
||||
export function formatSize(bytes: number): string {
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
export function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", timeZone: "UTC" });
|
||||
}
|
||||
Reference in new issue
Block a user