Support naddr/nevent/note slugs for unindexed posts, cache naddr lookups, render Nostr embeds in markdown, and add a consistency check for events mirrors. Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
2.9 KiB
JavaScript
101 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Consistency check: confirms the number of upcoming meetups reported by the
|
|
* live /events page, the /events.md mirror, and the /llms.txt summary all match.
|
|
*
|
|
* Catches the exact "confidently wrong" drift this check exists to prevent:
|
|
* a mirror or summary going stale relative to the rendered page.
|
|
*
|
|
* Usage:
|
|
* node scripts/check-consistency.mjs [baseUrl]
|
|
* BASE_URL=https://belgianbitcoinembassy.org node scripts/check-consistency.mjs
|
|
*
|
|
* Exits 0 if all three agree, 1 on any mismatch or fetch failure.
|
|
*/
|
|
|
|
const baseUrl = (
|
|
process.argv[2] ||
|
|
process.env.BASE_URL ||
|
|
"http://localhost:3000"
|
|
).replace(/\/$/, "");
|
|
|
|
async function getText(path) {
|
|
const url = `${baseUrl}${path}`;
|
|
const res = await fetch(url, {
|
|
headers: { "User-Agent": "bbe-consistency-check" },
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`GET ${url} -> ${res.status}`);
|
|
}
|
|
return res.text();
|
|
}
|
|
|
|
/** /llms.txt: "There are currently N upcoming meetup(s) scheduled." */
|
|
function parseLlmsTxt(text) {
|
|
const m = text.match(/currently\s+(\d+)\s+upcoming meetup/i);
|
|
if (!m) throw new Error("llms.txt: could not find upcoming-meetup count line");
|
|
return Number(m[1]);
|
|
}
|
|
|
|
/** /events.md: count "### " headings within the "## Upcoming" section. */
|
|
function parseEventsMd(text) {
|
|
const lines = text.split("\n");
|
|
let inUpcoming = false;
|
|
let count = 0;
|
|
let sawUpcoming = false;
|
|
for (const line of lines) {
|
|
if (/^##\s+Upcoming\b/.test(line)) {
|
|
inUpcoming = true;
|
|
sawUpcoming = true;
|
|
continue;
|
|
}
|
|
if (/^##\s+/.test(line) && inUpcoming) inUpcoming = false; // next section
|
|
if (inUpcoming && /^###\s+/.test(line)) count += 1;
|
|
}
|
|
if (!sawUpcoming) throw new Error("events.md: no '## Upcoming' section found");
|
|
return count;
|
|
}
|
|
|
|
/** /events: data-upcoming-count attribute rendered into the HTML. */
|
|
function parseEventsHtml(text) {
|
|
const m = text.match(/data-upcoming-count="(\d+)"/);
|
|
if (!m) throw new Error("events: data-upcoming-count attribute not found in HTML");
|
|
return Number(m[1]);
|
|
}
|
|
|
|
async function main() {
|
|
const [llmsTxt, eventsMd, eventsHtml] = await Promise.all([
|
|
getText("/llms.txt"),
|
|
getText("/events.md"),
|
|
getText("/events"),
|
|
]);
|
|
|
|
const counts = {
|
|
"/llms.txt": parseLlmsTxt(llmsTxt),
|
|
"/events.md": parseEventsMd(eventsMd),
|
|
"/events": parseEventsHtml(eventsHtml),
|
|
};
|
|
|
|
const values = Object.values(counts);
|
|
const allMatch = values.every((v) => v === values[0]);
|
|
|
|
console.log(`Consistency check against ${baseUrl}`);
|
|
for (const [name, value] of Object.entries(counts)) {
|
|
console.log(` ${name.padEnd(12)} upcoming = ${value}`);
|
|
}
|
|
|
|
if (!allMatch) {
|
|
console.error(
|
|
`\n✗ MISMATCH: upcoming-meetup counts disagree (${values.join(", ")}).`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`\n✓ All sources agree: ${values[0]} upcoming meetup(s).`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(`✗ Consistency check failed: ${err.message}`);
|
|
process.exit(1);
|
|
});
|