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",
},
});
}