1- import { promises as fs } from 'node:fs ' ;
2- import { join } from 'node:path ' ;
1+ import type { CollectionEntry } from 'astro:content ' ;
2+ import { getCollection } from 'astro:content ' ;
33import type { APIRoute } from 'astro' ;
4+ import { sidebarConfig } from '@/lib/sidebar' ;
5+ import { cleanMdxContent } from '@/lib/utils' ;
46
57export const prerender = true ;
68
7- // Use process.cwd() which is the project root during Astro build
8- const docsDir = join ( process . cwd ( ) , 'src/content/docs' ) ;
9-
10- // Document order matching the sidebar structure
11- const docOrder = [
12- // Getting Started
13- 'index.mdx' ,
14- 'quickstart.mdx' ,
15- 'working-with-sprites.mdx' ,
16- // Concepts
17- 'concepts/lifecycle.mdx' ,
18- 'concepts/services.mdx' ,
19- 'concepts/networking.mdx' ,
20- 'concepts/checkpoints.mdx' ,
21- // CLI
22- 'cli/installation.mdx' ,
23- 'cli/authentication.mdx' ,
24- 'cli/commands.mdx' ,
25- // SDKs
26- 'sdks/javascript.mdx' ,
27- 'sdks/go.mdx' ,
28- // API (generated)
29- 'api/index.mdx' ,
30- 'api/exec.mdx' ,
31- 'api/checkpoints.mdx' ,
32- 'api/services.mdx' ,
33- 'api/proxy.mdx' ,
34- 'api/policy.mdx' ,
35- 'api/types.mdx' ,
36- // Reference
37- 'reference/base-images.mdx' ,
38- 'reference/configuration.mdx' ,
39- 'reference/billing.mdx' ,
40- ] ;
41-
42- // Section headers for organization
43- const sections : Record < string , string > = {
44- 'index.mdx' : '# Getting Started' ,
45- 'concepts/lifecycle.mdx' : '# Concepts' ,
46- 'cli/installation.mdx' : '# CLI' ,
47- 'sdks/javascript.mdx' : '# SDKs' ,
48- 'api/index.mdx' : '# API' ,
49- 'reference/base-images.mdx' : '# Reference' ,
9+ export type DocsGroup = {
10+ label : string ;
11+ items : {
12+ slug : string ;
13+ body : string ;
14+ title : string ;
15+ description ?: string ;
16+ } [ ] ;
5017} ;
5118
52- interface DocMeta {
53- title : string ;
54- description ?: string ;
55- }
56-
57- function extractFrontmatter ( content : string ) : { meta : DocMeta ; body : string } {
58- const frontmatterMatch = content . match ( / ^ - - - \n ( [ \s \S ] * ?) \n - - - \n ( [ \s \S ] * ) $ / ) ;
59-
60- if ( ! frontmatterMatch ) {
61- return { meta : { title : 'Untitled' } , body : content } ;
62- }
63-
64- const [ , frontmatterStr , body ] = frontmatterMatch ;
65- const meta : DocMeta = { title : 'Untitled' } ;
19+ export async function getGroupedDocs ( ) : Promise < DocsGroup [ ] > {
20+ const collection = await getCollection ( 'docs' , ( { data } ) => {
21+ return data . draft !== true ;
22+ } ) ;
6623
67- // Parse YAML-like frontmatter (simple key: value parsing)
68- for ( const line of frontmatterStr . split ( '\n' ) ) {
69- const titleMatch = line . match ( / ^ t i t l e : \s * ( .+ ) $ / ) ;
70- if ( titleMatch ) {
71- meta . title = titleMatch [ 1 ] . replace ( / ^ [ " ' ] | [ " ' ] $ / g, '' ) ;
72- }
73- const descMatch = line . match ( / ^ d e s c r i p t i o n : \s * ( .+ ) $ / ) ;
74- if ( descMatch ) {
75- meta . description = descMatch [ 1 ] . replace ( / ^ [ " ' ] | [ " ' ] $ / g, '' ) ;
76- }
24+ const atlas = new Map < string , CollectionEntry < 'docs' > > ( ) ;
25+ for ( const doc of collection ) {
26+ atlas . set ( doc . id , doc ) ;
7727 }
7828
79- return { meta, body } ;
80- }
81-
82- function cleanMdxContent ( content : string ) : string {
83- // Remove MDX import statements at the start of the file (before any content)
84- // This preserves imports inside code blocks
85- content = content . replace (
86- / ^ ( \s * i m p o r t \s + .* ?(?: f r o m \s + [ ' " ] .* ?[ ' " ] ) ? ; ? \s * \n ) + / m,
87- '' ,
88- ) ;
89-
90- // Process Tabs components - extract TabItem contents
91- // Need to handle nested content carefully (code blocks with special chars)
92- content = content . replace ( / < T a b s > [ \s \S ] * ?< \/ T a b s > / g, ( match ) => {
93- const results : string [ ] = [ ] ;
94-
95- // Split by TabItem boundaries and extract content
96- const tabItemRegex =
97- / < T a b I t e m [ ^ > ] * l a b e l = " ( [ ^ " ] * ) " [ ^ > ] * > ( [ \s \S ] * ?) (? = < T a b I t e m | < \/ T a b s > ) / g;
98-
99- for ( const [ , label , tabContent ] of match . matchAll ( tabItemRegex ) ) {
100- // Clean up the content - remove closing </TabItem> if present
101- const cleanContent = tabContent . replace ( / < \/ T a b I t e m > \s * $ / , '' ) . trim ( ) ;
102- if ( cleanContent ) {
103- results . push ( `**${ label } :**\n${ cleanContent } ` ) ;
29+ const groups = [ ] ;
30+ for ( const { label, items : sidebarItems } of sidebarConfig ) {
31+ const items = [ ] ;
32+ for ( const sidebarItem of sidebarItems ) {
33+ if (
34+ typeof sidebarItem === 'object' &&
35+ sidebarItem != null &&
36+ 'slug' in sidebarItem
37+ ) {
38+ const doc = atlas . get ( sidebarItem . slug ) ;
39+ if ( doc != null && doc . body != null ) {
40+ items . push ( {
41+ slug : doc . id ,
42+ body : doc . body ,
43+ title : doc . data . title ,
44+ description : doc . data . description ,
45+ } ) ;
46+ } else {
47+ console . warn ( `Warning: Could not find ${ sidebarItem . label } :` ) ;
48+ }
10449 }
10550 }
51+ groups . push ( { label, items } ) ;
52+ }
10653
107- return results . length > 0 ? results . join ( '\n\n' ) : '' ;
108- } ) ;
109-
110- // Remove self-closing JSX/MDX components (like <Callout ... />)
111- content = content . replace ( / < [ A - Z ] [ a - z A - Z ] * \s + [ ^ > ] * \/ > / g, '' ) ;
112-
113- // Remove JSX components with content (non-greedy, for simple components)
114- // Handle Callout, Snippet, and other simple wrapper components
115- content = content . replace (
116- / < C a l l o u t [ ^ > ] * > ( [ \s \S ] * ?) < \/ C a l l o u t > / g,
117- ( _ , inner ) => {
118- // Keep the content, just remove the wrapper
119- return inner . trim ( ) ;
120- } ,
121- ) ;
122-
123- // Remove remaining JSX component tags (opening and closing)
124- content = content . replace ( / < [ A - Z ] [ a - z A - Z ] * [ ^ > ] * > / g, '' ) ;
125- content = content . replace ( / < \/ [ A - Z ] [ a - z A - Z ] * > / g, '' ) ;
126-
127- // Convert relative links to fully qualified URLs
128- // Matches markdown links like [text](/path) or [text](/path/)
129- content = content . replace (
130- / \[ ( [ ^ \] ] + ) \] \( \/ ( [ ^ ) ] * ) \) / g,
131- ( _ , text , path ) => `[${ text } ](https://docs.sprites.dev/${ path } )` ,
132- ) ;
133-
134- // Clean up excessive blank lines
135- content = content . replace ( / \n { 4 , } / g, '\n\n\n' ) ;
136-
137- // Trim leading/trailing whitespace
138- content = content . trim ( ) ;
139-
140- return content ;
141- }
142-
143- function slugToUrl ( slug : string ) : string {
144- const path = slug . replace ( / \. m d x $ / , '' ) . replace ( / ^ i n d e x $ / , '' ) ;
145- return `https://docs.sprites.dev/${ path } ${ path ? '/' : '' } ` ;
54+ return groups ;
14655}
14756
14857export const GET : APIRoute = async ( ) => {
@@ -160,34 +69,21 @@ Summary: https://docs.sprites.dev/llms.txt
16069---
16170` ) ;
16271
163- let currentSection = '' ;
164-
165- for ( const docPath of docOrder ) {
166- const fullPath = join ( docsDir , docPath ) ;
167-
168- try {
169- const content = await fs . readFile ( fullPath , 'utf-8' ) ;
170- const { meta, body } = extractFrontmatter ( content ) ;
72+ const groups = await getGroupedDocs ( ) ;
73+ for ( const { label, items } of groups ) {
74+ parts . push ( `\n# ${ label } \n` ) ;
75+ for ( const { slug, title, description, body } of items ) {
17176 const cleanedContent = cleanMdxContent ( body ) ;
17277
173- // Add section header if we're entering a new section
174- if ( sections [ docPath ] && sections [ docPath ] !== currentSection ) {
175- currentSection = sections [ docPath ] ;
176- parts . push ( `\n${ currentSection } \n` ) ;
177- }
178-
17978 // Add document with title and URL
180- const url = slugToUrl ( docPath ) ;
181- parts . push ( `## ${ meta . title }
79+ parts . push ( `## ${ title }
18280
183- URL: ${ url }
184- ${ meta . description ? `\n${ meta . description } \n` : '' }
81+ URL: https://docs.sprites.dev/ ${ slug } .md
82+ ${ description ? `\n${ description } \n` : '' }
18583${ cleanedContent }
18684
18785---
18886` ) ;
189- } catch ( error ) {
190- console . warn ( `Warning: Could not read ${ docPath } :` , error ) ;
19187 }
19288 }
19389
0 commit comments