Skip to content

Commit 814a86e

Browse files
committed
Merge branch 'release' into deploy-web
2 parents e5389b2 + f85a18d commit 814a86e

File tree

2 files changed

+107
-66
lines changed

2 files changed

+107
-66
lines changed

web/.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,4 @@ yarn-error.log*
2121
.vercel
2222

2323
# LLM files
24-
llms.txt
25-
llms-full.txt
24+
llms*.txt

web/scripts/generate-llm-files.ts

Lines changed: 106 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,22 @@ import fs from "fs/promises";
77
import { globSync } from "glob";
88
import path from "path";
99

10-
import docsSidebarConfig from "../sidebars.js";
10+
import versionsJson from "../versions.json";
1111

1212
const SITE_ROOT = process.cwd();
1313
const 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/");
1516
const BLOG_DIR = path.join(SITE_ROOT, "blog/");
1617
const GITHUB_RAW_BASE_URL =
1718
"https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/release/web/"; // Use the release branch
1819
const 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);
2320
const 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
*/
4437
async 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
*/
73103
async 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 = {
162200
async 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

Comments
 (0)