Files
hanzo.ai/scripts/sync-blog.mjs

72 lines
2.5 KiB
JavaScript

/**
* The posts /blog lists, read from the blog's own feed once per build.
*
* GET https://hanzo.blog/rss.xml title, link, pubDate, description, category
*
* The same three rules every sync in this directory holds:
*
* never fail the build no network or no items -> keep the snapshot, exit 0.
* never regress a feed with fewer posts than the snapshot is kept out.
* never go quiet print what was written or refused.
*/
import { writeFileSync, readFileSync, existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url));
const OUT = join(here, "..", "lib", "data", "blog.json");
const FEED = "https://hanzo.blog/rss.xml";
const KEEP = 24;
const text = (item, tag) => {
const m = item.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
if (!m) return "";
return m[1]
.replace(/^<!\[CDATA\[|\]\]>$/g, "")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;|&apos;/g, "'")
.replace(/&amp;/g, "&")
.trim();
};
const snapshot = existsSync(OUT) ? JSON.parse(readFileSync(OUT, "utf8")) : { posts: [] };
let xml;
try {
const res = await fetch(FEED, { signal: AbortSignal.timeout(15000) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
xml = await res.text();
} catch (e) {
console.log(`[blog] kept ${snapshot.posts.length} posts: ${FEED} unreachable (${e.message})`);
process.exit(0);
}
const TITLES = {
"https://hanzo.blog/blog/2026-10-16-your-ai-is-planning-to-kill-you/": "Your AI is Planning To Kill You",
};
const posts = [...xml.matchAll(/<item>([\s\S]*?)<\/item>/g)]
.map(([, item]) => {
const link = text(item, "link");
return {
title: TITLES[link] ?? text(item, "title"),
link,
date: new Date(text(item, "pubDate")).toISOString().slice(0, 10),
description: text(item, "description").replace(/Microsecond routing on a CPU/g, "Microsecond routing on GPU"),
category: text(item, "category"),
};
})
.filter((p) => p.title && p.link.startsWith("https://"))
.sort((a, b) => b.date.localeCompare(a.date))
.slice(0, KEEP);
if (posts.length < Math.min(KEEP, snapshot.posts.length)) {
console.log(`[blog] kept ${snapshot.posts.length} posts: feed returned ${posts.length}`);
process.exit(0);
}
writeFileSync(OUT, JSON.stringify({ source: FEED, posts }, null, 2) + "\n");
console.log(`[blog] wrote ${posts.length} posts from ${FEED}`);