From 037b1777d6a836390f0223114706027a8ae7fe8a Mon Sep 17 00:00:00 2001 From: Oier Bravo Urtasun Date: Thu, 11 Jun 2026 01:55:05 +0200 Subject: [PATCH] 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 --- .gitignore | 7 +- CLAUDE.md | 7 +- .../src/data/launcher-manifest.example.json | 41 ------ .../src/data/launcher-manifest.schema.json | 119 ------------------ landing/src/data/launcher.example.json | 39 ++++++ landing/src/data/site.ts | 71 ++++++----- landing/src/i18n/ui.ts | 18 +-- plan/18-landing-rework.md | 25 ++-- 8 files changed, 113 insertions(+), 214 deletions(-) delete mode 100644 landing/src/data/launcher-manifest.example.json delete mode 100644 landing/src/data/launcher-manifest.schema.json create mode 100644 landing/src/data/launcher.example.json diff --git a/.gitignore b/.gitignore index 682bb49..4a13d41 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,10 @@ landing/node_modules/ landing/.astro/ landing/dist/ -# launcher download manifest: produced/synced by the custom-launcher release -# flow (the .example.json is committed as the documented shape) -landing/src/data/launcher-manifest.json +# launcher download list: the custom-launcher build output launcher.json, +# synced from UlicraftLauncher/dist/launcher.json (launcher.example.json is +# committed as the documented shape) +landing/src/data/launcher.json # mod catalogue + extracted logos: generated by tooling/build-modlist.py from # the distribution forgemods (the .example.json is committed as the empty shape) diff --git a/CLAUDE.md b/CLAUDE.md index 147eb19..7aa413c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,9 +137,10 @@ mirrored at apex `/launcher/`. Per guest: Full design settled across WS1–WS6; all tasks unchecked, ready to execute. - [ ] **WS1 — Join flow off packwiz.** Homepage = UlicraftLauncher, 3 steps (register → download launcher → join); drop `packwizUrl` + packwiz step. - Per-OS downloads from a synced `launcher-manifest.json` (contract: - `landing/src/data/launcher-manifest.schema.json`, produced by the - `custom-launcher` repo). Fjord moves to its own `/fjord` page. + Per-OS downloads from a synced `launcher.json` (shape + `productName/version/files[]`, the build output of the `custom-launcher` + repo; `launcher.example.json` committed as the documented fallback). Fjord + moves to its own `/fjord` page. - [ ] **WS2/3 — Player rosters.** Online (mc-status, live) + all-registered (new `mc-status /api/mcstatus/players`, admin-login → Drasl `GET /players`, ~60s cache). Shared avatar tile, `AVATAR_MODE` env (default `headiso`). Admin creds diff --git a/landing/src/data/launcher-manifest.example.json b/landing/src/data/launcher-manifest.example.json deleted file mode 100644 index cec8afd..0000000 --- a/landing/src/data/launcher-manifest.example.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "schemaVersion": 1, - "product": "UlicraftLauncher", - "version": "1.0.0", - "channel": "stable", - "releasedAt": "2026-06-01T12:00:00Z", - "minecraft": "1.21.1", - "neoforge": "21.1.233", - "builds": [ - { - "os": "windows", - "arch": "x64", - "format": "exe", - "filename": "UlicraftLauncher-1.0.0-setup.exe", - "url": "https://distribution.ulicraft.net/launcher/UlicraftLauncher-1.0.0-setup.exe", - "size": 78643200, - "sha256": "0000000000000000000000000000000000000000000000000000000000000000" - }, - { - "os": "macos", - "arch": "universal", - "format": "dmg", - "filename": "UlicraftLauncher-1.0.0-universal.dmg", - "url": "https://distribution.ulicraft.net/launcher/UlicraftLauncher-1.0.0-universal.dmg", - "size": 92274688 - }, - { - "os": "linux", - "arch": "x64", - "format": "appimage", - "filename": "UlicraftLauncher-1.0.0-x86_64.AppImage", - "url": "https://distribution.ulicraft.net/launcher/UlicraftLauncher-1.0.0-x86_64.AppImage", - "size": 85983232 - } - ], - "fjordPack": { - "filename": "ulicraft-1.21.1.mrpack", - "url": "https://distribution.ulicraft.net/launcher/ulicraft-1.21.1.mrpack", - "size": 12582912 - } -} diff --git a/landing/src/data/launcher-manifest.schema.json b/landing/src/data/launcher-manifest.schema.json deleted file mode 100644 index 2b53510..0000000 --- a/landing/src/data/launcher-manifest.schema.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://ulicraft.net/schemas/launcher-manifest.schema.json", - "title": "Ulicraft launcher download manifest", - "description": "Produced by the custom-launcher release flow, consumed by the landing build to render per-OS download buttons. One file describes the current UlicraftLauncher release plus the optional Fjord pack.", - "type": "object", - "additionalProperties": false, - "required": ["schemaVersion", "product", "version", "builds"], - "properties": { - "schemaVersion": { - "description": "Bump on breaking shape changes. Landing pins the major it understands.", - "type": "integer", - "const": 1 - }, - "product": { - "description": "Display name of the launcher.", - "type": "string", - "examples": ["UlicraftLauncher"] - }, - "version": { - "description": "Launcher release version (semver, no leading v).", - "type": "string", - "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+].+)?$" - }, - "channel": { - "description": "Release channel.", - "type": "string", - "enum": ["stable", "beta"], - "default": "stable" - }, - "releasedAt": { - "description": "Release timestamp (ISO 8601 / RFC 3339).", - "type": "string", - "format": "date-time" - }, - "minecraft": { - "description": "Target Minecraft version the launcher installs.", - "type": "string", - "examples": ["1.21.1"] - }, - "neoforge": { - "description": "NeoForge version the distribution pins.", - "type": "string", - "examples": ["21.1.233"] - }, - "builds": { - "description": "Downloadable launcher binaries, one entry per OS/arch/format.", - "type": "array", - "minItems": 1, - "items": { "$ref": "#/$defs/asset" } - }, - "fjordPack": { - "description": "Optional importable pack for the Fjord (Prism) alternative path. Lives on its own landing page, not the homepage.", - "$ref": "#/$defs/file" - } - }, - "$defs": { - "asset": { - "type": "object", - "additionalProperties": false, - "required": ["os", "arch", "format", "filename", "url"], - "properties": { - "os": { - "type": "string", - "enum": ["windows", "macos", "linux"] - }, - "arch": { - "description": "CPU arch; 'universal' for a fat/any binary.", - "type": "string", - "enum": ["x64", "arm64", "universal"] - }, - "format": { - "description": "Installer/package kind, drives the button hint.", - "type": "string", - "enum": ["exe", "msi", "dmg", "pkg", "appimage", "deb", "rpm", "tar.gz", "zip"] - }, - "label": { - "description": "Optional human hint override (else derived from os+arch+format).", - "type": "string" - }, - "filename": { - "type": "string", - "minLength": 1 - }, - "url": { - "description": "Absolute download URL.", - "type": "string", - "format": "uri", - "pattern": "^https://" - }, - "size": { - "description": "Bytes, for showing a size next to the button.", - "type": "integer", - "minimum": 0 - }, - "sha256": { - "description": "Lowercase hex SHA-256 for integrity display/verify.", - "type": "string", - "pattern": "^[a-f0-9]{64}$" - } - } - }, - "file": { - "type": "object", - "additionalProperties": false, - "required": ["filename", "url"], - "properties": { - "filename": { "type": "string", "minLength": 1 }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^https://" - }, - "size": { "type": "integer", "minimum": 0 }, - "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" } - } - } - } -} diff --git a/landing/src/data/launcher.example.json b/landing/src/data/launcher.example.json new file mode 100644 index 0000000..8efedf9 --- /dev/null +++ b/landing/src/data/launcher.example.json @@ -0,0 +1,39 @@ +{ + "productName": "Ulicraft", + "version": "1.0.0", + "baseUrl": "https://distribution.ulicraft.net/launcher", + "files": [ + { + "name": "Ulicraft-1.0.0.exe", + "os": "windows", + "arch": "x64", + "ext": "exe", + "size": 90893289, + "url": "https://distribution.ulicraft.net/launcher/Ulicraft-1.0.0.exe" + }, + { + "name": "Ulicraft-1.0.0.AppImage", + "os": "linux", + "arch": "x64", + "ext": "AppImage", + "size": 95722608, + "url": "https://distribution.ulicraft.net/launcher/Ulicraft-1.0.0.AppImage" + }, + { + "name": "Ulicraft-1.0.0.deb", + "os": "linux", + "arch": "x64", + "ext": "deb", + "size": 87751876, + "url": "https://distribution.ulicraft.net/launcher/Ulicraft-1.0.0.deb" + }, + { + "name": "Ulicraft-1.0.0.tar.gz", + "os": "linux", + "arch": "x64", + "ext": "tar.gz", + "size": 116347746, + "url": "https://distribution.ulicraft.net/launcher/Ulicraft-1.0.0.tar.gz" + } + ] +} diff --git a/landing/src/data/site.ts b/landing/src/data/site.ts index 38a5e70..250b932 100644 --- a/landing/src/data/site.ts +++ b/landing/src/data/site.ts @@ -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 /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 = []; - 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) } diff --git a/landing/src/i18n/ui.ts b/landing/src/i18n/ui.ts index 7429bda..da116fa 100644 --- a/landing/src/i18n/ui.ts +++ b/landing/src/i18n/ui.ts @@ -188,7 +188,7 @@ export const ui: Record = { eyebrow: "Modded LAN · NeoForge", tagline: "Kitchen-sink survival, built to outlast the weekend.", sub: - "A self-hosted modded server for the crew — a curated tech + magic + " + + "A self-hosted modded server for El valle — a curated tech + magic + " + "exploration pack, your own skins, one-click setup. Grab the address, " + "import the pack, dig in.", copyIp: "Copy IP", @@ -206,14 +206,14 @@ export const ui: Record = { }, statLabels: ["Mods", "NeoForge", "Player slots", "World backups"], members: { - eyebrow: "The crew", + eyebrow: "El valle", title: "Who's on the server.", lead: "Everyone registered on Ulicraft, with their own self-hosted skin.", empty: "No members yet — be the first to register.", }, features: { eyebrow: "What makes it Ulicraft", - title: "Built for the crew.", + title: "Built for El valle.", lead: "Self-hosted, curated, and persistent — a modded server that respects " + "your time and outlives the LAN.", @@ -370,7 +370,7 @@ export const ui: Record = { eyebrow: "LAN con mods · NeoForge", tagline: "Supervivencia todo incluido, para durar más que el finde.", sub: - "Un servidor con mods autoalojado para la cuadrilla: un pack curado de " + + "Un servidor con mods autoalojado para El valle: un pack curado de " + "tecnología, magia y exploración, tus propias skins y configuración en un " + "clic. Copia la dirección, importa el pack y a cavar.", copyIp: "Copiar IP", @@ -388,14 +388,14 @@ export const ui: Record = { }, statLabels: ["Mods", "NeoForge", "Plazas", "Copias del mundo"], members: { - eyebrow: "La cuadrilla", + eyebrow: "El valle", title: "Quién está en el servidor.", lead: "Todos los registrados en Ulicraft, cada uno con su skin autoalojada.", empty: "Aún no hay miembros — sé el primero en registrarte.", }, features: { eyebrow: "Qué hace especial a Ulicraft", - title: "Hecho para la cuadrilla.", + title: "Hecho para El valle.", lead: "Autoalojado, curado y persistente: un servidor con mods que respeta tu " + "tiempo y sobrevive a la LAN.", @@ -552,7 +552,7 @@ export const ui: Record = { eyebrow: "Mod-dun LANa · NeoForge", tagline: "Mod ugariko biziraupena, irauteko egina.", sub: - "Koadrilarentzako mod-dun zerbitzari auto-ostatatua: teknologia, magia eta " + + "El valle-rentzako mod-dun zerbitzari auto-ostatatua: teknologia, magia eta " + "esplorazio pack zaindua, zure azalak eta konfigurazioa klik bakarrean. " + "Hartu helbidea, inportatu packa eta hasi zulatzen.", copyIp: "Kopiatu IPa", @@ -570,14 +570,14 @@ export const ui: Record = { }, statLabels: ["Modak", "NeoForge", "Plazak", "Munduaren babeskopiak"], members: { - eyebrow: "Koadrila", + eyebrow: "El valle", title: "Nor dago zerbitzarian.", lead: "Ulicraft-en erregistratutako guztiak, bakoitza bere azal auto-ostatatuarekin.", empty: "Oraindik ez dago kiderik — izan zaitez lehena erregistratzen.", }, features: { eyebrow: "Zerk egiten du Ulicraft berezi", - title: "Koadrilarentzat eginda.", + title: "El valle-rentzat eginda.", lead: "Auto-ostatatua, zaindua eta iraunkorra: zure denbora errespetatzen duen " + "eta LANa gainditzen duen mod-dun zerbitzaria.", diff --git a/plan/18-landing-rework.md b/plan/18-landing-rework.md index 0ad7f82..f95fb00 100644 --- a/plan/18-landing-rework.md +++ b/plan/18-landing-rework.md @@ -35,14 +35,16 @@ authlib step. your credentials. (No authlib URL — launcher is preconfigured.) 3. **Join the server** — launch, connect to `site.serverAddress` (IP copy chip). -**Launcher manifest (delivery = synced local file, option B):** -- Contract: `landing/src/data/launcher-manifest.schema.json` (already written; - JSON-Schema draft 2020-12). -- The `custom-launcher` release flow **produces** `launcher-manifest.json` and syncs - it into `landing/src/data/launcher-manifest.json` (gitignored, generated), same - pattern as `sync-server-mods.sh`. -- Landing **imports** it at build → renders per-OS buttons from `builds[]`. Missing - file → hide the buttons (build must not fail). +**Launcher list (delivery = synced local file, option B):** +- Source of truth: the `custom-launcher` build output `launcher.json` (shape + `productName/version/baseUrl/files[]`, each file `{name,os,arch,ext,size,url}`), + published to `https://distribution.${BASE}/launcher/launcher.json`. +- The release flow syncs it into `landing/src/data/launcher.json` (gitignored, + generated), same pattern as `sync-server-mods.sh`. `launcher.example.json` + committed as the documented fallback shape. +- Landing reads it at build (`site.ts` `loadLauncherManifest`), normalizes + `files[]` → internal `builds[]`, renders per-OS buttons. Missing file → hide + the buttons (build must not fail). **Fjord — alternative path, OWN page, off the homepage:** new `/fjord` (+ `/es`, `/eu`). Download Fjord (existing `/launcher/` mirror), add authlib-injector account @@ -174,7 +176,7 @@ Tasks: - [ ] `site.ts`: feed `stats[0]` count from `mods.json`. - [ ] Run the generator pre-`pnpm build`; document in `12-build-order.md` / `14-deploy.md`. `.gitignore`: `landing/src/data/mods.json`, - `landing/src/data/launcher-manifest.json`, `landing/src/data/players.json` (if + `landing/src/data/launcher.json`, `landing/src/data/players.json` (if any baked), `landing/public/mod-logos/`. --- @@ -182,7 +184,7 @@ Tasks: ## Verification (whole rework) - **Build:** `cd landing && set -a && . ../.env && set +a && pnpm run build` then - `pnpm check`. Must pass offline even if `launcher-manifest.json` / `mods.json` are + `pnpm check`. Must pass offline even if `launcher.json` / `mods.json` are absent (buttons/list hidden). - `grep -ri packwiz landing/` → no live join references remain (Fjord page may mention its own import). @@ -201,4 +203,5 @@ Tasks: - `15-mc-status.md` — the mc-status service (now also serves the roster). - `16-landing-registration.md` — the browser-direct Drasl pattern WS4 reuses. - `17-mod-shortlist.md` — the mod gap doc + the `mods.json` dataset. -- `landing/src/data/launcher-manifest.schema.json` — the launcher manifest contract. +- `launcher.json` — the `custom-launcher` build output (download list); landing + syncs it to `landing/src/data/launcher.json`, `launcher.example.json` committed.