feat: add llms.txt and rename raw spec URL to match filename

- Add /llms.txt endpoint following llmstxt.org specification format
- Change raw markdown URL from /spec/{version}/raw.md to
  /spec/git-common-flow-v{version}.md to match download filename
- Add <link rel="alternate"> for raw markdown in spec pages
- Add Raw button to markdown view page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 18:04:56 +00:00
parent 3243639e3c
commit ee0c0171a5
5 changed files with 100 additions and 9 deletions

View File

@@ -11,7 +11,9 @@ interface Props {
}
const { title, description = config.description } = Astro.props;
const fullTitle = title.includes(config.title) ? title : `${title} | ${config.title}`;
const fullTitle = title.includes(config.title)
? title
: `${title} | ${config.title}`;
const canonicalUrl = Astro.url.href;
---
@@ -42,12 +44,15 @@ const canonicalUrl = Astro.url.href;
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Slot for page-specific head content -->
<slot name="head" />
<!-- Prevent flash of wrong theme -->
<script is:inline>
(function () {
const mode = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
"(prefers-color-scheme: dark)"
).matches;
if (mode === "dark" || (mode !== "light" && prefersDark)) {
document.documentElement.classList.add("dark");
@@ -63,7 +68,7 @@ const canonicalUrl = Astro.url.href;
document.addEventListener("astro:after-swap", () => {
const mode = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
"(prefers-color-scheme: dark)"
).matches;
if (mode === "dark" || (mode !== "light" && prefersDark)) {
document.documentElement.classList.add("dark");

View File

@@ -31,7 +31,11 @@ const markdown = content.replace(/^---[\s\S]*?---\n/, "");
const parsed = await parseSpecContent(markdown);
// Read SVG content for inline embedding
const svgFilePath = path.join(process.cwd(), "src/content/spec", `${version}.svg`);
const svgFilePath = path.join(
process.cwd(),
"src/content/spec",
`${version}.svg`
);
let svgContent: string | null = null;
if (fs.existsSync(svgFilePath)) {
svgContent = fs.readFileSync(svgFilePath, "utf-8");
@@ -39,14 +43,19 @@ if (fs.existsSync(svgFilePath)) {
---
<BaseLayout title={spec.data.title}>
<Fragment slot="head">
<link
rel="alternate"
type="text/markdown"
href={`/spec/git-common-flow-v${version}.md`}
title="Raw Markdown"
/>
</Fragment>
<Header version={version} versions={versions} />
<main>
<Hero
version={version}
versions={versions}
svgContent={svgContent}
/>
<Hero version={version} versions={versions} svgContent={svgContent} />
<AboutSection
introduction={parsed.introduction}

25
src/pages/llms.txt.ts Normal file
View File

@@ -0,0 +1,25 @@
import type { APIRoute } from "astro";
import { getVersionInfo } from "../utils/versions";
export const GET: APIRoute = async () => {
const { currentVersion } = await getVersionInfo();
const content = `# Common-Flow
> A Git workflow specification combining GitHub Flow with versioned releases.
Common-Flow is a sensible git workflow based on GitHub Flow, with the addition
of versioned releases, optional release branches, and without the requirement
to deploy to production all the time.
## Docs
- [Git Common-Flow Specification](/spec/git-common-flow-v${currentVersion}.md): The complete Git Common-Flow v${currentVersion} specification in Markdown format
`;
return new Response(content, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
},
});
};

View File

@@ -116,6 +116,15 @@ const previewHtml = await unified()
---
<BaseLayout title={`${spec.data.title} - Markdown`}>
<Fragment slot="head">
<link
rel="alternate"
type="text/markdown"
href={`/spec/git-common-flow-v${version}.md`}
title="Raw Markdown"
/>
</Fragment>
<!-- Header -->
<header
class="sticky top-0 z-50 border-b
@@ -227,6 +236,19 @@ const previewHtml = await unified()
</div>
</div>
<!-- Raw button -->
<a
href={`/spec/git-common-flow-v${version}.md`}
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
font-medium rounded-lg transition-colors
text-gray-600 dark:text-neutral-400
hover:bg-gray-100 hover:text-gray-950
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
>
<Icon name="heroicons:document-text" class="w-4 h-4" />
<span>Raw</span>
</a>
<!-- Download button -->
<button
id="download-btn"

View File

@@ -0,0 +1,30 @@
import type { APIRoute, GetStaticPaths } from "astro";
import { getCollection } from "astro:content";
import * as fs from "node:fs";
import * as path from "node:path";
export const getStaticPaths: GetStaticPaths = async () => {
const specs = await getCollection("spec");
return specs.map((spec) => ({
params: { version: spec.data.version },
}));
};
export const GET: APIRoute = ({ params }) => {
const version = params.version;
const filePath = path.join(
process.cwd(),
"src/content/spec",
`${version}.md`
);
const content = fs.readFileSync(filePath, "utf-8");
const markdown = content.replace(/^---[\s\S]*?---\n/, "");
const filename = `git-common-flow-v${version}.md`;
return new Response(markdown, {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
"Content-Disposition": `inline; filename="${filename}"`,
},
});
};