feat(demo): list the demo's sign-ins on /beta and link into it per game
Some checks failed
PR checks / checks (pull_request) Failing after 2m16s

The public demo (demo.runicgateway.com) is live, so the site's demo slot
can be switched on. Two decisions of record, PLAN.md §15:

- D82: /beta reads the demo's GET /api/v1/public/settings demo.logins on
  the server, caches it for a minute, and lists the published sign-ins
  with the next wipe. If the demo does not answer, /beta links to its
  sign-in page instead. Gate two reads as in place.
- D83: a deep link into a game module's pages carries data-demo-game, and
  applyBrand.mjs fills it only for games in the new mounted demoGames
  list. The Rust features gain links into /rust and /rust/servers/demo.
  UO's stay hidden on a Rust demo.

The public-demo not-built entry is removed, because its resolvedBy was met.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-28 15:32:34 -05:00
parent 8701126d64
commit 769d213c80
18 changed files with 650 additions and 63 deletions

View File

@@ -32,6 +32,7 @@ import path from 'node:path';
import sharp from 'sharp';
import { classify } from '../src/lib/brandAssets.mjs';
import { games } from '../src/data/games/index.mjs';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
const DEFAULTS = path.join(ROOT, 'brand-default');
@@ -157,6 +158,20 @@ if (brand) {
}
}
// D83: the one list-valued field. Each entry is a game module's id, checked against the
// games this site describes, so a typo is a failed build rather than a demo whose game
// links silently never appear.
const gameIds = games.map((game) => game.key);
if (!Array.isArray(brand.demoGames)) {
fail('brand.json is missing the list field "demoGames" (an empty list is the stock value).');
} else {
for (const id of brand.demoGames) {
if (!gameIds.includes(id)) {
fail(`brand.json's "demoGames" names ${JSON.stringify(id)}, which is not one of ${gameIds.join(', ')}.`);
}
}
}
/**
* Kept in step with `TEXT_FIELDS` in `applyBrand.mjs` by reading that file rather than by
* restating the list. A field added there and forgotten here would be unchecked; a field
@@ -348,6 +363,23 @@ if (!deepPattern) {
);
}
// A game's link (D83) must also be read WITH its game. A pattern that matched the first
// three attributes and dropped the fourth would treat a /uo/ link as a core one and fill
// it on every demo, which is the 404 the attribute exists to prevent.
const gameSample = `${sample} data-demo-game="rust"`;
let gameRead = null;
try {
gameRead = new RegExp(deepPattern[1]).exec(gameSample)?.[2] ?? null;
} catch {
// Reported above.
}
if (gameRead !== 'rust') {
fail(
`applyBrand.mjs's DEEP_LINK does not read the game out of \`${gameSample}\`.\n` +
' Game links would follow demoUrl alone and point into modules the demo may not run.'
);
}
const deepStrays = [];
let deepLinks = 0;