diff --git a/bun.lockb b/bun.lockb
index 24f19bd..6297847 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index c6bc766..3eefbd3 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.13",
"@types/bun": "latest",
+ "fast-xml-parser": "^4.4.0",
"kleur": "^4.1.5",
"prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1"
@@ -25,6 +26,7 @@
},
"dependencies": {
"@astrojs/mdx": "3.1.3",
+ "@astrojs/rss": "^4.0.7",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/tailwind": "^5.1.0",
"@astrojs/ts-plugin": "1.9.0",
diff --git a/src/components/global/head.astro b/src/components/global/head.astro
index aaeb3f6..2821520 100644
--- a/src/components/global/head.astro
+++ b/src/components/global/head.astro
@@ -34,3 +34,10 @@ const { title, description, author, image } = Astro.props;
{title !== undefined ? title : "Ladybird"}
+
+
diff --git a/src/pages/posts.xml.ts b/src/pages/posts.xml.ts
new file mode 100644
index 0000000..04548d6
--- /dev/null
+++ b/src/pages/posts.xml.ts
@@ -0,0 +1,20 @@
+import rss from "@astrojs/rss";
+import { getCollection } from "astro:content";
+import type { APIContext } from "astro";
+
+export async function GET(context: APIContext) {
+ const posts = await getCollection("posts");
+ return rss({
+ title: "Ladybird Browser Posts",
+ description: "Ladybird is a brand-new browser & web engine",
+ site: context.site!,
+ items: posts.map((post) => ({
+ title: post.data.title,
+ description: post.data.description,
+ author: post.data.author,
+ pubDate: post.data.date,
+ link: `/posts/${post.slug}`,
+ })),
+ trailingSlash: false,
+ });
+}
diff --git a/tests/rss.test.ts b/tests/rss.test.ts
new file mode 100644
index 0000000..fbc7f15
--- /dev/null
+++ b/tests/rss.test.ts
@@ -0,0 +1,45 @@
+import { test, expect, describe } from "bun:test";
+import { XMLParser } from "fast-xml-parser";
+import fs from "fs";
+import path from "path";
+
+const rootDir: string = path.join(__dirname, "../");
+
+describe("RSS Feeds", () => {
+ const srcDir = path.join(rootDir, "/src/content/posts");
+ const distDir = path.join(rootDir, "/dist/");
+
+ const mdFiles = fs
+ .readdirSync(srcDir)
+ .filter((file) => file.endsWith(".mdx"));
+ const xmlFile = fs.readFileSync(path.join(distDir, "posts.xml"));
+
+ const parser = new XMLParser();
+ const parsedXML = parser.parse(xmlFile);
+
+ test("RSS Channel includes overall metadata", () => {
+ expect(parsedXML.rss.channel).toEqual(
+ expect.objectContaining({
+ title: "Ladybird Browser Posts",
+ description: "Ladybird is a brand-new browser & web engine",
+ link: "https://ladybird.org",
+ })
+ );
+ });
+
+ test("XML should contain entries for every post", async () => {
+ expect(parsedXML.rss.channel.item).toBeArrayOfSize(mdFiles.length);
+ parsedXML.rss.channel.item.forEach((item: any) => {
+ const itemAttributes = Object.keys(item);
+ expect(itemAttributes).toEqual(
+ expect.arrayContaining([
+ "title",
+ "link",
+ "description",
+ "pubDate",
+ "author",
+ ])
+ );
+ });
+ });
+});