Skip to content
Merged
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
16 changes: 12 additions & 4 deletions website/lib/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,12 @@ export function getDocsConfig(): DocsProduct[] {
return docsConfig as unknown as DocsProduct[];
}

export function getAllDocPages(): DocPage[] {
if (_globalCache.__docPagesCache) return _globalCache.__docPagesCache;
export function getAllDocPages(options?: {
skipGitMetadata?: boolean;
}): DocPage[] {
if (!options?.skipGitMetadata && _globalCache.__docPagesCache) {
return _globalCache.__docPagesCache;
}

const files = getFilesRecursively(DOCS_DIR, ".md");
const pages: DocPage[] = [];
Expand All @@ -112,7 +116,9 @@ export function getAllDocPages(): DocPage[] {
version = parts[1];
}

const gitMeta = getGitMetadata(file);
const gitMeta = options?.skipGitMetadata
? { lastUpdated: "", lastAuthorName: "" }
: getGitMetadata(file);

pages.push({
slug,
Expand All @@ -126,7 +132,9 @@ export function getAllDocPages(): DocPage[] {
});
}

_globalCache.__docPagesCache = pages;
if (!options?.skipGitMetadata) {
_globalCache.__docPagesCache = pages;
}
return pages;
}

Expand Down
151 changes: 151 additions & 0 deletions website/lib/llms-txt-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import {
getDocsConfig,
getAllDocPages,
type DocsProduct,
type DocsNavItem,
} from "./docs";
import { getAllBlogPosts } from "./blog";

const SITE_URL = "https://chillicream.com";

function buildDocUrl(
product: DocsProduct,
versionPath: string,
itemPath: string
): string {
const parts = ["/docs", product.path];
if (versionPath) {
parts.push(versionPath);
}
if (itemPath && itemPath !== "index") {
parts.push(itemPath);
}
return parts.join("/");
}

function flattenNavItems(
product: DocsProduct,
versionPath: string,
items: DocsNavItem[],
parentPath = ""
): { url: string; title: string }[] {
const result: { url: string; title: string }[] = [];
for (const item of items) {
const itemPath = parentPath ? `${parentPath}/${item.path}` : item.path;
// Skip nested "index" sub-items — the parent already covers this URL
if (!(parentPath && item.path === "index")) {
result.push({
url: buildDocUrl(product, versionPath, itemPath),
title: item.title,
});
}
if (item.items) {
result.push(
...flattenNavItems(product, versionPath, item.items, itemPath)
);
}
}
return result;
}

function getLatestVersion(product: DocsProduct) {
if (product.latestStableVersion) {
return product.versions.find((v) => v.path === product.latestStableVersion);
}
return product.versions[0];
}

const PRODUCT_DESCRIPTIONS: Record<string, string> = {
hotchocolate: "GraphQL server framework for .NET",
strawberryshake: "Type-safe GraphQL client for .NET",
fusion: "Distributed GraphQL gateway",
nitro: "GraphQL IDE and API management",
};

export function generateLlmsTxt(): string {
const products = getDocsConfig();
const blogPosts = getAllBlogPosts();
const allPages = getAllDocPages({ skipGitMetadata: true });
const validSlugs = new Set(allPages.map((p) => p.slug));

const lines: string[] = [
"# ChilliCream GraphQL Platform",
"",
"> The ChilliCream GraphQL Platform is an open-source ecosystem for building GraphQL APIs in .NET, including Hot Chocolate (server), Strawberry Shake (client), Green Donut (DataLoader), Fusion (distributed GraphQL), and Nitro (GraphQL IDE).",
"",
`For complete documentation in a single file, see [Full Documentation](${SITE_URL}/llms-full.txt)`,
"",
"## Documentation",
"",
];

// Product overview links
for (const product of products) {
const version = getLatestVersion(product);
const versionPath = version?.path ?? "";
const url = buildDocUrl(product, versionPath, "index");
const desc = PRODUCT_DESCRIPTIONS[product.path] || product.description;
lines.push(`- [${product.title}](${url}): ${desc}`);
}
lines.push("");

// Per-product nav items
for (const product of products) {
const version = getLatestVersion(product);
if (!version) continue;

lines.push(`### ${product.title}`, "");
const navItems = flattenNavItems(product, version.path, version.items);
for (const item of navItems) {
if (!validSlugs.has(item.url)) continue;
lines.push(`- [${item.title}](${item.url})`);
}
lines.push("");
Comment thread
PascalSenn marked this conversation as resolved.
}

// Blog
lines.push("## Blog", "");
const recentPosts = blogPosts.slice(0, 20);
for (const post of recentPosts) {
const desc = post.description ? `: ${post.description}` : "";
lines.push(`- [${post.title}](${post.path})${desc}`);
}
lines.push("");

return lines.join("\n");
}

export function generateLlmsFullTxt(): string {
const products = getDocsConfig();
const allPages = getAllDocPages({ skipGitMetadata: true });

const lines: string[] = [
"# ChilliCream GraphQL Platform - Full Documentation",
"",
"> The ChilliCream GraphQL Platform is an open-source ecosystem for building GraphQL APIs in .NET, including Hot Chocolate (server), Strawberry Shake (client), Green Donut (DataLoader), Fusion (distributed GraphQL), and Nitro (GraphQL IDE).",
"",
];

const pagesBySlug = new Map(allPages.map((p) => [p.slug, p]));

for (const product of products) {
const version = getLatestVersion(product);
if (!version) continue;

const versionPath = version.path;
const navItems = flattenNavItems(product, versionPath, version.items);

for (const navItem of navItems) {
const page = pagesBySlug.get(navItem.url);
if (!page || !page.content.trim()) continue;
Comment thread
PascalSenn marked this conversation as resolved.

lines.push(`## ${navItem.url}`, "");
const title = page.frontmatter?.title || navItem.title;
lines.push(`# ${title}`, "");
lines.push(page.content.trim(), "");
lines.push("---", "");
}
}

return lines.join("\n");
}
2 changes: 1 addition & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"scripts": {
"dev": "next dev --hostname 0.0.0.0 --port 3000",
"build": "next build",
"build": "next build && npx tsx scripts/generate-llms-txt.ts",
"optimize-images": "npx tsx -e \"import { optimizeDirectory } from './lib/image-optimization'; (async () => { await optimizeDirectory(['./public/images', './public/docs']); })();\"",
"start": "next start",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
Expand Down
30 changes: 30 additions & 0 deletions website/scripts/generate-llms-txt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import fs from "fs";
import path from "path";
import {
generateLlmsTxt,
generateLlmsFullTxt,
} from "../lib/llms-txt-generator";

const outDir = path.resolve(__dirname, "../out");

if (!fs.existsSync(outDir)) {
console.error(`Output directory not found: ${outDir}`);
console.error("Run 'next build' first to generate the output directory.");
process.exit(1);
}

console.log("Generating llms.txt...");
const llmsTxt = generateLlmsTxt();
fs.writeFileSync(path.join(outDir, "llms.txt"), llmsTxt, "utf-8");
console.log(
` Written: out/llms.txt (${(llmsTxt.length / 1024).toFixed(1)} KB)`
);

console.log("Generating llms-full.txt...");
const llmsFullTxt = generateLlmsFullTxt();
fs.writeFileSync(path.join(outDir, "llms-full.txt"), llmsFullTxt, "utf-8");
console.log(
` Written: out/llms-full.txt (${(llmsFullTxt.length / 1024).toFixed(1)} KB)`
);

console.log("Done.");