import { getCollection } from "astro:content"; import { SITE, getAuthor, sortedPosts } from "../lib/blog-data.js"; const BASE_URL = SITE.url || ""; const esc = (value) => value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); // Wrap arbitrary text safely in a CDATA section. const cdata = (s) => `/g, "]]]]>")}]]>`; // Convert MDX/Markdown body to plain text (text only — strip formatting, keep link text). function mdToText(md = "") { return md .replace(/^---[\s\S]*?---\s*/, "") // strip frontmatter if present .replace(/^import\s.*$/gm, "") // strip MDX import lines .replace(/`{1,3}([^`]*)`{1,3}/g, "$1") // inline code .replace(/!\[[^\]]*\]\([^)]*\)/g, "") // images .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1") // links → text .replace(/^\s{0,3}#{1,6}\s+/gm, "") // headings .replace(/^\s{0,3}>\s?/gm, "") // blockquotes .replace(/^\s{0,3}([-*+])\s+/gm, "") // unordered list markers .replace(/^\s{0,3}\d+\.\s+/gm, "") // ordered list markers .replace(/^\s*[-*_]{3,}\s*$/gm, "") // horizontal rules .replace(/(\*\*|__)(.*?)\1/g, "$2") // bold .replace(/(\*|_)(.*?)\1/g, "$2") // italic .replace(/<[^>]+>/g, "") // any stray HTML/JSX tags .replace(/\n{3,}/g, "\n\n") // collapse blank lines .trim(); } export async function GET() { const bodyBySlug = Object.fromEntries((await getCollection("blog")).map((e) => [e.id, e.body || ""])); const items = (await sortedPosts()).map((post) => { const link = `${BASE_URL}/blog/${post.slug}`; const author = getAuthor(post.author); const fullText = mdToText(bodyBySlug[post.slug]); return [ " ", ` ${esc(post.title)}`, ` ${link}`, ` ${link}`, ` ${new Date(post.date).toUTCString()}`, author ? ` ${esc(author.name)}` : "", ` ${esc(post.excerpt)}`, ` ${cdata(fullText)}`, " ", ] .filter(Boolean) .join("\n"); }); const xml = [ '', '', " ", ` ${esc(SITE.name)}`, ` ${BASE_URL}/`, ` ${esc(SITE.description)}`, " en-us", ...items, " ", "", ].join("\n"); return new Response(xml, { headers: { "Content-Type": "application/rss+xml; charset=utf-8", "Cache-Control": "public, max-age=3600", }, }); }