@@ -7,29 +7,22 @@ import fs from "fs/promises";
77import { globSync } from "glob" ;
88import path from "path" ;
99
10- import docsSidebarConfig from "../sidebars.js " ;
10+ import versionsJson from "../versions.json " ;
1111
1212const SITE_ROOT = process . cwd ( ) ;
1313const STATIC_DIR = path . join ( SITE_ROOT , "static/" ) ;
14- const DOCS_DIR = path . join ( SITE_ROOT , "docs/" ) ;
14+ const VERSIONED_DOCS_DIR = path . join ( SITE_ROOT , "versioned_docs/" ) ;
15+ const VERSIONED_SIDEBARS_DIR = path . join ( SITE_ROOT , "versioned_sidebars/" ) ;
1516const BLOG_DIR = path . join ( SITE_ROOT , "blog/" ) ;
1617const GITHUB_RAW_BASE_URL =
1718 "https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/release/web/" ; // Use the release branch
1819const WASP_BASE_URL = "https://wasp.sh/" ;
19- const LLM_FULL_FILENAME = "llms-full.txt" ;
20- const LLM_OVERVIEW_FILENAME = "llms.txt" ;
21- const LLMS_TXT_FILE_PATH = path . join ( STATIC_DIR , LLM_OVERVIEW_FILENAME ) ;
22- const LLMS_FULL_TXT_FILE_PATH = path . join ( STATIC_DIR , LLM_FULL_FILENAME ) ;
2320const CATEGORIES_TO_IGNORE = [ "Miscellaneous" ] ;
2421
25- const OVERVIEW_INTRO_CONTENT = `
26- # Wasp
27- Wasp is a full-stack framework with batteries included for React, Node.js, and Prisma.
22+ const LLMS_TXT_INTRO = `# Wasp
23+ Wasp is a full-stack framework with batteries included for React, Node.js, and Prisma.` ;
2824
29- ## Individual documentation sections and guides:
30- ` ;
31- const OVERVIEW_MISC_SECTION_CONTENT = `
32- ## Miscellaneous
25+ const LLMS_TXT_MISC = `## Miscellaneous
3326- [Wasp Developer Discord](https://discord.com/invite/rzdnErX)
3427- [Open SaaS -- Wasp's free, open-source SaaS boilerplate starter](https://opensaas.sh)
3528` ;
@@ -38,31 +31,68 @@ generateFiles();
3831
3932/**
4033 * Main function to generate the LLM-friendly doc files.
41- * It orchestrates the process by fetching and processing documentation and blog files,
42- * and then writing the final output to the static directory .
34+ * Loops through all versioned docs and generates llms.txt files for each.
35+ * The latest version also gets llms-full.txt generated .
4336 */
4437async function generateFiles ( ) {
4538 console . log ( "Starting LLM file generation..." ) ;
4639
47- if ( ! Array . isArray ( docsSidebarConfig . docs ) ) {
48- throw new Error (
49- "Sidebar configuration for docs is not an array. Please check the sidebars.ts file." ,
50- ) ;
40+ const blogSectionContent = await processBlogFiles ( ) ;
41+ const latestVersion = versionsJson [ 0 ] ;
42+
43+ for ( const version of versionsJson ) {
44+ const isLatest = version === latestVersion ;
45+ console . log ( `Processing version ${ version } ...` ) ;
46+
47+ const docsDir = path . join ( VERSIONED_DOCS_DIR , `version-${ version } ` ) ;
48+ const sidebarItems = await loadVersionedSidebar ( version ) ;
49+
50+ const { overviewDocsSection, llmsFullTxtContent } =
51+ await processDocumentationFiles ( sidebarItems , docsDir , version , {
52+ generateLlmsFullTxt : isLatest ,
53+ } ) ;
54+
55+ const filename = isLatest ? "llms.txt" : `llms-${ version } .txt` ;
56+ await writeLlmsTxtFile ( filename , overviewDocsSection , blogSectionContent ) ;
57+
58+ if ( isLatest ) {
59+ await writeLlmsFullTxtFile ( llmsFullTxtContent ) ;
60+ }
5161 }
5262
53- const { overviewDocsSection, fullConcatContent } =
54- await processDocumentationFiles ( docsSidebarConfig . docs ) ;
55- const blogSectionContent = await processBlogFiles ( ) ;
63+ console . log ( "🎉 LLM files generation completed successfully." ) ;
64+ }
5665
57- const llmsTxtContent =
58- OVERVIEW_INTRO_CONTENT +
59- overviewDocsSection +
60- blogSectionContent +
61- OVERVIEW_MISC_SECTION_CONTENT ;
62- const llmsFullTxtContent = fullConcatContent ;
66+ async function writeLlmsTxtFile (
67+ filename : string ,
68+ overviewDocsSection : string ,
69+ blogSectionContent : string ,
70+ ) : Promise < void > {
71+ const content = buildLlmsTxtContent ( overviewDocsSection , blogSectionContent ) ;
72+ const outputPath = path . join ( STATIC_DIR , filename ) ;
73+ await fs . writeFile ( outputPath , content , "utf8" ) ;
74+ console . log ( ` Generated: ${ filename } ` ) ;
75+ }
76+
77+ async function writeLlmsFullTxtFile ( content : string ) : Promise < void > {
78+ const outputPath = path . join ( STATIC_DIR , "llms-full.txt" ) ;
79+ await fs . writeFile ( outputPath , content . trim ( ) , "utf8" ) ;
80+ console . log ( ` Generated: llms-full.txt` ) ;
81+ }
6382
64- await writeOutputFiles ( llmsTxtContent , llmsFullTxtContent ) ;
65- console . log ( "🎉 LLM file generation complete." ) ;
83+ /**
84+ * Assembles the overview content for llms.txt from its component sections.
85+ */
86+ function buildLlmsTxtContent (
87+ overviewDocsSection : string ,
88+ blogSectionContent : string ,
89+ ) : string {
90+ return [
91+ LLMS_TXT_INTRO ,
92+ overviewDocsSection ,
93+ blogSectionContent ,
94+ LLMS_TXT_MISC ,
95+ ] . join ( "\n\n" ) ;
6696}
6797
6898/**
@@ -72,9 +102,12 @@ async function generateFiles() {
72102 */
73103async function processDocumentationFiles (
74104 docsSidebarItems : SidebarItemConfig [ ] ,
75- ) : Promise < { overviewDocsSection : string ; fullConcatContent : string } > {
76- let overviewDocsSection = "" ;
77- let fullConcatContent = "" ;
105+ docsDir : string ,
106+ version : string ,
107+ { generateLlmsFullTxt = false } = { } ,
108+ ) : Promise < { overviewDocsSection : string ; llmsFullTxtContent : string } > {
109+ let overviewDocsSection = `## Documentation Raw Text URLs -- Version ${ version } :\n` ;
110+ let llmsFullTxtContent = "" ;
78111
79112 const orderedDocIds = flattenSidebarItemsToDocIds ( docsSidebarItems ) ;
80113 console . log (
@@ -84,9 +117,13 @@ async function processDocumentationFiles(
84117 const sidebarOverviewStructure =
85118 getDocsSidebarCategoryStructure ( docsSidebarItems ) ;
86119
87- const docIdToPathMap = buildDocIdToPathMap ( DOCS_DIR ) ;
120+ const docIdToPathMap = buildDocIdToPathMap ( docsDir ) ;
88121
89- const docInfoMap = await populateDocInfoMap ( orderedDocIds , docIdToPathMap ) ;
122+ const docInfoMap = await populateDocInfoMap (
123+ orderedDocIds ,
124+ docIdToPathMap ,
125+ docsDir ,
126+ ) ;
90127
91128 for ( const category of sidebarOverviewStructure ) {
92129 overviewDocsSection += `${ category . categoryLabel } \n` ;
@@ -104,23 +141,24 @@ async function processDocumentationFiles(
104141 }
105142 }
106143
107- // Build fullConcatContent using sidebar structure with proper heading hierarchy
108- for ( const category of sidebarOverviewStructure ) {
109- // Add category header as H1 and separator
110- fullConcatContent += `# ${ category . categoryLabel } \n\n` ;
111-
112- for ( const docId of category . docIds ) {
113- if ( docInfoMap . has ( docId ) ) {
114- const info = docInfoMap . get ( docId ) ;
115- // Add document title as H2
116- fullConcatContent += `## ${ info . title } \n\n${ info . processedBody } \n\n` ;
144+ if ( generateLlmsFullTxt ) {
145+ for ( const category of sidebarOverviewStructure ) {
146+ // Add category header as H1 and separator
147+ llmsFullTxtContent += `# ${ category . categoryLabel } \n\n` ;
148+
149+ for ( const docId of category . docIds ) {
150+ if ( docInfoMap . has ( docId ) ) {
151+ const info = docInfoMap . get ( docId ) ;
152+ // Add document title as H2
153+ llmsFullTxtContent += `## ${ info . title } \n\n${ info . processedBody } \n\n` ;
154+ }
117155 }
118- }
119156
120- // Add category separator
121- fullConcatContent += `------\n\n` ;
157+ // Add category separator
158+ llmsFullTxtContent += `------\n\n` ;
159+ }
122160 }
123- return { overviewDocsSection, fullConcatContent } ;
161+ return { overviewDocsSection, llmsFullTxtContent } ;
124162}
125163
126164/**
@@ -162,13 +200,14 @@ type DocDetails = {
162200async function populateDocInfoMap (
163201 orderedDocIds : string [ ] ,
164202 docIdToPathMap : Map < string , string > ,
203+ docsDir : string ,
165204) : Promise < Map < string , DocDetails > > {
166205 const docInfoMap = new Map < string , DocDetails > ( ) ;
167206 for ( const docId of orderedDocIds ) {
168207 const relativeDocPath = docIdToPathMap . get ( docId ) ;
169208
170209 if ( relativeDocPath ) {
171- const absolutePath = path . join ( DOCS_DIR , relativeDocPath ) ;
210+ const absolutePath = path . join ( docsDir , relativeDocPath ) ;
172211 try {
173212 const rawContent = await fs . readFile ( absolutePath , "utf8" ) ;
174213 const { attributes, body } = fm ( rawContent ) ;
@@ -412,23 +451,26 @@ function constructBlogUrl(filename: string): string {
412451}
413452
414453/**
415- * Writes the LLM-friendly content to their respective output files.
454+ * Loads and parses a versioned sidebar configuration file.
455+ * Returns the docs array from the sidebar config.
416456 */
417- async function writeOutputFiles (
418- llmsTxtContent : string ,
419- llmsFullTxtContent : string ,
420- ) : Promise < void > {
421- console . log ( "Writing output files to static/ ..." ) ;
457+ async function loadVersionedSidebar (
458+ version : string ,
459+ ) : Promise < SidebarItemConfig [ ] > {
460+ const sidebarPath = path . join (
461+ VERSIONED_SIDEBARS_DIR ,
462+ `version-${ version } -sidebars.json` ,
463+ ) ;
464+ const sidebarContent = await fs . readFile ( sidebarPath , "utf8" ) ;
465+ const sidebarConfig = JSON . parse ( sidebarContent ) ;
422466
423- await fs . writeFile ( LLMS_TXT_FILE_PATH , llmsTxtContent . trim ( ) , "utf8" ) ;
424- console . log ( `Generated overview file: ${ LLMS_TXT_FILE_PATH } ` ) ;
467+ if ( ! Array . isArray ( sidebarConfig . docs ) ) {
468+ throw new Error (
469+ `Versioned sidebar configuration for ${ version } does not have a docs array.` ,
470+ ) ;
471+ }
425472
426- await fs . writeFile (
427- LLMS_FULL_TXT_FILE_PATH ,
428- llmsFullTxtContent . trim ( ) ,
429- "utf8" ,
430- ) ;
431- console . log ( `Generated full concatenated file: ${ LLMS_FULL_TXT_FILE_PATH } ` ) ;
473+ return sidebarConfig . docs ;
432474}
433475
434476/**
0 commit comments