Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/install/release-notes/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ description: |-
- [1.1.1](/docs/install/release-notes/1-1-1) – Released on February 13, 2025
- [1.1.0](/docs/install/release-notes/1-1-0) – Released on January 30, 2025
- [1.0.1](/docs/install/release-notes/1-0-1) – Released on December 31, 2024

You can also subscribe to the [JSON feed](/docs/install/release-notes/feed.json).
85 changes: 85 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@r4ai/remark-callout": "^0.6.2",
"classnames": "^2.5.1",
"feed": "^5.1.0",
"gray-matter": "^4.0.3",
"klaw-sync": "^6.0.0",
"lucide-react": "^0.424.0",
Expand All @@ -22,6 +23,7 @@
"react-dom": "^18",
"react-intersection-observer": "^9.14.0",
"rehype-highlight": "^7.0.1",
"rehype-stringify": "^10.0.1",
"remark-gfm": "^4.0.0",
"slugify": "^1.6.6",
"xml2js": "^0.6.2",
Expand Down
12 changes: 12 additions & 0 deletions src/app/docs/install/release-notes/feed.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NextResponse } from "next/server";
import { generateFeed } from "@/lib/generate-feed";

export async function GET() {
const feed = await generateFeed();

return new NextResponse(feed, {
headers: {
"Content-Type": "application/feed+json",
},
});
}
6 changes: 6 additions & 0 deletions src/layouts/root-layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export default function RootLayout({
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="3200" />

<link
rel="alternate"
type="application/feed+json"
title="Ghostty Release Notes"
href="/docs/install/release-notes/feed.json"
/>
<link
rel="icon"
type="image/png"
Expand Down
75 changes: 75 additions & 0 deletions src/lib/generate-feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { promises as fs } from "fs";
import path from "path";
import matter from "gray-matter";
import { Feed } from "feed";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";

const BASE_URL = "https://ghostty.org";
const RELEASE_NOTES_DIRECTORY = "./docs/install/release-notes/";
const FEED_FILENAME = "feed.json";

async function mdToHtml(md: string): Promise<string> {
const result = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(md);

return result.toString();
}

export async function generateFeed(): Promise<string> {
const releaseNotesUrl = new URL(RELEASE_NOTES_DIRECTORY, BASE_URL).href;
const feedUrl = new URL(FEED_FILENAME, releaseNotesUrl).href;
const currentYear = new Date().getFullYear();

const feed = new Feed({
title: "Ghostty Release Notes",
description: "Release notes for Ghostty",
id: feedUrl,
link: releaseNotesUrl,
feedLinks: {
json: feedUrl,
},
favicon: new URL("favicon.ico", BASE_URL).href,
copyright: `© ${currentYear} Mitchell Hashimoto`,
});

const releaseNotesDir = path.join(process.cwd(), RELEASE_NOTES_DIRECTORY);
const filenames = (await fs.readdir(releaseNotesDir, { withFileTypes: true }))
.filter((dirent) =>
dirent.isFile() && dirent.name !== "index.mdx" &&
dirent.name.endsWith(".mdx")
)
.map((dirent) => dirent.name)
.toReversed();

for (const filename of filenames) {
const filePath = path.join(RELEASE_NOTES_DIRECTORY, filename);

const { data, content } = matter.read(filePath);
const contentHtml = await mdToHtml(content);

const slug = filename.replace(".mdx", "");

const fileUrl = new URL(slug, releaseNotesUrl).href;
const dateString = data.description?.match(
/released on ([A-Za-z]+ \d+, \d{4})/i,
)?.[1];
const date = dateString ? new Date(dateString) : new Date();

feed.addItem({
title: data.title,
id: fileUrl,
link: fileUrl,
description: data.description,
content: contentHtml,
date,
});
}

return feed.json1();
}