Files
idle_clicker/server/api.js
T

77 lines
2.3 KiB
JavaScript

import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
// Content packs are discovered from the filesystem, so adding a theme or a
// language is just dropping files in a folder: no code change, no rebuild list.
const THEME_ID = /^[a-z0-9][a-z0-9-]*$/;
const LOCALE_CODE = /^[a-z]{2,3}(-[A-Za-z0-9]+)*$/;
async function readJson(file) {
return JSON.parse(await readFile(file, 'utf8'));
}
async function listLocaleCodes(dir) {
let names;
try {
names = await readdir(dir);
} catch {
return [];
}
return names
.filter((n) => n.endsWith('.json'))
.map((n) => n.slice(0, -'.json'.length))
.filter((code) => LOCALE_CODE.test(code))
.sort();
}
/** GET /api/themes -> [{ id, order, assets, locales }] */
async function listThemes(publicDir) {
const root = path.join(publicDir, 'themes');
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
const themes = [];
for (const entry of entries) {
if (!entry.isDirectory() || !THEME_ID.test(entry.name)) continue;
const dir = path.join(root, entry.name);
try {
const manifest = await readJson(path.join(dir, 'theme.json'));
themes.push({
id: entry.name,
order: Number.isFinite(manifest.order) ? manifest.order : 100,
assets: manifest.assets && typeof manifest.assets === 'object' ? manifest.assets : {},
locales: await listLocaleCodes(path.join(dir, 'locales')),
});
} catch {
// Skip folders without a valid theme.json rather than failing the whole list.
}
}
return themes.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
}
/** GET /api/locales -> [{ code, name }] */
async function listLocales(publicDir) {
const dir = path.join(publicDir, 'locales');
const out = [];
for (const code of await listLocaleCodes(dir)) {
try {
const file = await readJson(path.join(dir, `${code}.json`));
out.push({ code, name: file?.meta?.name ?? code });
} catch {
// Ignore malformed locale files.
}
}
return out;
}
/** Returns a JSON payload for known /api routes, or undefined for unknown ones. */
export async function handleApi(publicDir, pathname) {
switch (pathname) {
case '/api/themes':
return listThemes(publicDir);
case '/api/locales':
return listLocales(publicDir);
default:
return undefined;
}
}