clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

main
bindws / scripts / check / config.mjs
config.mjs 3.0K
1// Checks a relay configuration file (JSONC) two ways: against the schema,2// for the editor's sake, and with the relay's own parser (src/config.ts),3// which is the last word. Both must pass and the parser must drop nothing,4// since an entry a relay would quietly leave out is a mistake in a file5// under source control. Returns the parsed configuration or throws with6// every problem found.7import { readFileSync } from "node:fs";8import { buildSync } from "esbuild";9import Ajv from "ajv";10import { stripComments } from "./jsonc.mjs";1112const ROOT = new URL("../../", import.meta.url);13const schema = JSON.parse(readFileSync(new URL("relay-config.schema.json", ROOT), "utf8"));14const validate = new Ajv({ allErrors: true }).compile(schema);1516let parser = null;17async function parseConfig() {18 if (parser) return parser;19 const js = buildSync({ entryPoints: [new URL("src/config.ts", ROOT).pathname], bundle: true, format: "esm", platform: "neutral", write: false, logLevel: "silent", external: ["cloudflare:*"] }).outputFiles[0].text;20 parser = (await import("data:text/javascript;base64," + Buffer.from(js).toString("base64"))).parseConfig;21 return parser;22}2324export function readConfig(path) {25 return JSON.parse(stripComments(readFileSync(path, "utf8")));26}2728export async function checkConfig(doc, label = "configuration") {29 const problems = [];30 if (!validate(doc)) for (const e of validate.errors) problems.push(`${label}: ${e.instancePath || "/"} ${e.message}`);31 const parsed = (await parseConfig())(doc);32 if (typeof parsed === "string") problems.push(`${label}: ${parsed}`);33 else for (const w of parsed.warnings) problems.push(`${label}: ${w}`);34 if (problems.length) throw new Error(problems.join("\n"));35 return parsed;36}3738// describe says what a parsed configuration declares, one line per section.39export function describe(cfg) {40 const lines = [];41 if (cfg.template) lines.push(`template: ${cfg.template.title}`);42 if (cfg.sections.includes("policy")) lines.push(`policy: ${Object.keys(cfg.policy).length} settings (${Object.keys(cfg.policy).join(", ")})`);43 if (cfg.sections.includes("members")) lines.push(`members: ${cfg.members.length}`);44 if (cfg.sections.includes("bans")) lines.push(`bans: ${cfg.bans.length}`);45 if (cfg.sections.includes("addresses")) lines.push(`addresses: ${cfg.addresses.length}`);46 if (cfg.sections.includes("banned_events")) lines.push(`banned events: ${cfg.banned_events.length}`);47 if (cfg.sections.includes("kinds")) lines.push(`kinds: allow ${cfg.kinds.allow.length ? cfg.kinds.allow.join(", ") : "every kind"}; block ${cfg.kinds.block.join(", ") || "none"}`);48 if (cfg.sections.includes("retention")) lines.push(`retention: ${cfg.retention.map((r) => `${r.kind === null ? "everything" : "kind " + r.kind} ${r.days} days`).join(", ") || "none"}`);49 if (cfg.sections.includes("connections")) lines.push(`connections: ${cfg.connections.map((c) => c.template + (c.visibility === "public" ? "" : " (" + c.visibility + ")")).join(", ") || "none"}`);50 return lines;51}