import { randomUUID } from "node:crypto"; import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; async function load(path: string): Promise> { let raw: string; try { raw = await readFile(path, "utf8"); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return {}; throw error; } try { return JSON.parse(raw) ?? {}; } catch { throw new Error(`${path} is not valid JSON.`); } } async function save(path: string, config: Record): Promise { await mkdir(dirname(path), { recursive: true }); const temporary = `${path}.${randomUUID()}.tmp`; try { await writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, { flag: "wx" }); await rename(temporary, path); } finally { await rm(temporary, { force: true }); } } function hostsOf(path: string, config: Record): string[] { const hosts = config.allowedHosts ?? []; if (!Array.isArray(hosts) || !hosts.every(host => typeof host === "string")) { throw new Error(`${path} needs "allowedHosts" to be an array of hostnames.`); } return hosts.map(host => host.toLowerCase()); } export async function loadHosts(path: string): Promise { return hostsOf(path, await load(path)); } export async function addHost(path: string, host: string): Promise { const config = await load(path); const hosts = hostsOf(path, config); if (!hosts.includes(host)) await save(path, { ...config, allowedHosts: [...hosts, host] }); } export async function removeHosts(path: string, host?: string): Promise { const config = await load(path); const hosts = hostsOf(path, config); await save(path, { ...config, allowedHosts: host === undefined ? [] : hosts.filter(h => h !== host) }); } export async function loadConfirmEval(path: string): Promise { const value = (await load(path)).confirmEval ?? false; if (typeof value !== "boolean") throw new Error(`${path} needs "confirmEval" to be true or false.`); return value; } export async function setConfirmEval(path: string, value: boolean): Promise { await save(path, { ...await load(path), confirmEval: value }); }