feat(landing): join-flow rework, player rosters, account page, mod list
Execute plan/18 (WS1-6) + plan/17.
- WS1: homepage join = 3 steps off packwiz (register -> UlicraftLauncher
-> join); per-OS downloads from a synced launcher-manifest.json (schema +
example committed); Fjord moved to its own /fjord page. Drop packwizUrl.
- WS2/3: mc-status gains an isolated /players endpoint (admin login ->
Drasl GET /players, own client+TTL+token cache, never blocks the status
path); online + registered rosters render shared name+avatar tiles;
AVATAR_MODE env (default headiso).
- WS4: /account skin CRUD via browser-direct Drasl (login -> upload ->
reset), in-memory token, players[0]; register relinks "Manage it here".
- WS5: footer link to status.${BASE_DOMAIN} in every footer.
- WS6: tooling/build-modlist.py generates mods.json + extracted logos from
the distribution forgemods; ModList.astro renders a flat list; stat tile
fed from the count.
Manifest/mods loaders read at build with a process.cwd() fallback (the
import.meta.url-only path does not resolve in Astro's bundled build).
New env: AVATAR_MODE, DRASL_ADMIN_USERNAME, DRASL_ADMIN_PASSWORD (mc-status
only). Generated mods.json / launcher-manifest.json / logos are gitignored;
.example.json files document the shapes. Build: 12 pages; mc-status builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
56
landing/src/data/mods.ts
Normal file
56
landing/src/data/mods.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
// Build-time loader for the auto-generated mod catalogue. tooling/build-modlist.py
|
||||
// writes src/data/mods.json (gitignored, generated) from the distribution's full
|
||||
// client modset; it MAY NOT EXIST on a fresh checkout, so we read it at runtime
|
||||
// with fs (a static `import` would hard-fail the build when absent). Fall back to
|
||||
// the committed .example.json (an empty catalogue); if even that is unreadable,
|
||||
// expose an empty catalogue so the page simply renders no mods and the stat tile
|
||||
// keeps its default. Same pattern as loadLauncherManifest in site.ts.
|
||||
// @ts-ignore — node builtins (no @types/node in this Astro SSG; same reason
|
||||
// `process`/`import.meta.url` reads work at build time, as in site.ts).
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
export type Mod = {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
authors: string;
|
||||
description: string;
|
||||
logo: string | null;
|
||||
};
|
||||
|
||||
export type ModCatalogue = {
|
||||
schemaVersion: number;
|
||||
count: number;
|
||||
mods: Mod[];
|
||||
};
|
||||
|
||||
export function loadMods(): ModCatalogue {
|
||||
const empty: ModCatalogue = { schemaVersion: 1, count: 0, mods: [] };
|
||||
// Resolve relative to this module first (works when import.meta.url points at
|
||||
// the source dir), then fall back to a cwd-relative path (Astro runs the build
|
||||
// from the landing root, so src/data/* resolves there even when the module has
|
||||
// been bundled to a different location). Prefer the generated mods.json, then
|
||||
// the committed empty mods.example.json.
|
||||
// @ts-ignore — `process` is untyped here (no @types/node), same as site.ts.
|
||||
const cwd: string = typeof process !== "undefined" ? process.cwd() : ".";
|
||||
const candidates: Array<string | URL> = [];
|
||||
for (const name of ["mods.json", "mods.example.json"]) {
|
||||
try {
|
||||
candidates.push(new URL(name, import.meta.url));
|
||||
} catch {
|
||||
// import.meta.url unavailable — skip the module-relative candidate.
|
||||
}
|
||||
candidates.push(`${cwd}/src/data/${name}`);
|
||||
}
|
||||
for (const c of candidates) {
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(c, "utf8")) as ModCatalogue;
|
||||
if (parsed && Array.isArray(parsed.mods)) return parsed;
|
||||
} catch {
|
||||
// try the next candidate (file missing/unreadable → fall back)
|
||||
}
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
export const MODS = loadMods();
|
||||
Reference in New Issue
Block a user