feat(landing): rename crew to "El valle" + source downloads from launcher.json
Rename all "crew" mentions (en/es/eu) to the untranslated proper name "El valle" across hero, members, and features copy in i18n/ui.ts. Rewire the homepage launcher download list to the custom-launcher build output launcher.json (productName/version/files[]) instead of the never-produced launcher-manifest.json shape. site.ts now reads launcher.json and normalizes files[] -> internal builds[]; launcher.example.json is the committed fallback. Drop the stale launcher-manifest.example/schema, update .gitignore and docs (CLAUDE.md, plan/18). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,51 +23,66 @@ const AVATAR_MODE = process.env.AVATAR_MODE ?? "headiso";
|
||||
// @ts-ignore — `process` is untyped here (no @types/node).
|
||||
const ROSTER_AVATAR_MODE = process.env.ROSTER_AVATAR_MODE ?? "fullbodyiso";
|
||||
|
||||
// Launcher download manifest (per launcher-manifest.schema.json). The
|
||||
// custom-launcher release flow syncs launcher-manifest.json into this dir
|
||||
// (gitignored, generated) — same pattern as sync-server-mods.sh. It MAY NOT
|
||||
// EXIST at build time, 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 (the documented shape); if even that is unreadable, expose an
|
||||
// empty-builds manifest so the homepage simply renders no download buttons.
|
||||
// UlicraftLauncher download list. Source of truth is the custom-launcher repo's
|
||||
// build output `launcher.json` (shape: productName/version/baseUrl/files[]),
|
||||
// published to https://distribution.${BASE}/launcher/launcher.json. The release
|
||||
// flow syncs that file into this dir as `launcher.json` (gitignored, generated)
|
||||
// — same pattern as sync-server-mods.sh. It MAY NOT EXIST at build time, so we
|
||||
// read it at runtime with fs (a static `import` would hard-fail the build when
|
||||
// absent). Fall back to the committed `launcher.example.json` (the documented
|
||||
// shape); if even that is unreadable, expose an empty file list so the homepage
|
||||
// simply renders no download buttons. The raw launcher.json is normalized into
|
||||
// the internal shape the page renders against (builds[]).
|
||||
type LauncherBuild = {
|
||||
os: "windows" | "macos" | "linux";
|
||||
arch: "x64" | "arm64" | "universal";
|
||||
format: "exe" | "msi" | "dmg" | "pkg" | "appimage" | "deb" | "rpm" | "tar.gz" | "zip";
|
||||
label?: string;
|
||||
os: string; // "windows" | "macos" | "linux"
|
||||
arch: string; // "x64" | "arm64" | "universal"
|
||||
format: string; // file extension: "exe" | "dmg" | "AppImage" | "deb" | "tar.gz" | …
|
||||
filename: string;
|
||||
url: string;
|
||||
size?: number;
|
||||
sha256?: string;
|
||||
};
|
||||
type LauncherFile = { filename: string; url: string; size?: number; sha256?: string };
|
||||
type LauncherFile = { filename: string; url: string; size?: number };
|
||||
type LauncherManifest = {
|
||||
schemaVersion: number;
|
||||
product: string;
|
||||
version: string;
|
||||
channel?: string;
|
||||
releasedAt?: string;
|
||||
minecraft?: string;
|
||||
neoforge?: string;
|
||||
builds: LauncherBuild[];
|
||||
// Not present in launcher.json; the Fjord page degrades gracefully when absent.
|
||||
fjordPack?: LauncherFile;
|
||||
};
|
||||
|
||||
function loadLauncherManifest(): LauncherManifest {
|
||||
const empty: LauncherManifest = {
|
||||
schemaVersion: 1,
|
||||
product: "UlicraftLauncher",
|
||||
version: "0.0.0",
|
||||
builds: [],
|
||||
// Raw shape as produced by the custom-launcher build (UlicraftLauncher/dist/launcher.json).
|
||||
type RawLauncherJson = {
|
||||
productName?: string;
|
||||
version?: string;
|
||||
baseUrl?: string;
|
||||
files?: Array<{ name: string; os: string; arch: string; ext: string; url: string; size?: number }>;
|
||||
};
|
||||
|
||||
function normalizeLauncher(raw: RawLauncherJson): LauncherManifest {
|
||||
return {
|
||||
product: raw.productName ?? "UlicraftLauncher",
|
||||
version: raw.version ?? "0.0.0",
|
||||
builds: (raw.files ?? []).map((f) => ({
|
||||
os: f.os,
|
||||
arch: f.arch,
|
||||
format: f.ext,
|
||||
filename: f.name,
|
||||
url: f.url,
|
||||
size: f.size,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function loadLauncherManifest(): LauncherManifest {
|
||||
const empty: LauncherManifest = { product: "UlicraftLauncher", version: "0.0.0", builds: [] };
|
||||
// Resolve relative to this module first, then fall back to a cwd-relative path:
|
||||
// Astro bundles this module so import.meta.url may not point at src/data/ at
|
||||
// build time, but the build runs from the landing root, so <cwd>/src/data/*
|
||||
// resolves. Prefer the synced launcher-manifest.json, then the committed example.
|
||||
// resolves. Prefer the synced launcher.json, then the committed example.
|
||||
// @ts-ignore — `process` is untyped here (no @types/node).
|
||||
const cwd: string = typeof process !== "undefined" ? process.cwd() : ".";
|
||||
const candidates: Array<string | URL> = [];
|
||||
for (const name of ["launcher-manifest.json", "launcher-manifest.example.json"]) {
|
||||
for (const name of ["launcher.json", "launcher.example.json"]) {
|
||||
try {
|
||||
candidates.push(new URL(name, import.meta.url));
|
||||
} catch {
|
||||
@@ -77,8 +92,8 @@ function loadLauncherManifest(): LauncherManifest {
|
||||
}
|
||||
for (const c of candidates) {
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(c, "utf8")) as LauncherManifest;
|
||||
if (parsed && Array.isArray(parsed.builds)) return parsed;
|
||||
const raw = JSON.parse(readFileSync(c, "utf8")) as RawLauncherJson;
|
||||
if (raw && Array.isArray(raw.files)) return normalizeLauncher(raw);
|
||||
} catch {
|
||||
// try the next candidate (synced file missing → fall back to example)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user