[READ-ONLY] Mirror of https://github.com/bombshell-dev/docs. bomb.sh/docs
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: add markdown routes, llms.txt, and Vary Accept (#54)

authored by

paul valladares and committed by
GitHub
(Jun 19, 2026, 12:06 PM -0500) 219f360e 41b53814

+183 -2
+2
.gitignore
··· 4 4 # generated types 5 5 .astro/ 6 6 public/snapshot 7 + public/llms.txt 8 + public/docs-index.json 7 9 8 10 # dependencies 9 11 node_modules/
+2
astro.config.mjs
··· 3 3 import starlight from "@astrojs/starlight"; 4 4 import ecTwoSlash from "expressive-code-twoslash"; 5 5 import topics from "starlight-sidebar-topics"; 6 + import starlightMarkdown from "starlight-markdown"; 6 7 7 8 const site = "https://bomb.sh/docs/"; 8 9 ··· 87 88 { icon: 'github', label: 'GitHub', href: 'https://bomb.sh/on/github' }, 88 89 ], 89 90 plugins: [ 91 + starlightMarkdown(), 90 92 topics([ 91 93 { 92 94 label: "Clack",
+5 -2
package.json
··· 5 5 "scripts": { 6 6 "dev": "astro dev", 7 7 "start": "astro dev", 8 - "prebuild": "pnpm run snapshot", 8 + "prebuild": "pnpm run snapshot && pnpm run generate:docs-index", 9 + "predev": "pnpm run generate:docs-index", 9 10 "build": "astro build && cp public/_headers dist/_headers", 10 11 "preview": "astro preview", 11 12 "astro": "astro", 12 - "snapshot": "node --experimental-strip-types ./scripts/snapshot.ts" 13 + "snapshot": "node --experimental-strip-types ./scripts/snapshot.ts", 14 + "generate:docs-index": "node --experimental-strip-types ./scripts/generate-docs-index.ts" 13 15 }, 14 16 "dependencies": { 15 17 "@astrojs/starlight": "^0.37.1", ··· 26 28 "astro": "^5.16.6", 27 29 "expressive-code-twoslash": "^0.5.3", 28 30 "sharp": "^0.33.5", 31 + "starlight-markdown": "^0.1.5", 29 32 "starlight-sidebar-topics": "^0.6.2" 30 33 }, 31 34 "devDependencies": {
+12
pnpm-lock.yaml
··· 50 50 sharp: 51 51 specifier: ^0.33.5 52 52 version: 0.33.5 53 + starlight-markdown: 54 + specifier: ^0.1.5 55 + version: 0.1.5(astro@5.16.6(@types/node@22.19.3)(rollup@4.55.1)(typescript@5.8.2)) 53 56 starlight-sidebar-topics: 54 57 specifier: ^0.6.2 55 58 version: 0.6.2(@astrojs/starlight@0.37.1(astro@5.16.6(@types/node@22.19.3)(rollup@4.55.1)(typescript@5.8.2))) ··· 2138 2141 2139 2142 space-separated-tokens@2.0.2: 2140 2143 resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} 2144 + 2145 + starlight-markdown@0.1.5: 2146 + resolution: {integrity: sha512-23LXRaZp7pyE+r/HP6rxHfwic8HfvUBT4EImECA6encs/eTtrF0Z+7svANofdtfbiNt31D5q26i03B6FtcSmGg==} 2147 + peerDependencies: 2148 + astro: ^5.0.0 2141 2149 2142 2150 starlight-sidebar-topics@0.6.2: 2143 2151 resolution: {integrity: sha512-SNCTUZS/hcVor0ZcaXbaSVU37+V+qtvzNirkvnOg3Mqu/awuGpthkH5+uKpiZqWxLffp6TrOlsv5E5QsxrndNg==} ··· 5034 5042 source-map@0.7.6: {} 5035 5043 5036 5044 space-separated-tokens@2.0.2: {} 5045 + 5046 + starlight-markdown@0.1.5(astro@5.16.6(@types/node@22.19.3)(rollup@4.55.1)(typescript@5.8.2)): 5047 + dependencies: 5048 + astro: 5.16.6(@types/node@22.19.3)(rollup@4.55.1)(typescript@5.8.2) 5037 5049 5038 5050 starlight-sidebar-topics@0.6.2(@astrojs/starlight@0.37.1(astro@5.16.6(@types/node@22.19.3)(rollup@4.55.1)(typescript@5.8.2))): 5039 5051 dependencies:
+1
public/_headers
··· 3 3 Cross-Origin-Opener-Policy: same-origin 4 4 Cross-Origin-Resource-Policy: cross-origin 5 5 Referrer-Policy: strict-origin-when-cross-origin 6 + Vary: Accept
+161
scripts/generate-docs-index.ts
··· 1 + /** 2 + * Walks `src/content/docs` and emits `public/llms.txt` plus 3 + * `public/docs-index.json` for agent discoverability and offline search. 4 + */ 5 + import fs from 'node:fs/promises'; 6 + import path from 'node:path'; 7 + import { fileURLToPath } from 'node:url'; 8 + 9 + const rootDir = fileURLToPath(new URL('../', import.meta.url)); 10 + const docsDir = path.join(rootDir, 'src/content/docs'); 11 + const BASE_URL = 'https://bomb.sh/docs'; 12 + 13 + interface DocPage { 14 + slug: string; 15 + title: string; 16 + description: string; 17 + url: string; 18 + markdownUrl: string; 19 + template?: string; 20 + } 21 + 22 + function stripQuotes(value: string): string { 23 + if ( 24 + (value.startsWith('"') && value.endsWith('"')) || 25 + (value.startsWith("'") && value.endsWith("'")) 26 + ) { 27 + return value.slice(1, -1); 28 + } 29 + return value; 30 + } 31 + 32 + function parseFrontmatter(content: string): Record<string, string> { 33 + const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); 34 + if (!match) return {}; 35 + 36 + const result: Record<string, string> = {}; 37 + for (const line of match[1].split('\n')) { 38 + const kv = line.match(/^([\w-]+):\s*(.+)$/); 39 + if (!kv) continue; 40 + result[kv[1]] = stripQuotes(kv[2].trim()); 41 + } 42 + return result; 43 + } 44 + 45 + function filePathToSlug(relativePath: string): string { 46 + const withoutExt = relativePath.replace(/\.mdx?$/, ''); 47 + if (withoutExt === 'index') return ''; 48 + if (withoutExt.endsWith('/index')) { 49 + return withoutExt.slice(0, -'/index'.length); 50 + } 51 + return withoutExt; 52 + } 53 + 54 + function pageUrl(slug: string): string { 55 + return slug ? `${BASE_URL}/${slug}/` : `${BASE_URL}/`; 56 + } 57 + 58 + function markdownUrl(slug: string): string { 59 + return slug ? `${BASE_URL}/${slug}/index.md` : `${BASE_URL}/index.md`; 60 + } 61 + 62 + async function walkDocs(dir: string, base = ''): Promise<DocPage[]> { 63 + const pages: DocPage[] = []; 64 + const entries = await fs.readdir(dir, { withFileTypes: true }); 65 + 66 + for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { 67 + const rel = base ? `${base}/${entry.name}` : entry.name; 68 + 69 + if (entry.isDirectory()) { 70 + pages.push(...(await walkDocs(path.join(dir, entry.name), rel))); 71 + continue; 72 + } 73 + 74 + if (!entry.name.endsWith('.mdx') && !entry.name.endsWith('.md')) { 75 + continue; 76 + } 77 + 78 + const content = await fs.readFile(path.join(dir, entry.name), 'utf8'); 79 + const frontmatter = parseFrontmatter(content); 80 + const slug = filePathToSlug(rel); 81 + 82 + pages.push({ 83 + slug, 84 + title: frontmatter.title ?? (slug || 'Untitled'), 85 + description: frontmatter.description ?? '', 86 + url: pageUrl(slug), 87 + markdownUrl: markdownUrl(slug), 88 + template: frontmatter.template, 89 + }); 90 + } 91 + 92 + return pages; 93 + } 94 + 95 + function isIndexed(page: DocPage): boolean { 96 + if (page.slug === '404') return false; 97 + if (page.template === 'splash' && page.slug !== '') return false; 98 + return true; 99 + } 100 + 101 + function generateLlmsTxt(pages: DocPage[]): string { 102 + const indexed = pages.filter(isIndexed); 103 + const lines = [ 104 + '# Bombshell Documentation', 105 + '', 106 + '> Effortlessly build beautiful command-line apps. Docs for Clack, Args, and Tab.', 107 + '', 108 + `Canonical docs: ${BASE_URL}/`, 109 + '', 110 + ]; 111 + 112 + const homepage = indexed.find((page) => page.slug === ''); 113 + if (homepage) { 114 + lines.push(`- [${homepage.title}](${homepage.url}): ${homepage.description}`, ''); 115 + } 116 + 117 + const sections = new Map<string, DocPage[]>(); 118 + for (const page of indexed) { 119 + if (page.slug === '') continue; 120 + const section = page.slug.split('/')[0]; 121 + if (!sections.has(section)) sections.set(section, []); 122 + sections.get(section)!.push(page); 123 + } 124 + 125 + for (const [section, sectionPages] of [...sections.entries()].sort()) { 126 + const label = section.charAt(0).toUpperCase() + section.slice(1); 127 + lines.push(`## ${label}`, ''); 128 + for (const page of sectionPages.sort((a, b) => a.slug.localeCompare(b.slug))) { 129 + lines.push(`- [${page.title}](${page.url}): ${page.description}`); 130 + } 131 + lines.push(''); 132 + } 133 + 134 + return `${lines.join('\n').trimEnd()}\n`; 135 + } 136 + 137 + async function main() { 138 + const pages = await walkDocs(docsDir); 139 + const indexed = pages.filter(isIndexed); 140 + 141 + await fs.writeFile( 142 + path.join(rootDir, 'public/docs-index.json'), 143 + `${JSON.stringify( 144 + { 145 + generatedAt: new Date().toISOString(), 146 + baseUrl: BASE_URL, 147 + pages: indexed, 148 + }, 149 + null, 150 + 2, 151 + )}\n`, 152 + ); 153 + await fs.writeFile(path.join(rootDir, 'public/llms.txt'), generateLlmsTxt(pages)); 154 + 155 + console.log(`Generated docs index with ${indexed.length} pages`); 156 + } 157 + 158 + main().catch((error) => { 159 + console.error(error); 160 + process.exit(1); 161 + });