Skip to content

Commit 57e2175

Browse files
committed
refactor: improve readability and reduce logging in generate-llms-txt plugin
1 parent 614156b commit 57e2175

File tree

2 files changed

+80
-50
lines changed

2 files changed

+80
-50
lines changed

docusaurus.config.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import remarkRawMarkdown from './src/plugins/remark-raw-markdown.mjs';
99
import darkTheme from './src/themes/react-navigation-dark';
1010
import lightTheme from './src/themes/react-navigation-light';
1111

12+
const url = 'https://reactnavigation.org';
1213
const latestVersion = '7.x';
1314

1415
const config: Config = {
1516
title: 'React Navigation',
1617
tagline: 'Routing and navigation for your React Native apps',
17-
url: 'https://reactnavigation.org/',
18+
url,
1819
baseUrl: '/',
1920
favicon: 'img/favicon.ico',
2021
organizationName: 'react-navigation',
@@ -133,10 +134,7 @@ const config: Config = {
133134
plugins: [
134135
'./src/plugins/disable-fully-specified.mjs',
135136
'./src/plugins/react-navigation-versions.mjs',
136-
[
137-
'./src/plugins/generate-llms-txt.mjs',
138-
{ latestVersion, baseUrl: 'https://reactnavigation.org' },
139-
],
137+
['./src/plugins/llms-txt.mjs', { latestVersion, baseUrl: url }],
140138
[
141139
'@docusaurus/plugin-client-redirects',
142140
{
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
import fs from 'fs';
2-
import path from 'path';
3-
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import util from 'node:util';
4+
import versions from '../../versions.json';
5+
6+
/**
7+
* Parses frontmatter from markdown content.
8+
* Extracts metadata like title and description, and returns the cleaned content.
9+
*
10+
* @param {string} fileContent - Raw markdown file content.
11+
* @returns {{data: Object, content: string}} - Parsed data and stripped content.
12+
*/
413
function parseFrontMatter(fileContent) {
514
const frontMatterRegex = /^---\n([\s\S]+?)\n---\n/;
615
const match = fileContent.match(frontMatterRegex);
@@ -13,24 +22,42 @@ function parseFrontMatter(fileContent) {
1322
const content = fileContent.replace(frontMatterRegex, '');
1423

1524
const data = {};
25+
1626
frontMatterBlock.split('\n').forEach((line) => {
1727
const parts = line.split(':');
28+
1829
if (parts.length >= 2) {
1930
const key = parts[0].trim();
31+
2032
let value = parts.slice(1).join(':').trim();
33+
34+
// Remove surrounding quotes if present
2135
if (
22-
(value.startsWith("'" ) && value.endsWith("'")) ||
36+
(value.startsWith("'") && value.endsWith("'")) ||
2337
(value.startsWith('"') && value.endsWith('"'))
2438
) {
2539
value = value.slice(1, -1);
2640
}
41+
2742
data[key] = value;
2843
}
2944
});
3045

3146
return { data, content };
3247
}
3348

49+
/**
50+
* Recursively processes sidebar items to generate the LLM index and collect full docs.
51+
* Handles different sidebar structures (flat arrays, categories).
52+
*
53+
* @param {Array} items - Sidebar items to process.
54+
* @param {string} docsPath - Base path to documentation files.
55+
* @param {string} version - Current version being processed (e.g., '7.x').
56+
* @param {boolean} isLatest - Whether this is the latest/stable version.
57+
* @param {string} baseUrl - The base URL of the website.
58+
* @param {number} level - Current nesting level for headings.
59+
* @returns {{content: string, docs: Array}} - Generated index content and list of doc objects.
60+
*/
3461
function processSidebar(
3562
items,
3663
docsPath,
@@ -43,6 +70,7 @@ function processSidebar(
4370
let fullDocsList = [];
4471

4572
items.forEach((item) => {
73+
// Case 1: Item is a direct link (string ID)
4674
if (typeof item === 'string') {
4775
const id = item;
4876
const filePath = path.join(docsPath, `${id}.md`);
@@ -54,13 +82,9 @@ function processSidebar(
5482
const title = data.title || id;
5583
const description = data.description || '';
5684

57-
let urlPath;
58-
if (isLatest) {
59-
urlPath = `/docs/${id}`;
60-
} else {
61-
urlPath = `/docs/${version}/${id}`;
62-
}
63-
85+
// Construct the public URL for the document
86+
// Latest version uses /docs/id, others use /docs/version/id
87+
const urlPath = isLatest ? `/docs/${id}` : `/docs/${version}/${id}`;
6488
const fullUrl = `${baseUrl}${urlPath}`;
6589

6690
llmsContent += `- [${title}](${fullUrl})${
@@ -73,9 +97,12 @@ function processSidebar(
7397
content,
7498
});
7599
}
76-
} else if (item.type === 'category') {
100+
}
101+
// Case 2: Item is a category object
102+
else if (item.type === 'category') {
77103
const label = item.label;
78-
const headingPrefix = '#'.repeat(level + 3);
104+
const headingPrefix = '#'.repeat(level + 3); // Start at level 3 (###)
105+
79106
llmsContent += `\n${headingPrefix} ${label}\n\n`;
80107

81108
const { content: childContent, docs: childDocs } = processSidebar(
@@ -86,6 +113,7 @@ function processSidebar(
86113
baseUrl,
87114
level + 1
88115
);
116+
89117
llmsContent += childContent;
90118
fullDocsList = fullDocsList.concat(childDocs);
91119
}
@@ -94,6 +122,11 @@ function processSidebar(
94122
return { content: llmsContent, docs: fullDocsList };
95123
}
96124

125+
/**
126+
* Generates the llms.txt and llms-full.txt files for a specific version.
127+
*
128+
* @returns {Array<string>} - List of generated filenames.
129+
*/
97130
function generateForVersion(
98131
siteDir,
99132
outDir,
@@ -102,8 +135,6 @@ function generateForVersion(
102135
isLatest,
103136
baseUrl
104137
) {
105-
console.log(`[generate-llms-txt] Generating for version ${version}...`);
106-
107138
const docsPath = path.join(siteDir, 'versioned_docs', `version-${version}`);
108139
const sidebarPath = path.join(
109140
siteDir,
@@ -112,24 +143,27 @@ function generateForVersion(
112143
);
113144

114145
if (!fs.existsSync(sidebarPath)) {
115-
console.warn(
116-
`[generate-llms-txt] Sidebar not found: ${sidebarPath}, skipping.`
117-
);
118-
return;
146+
// Silent return if sidebar is missing to avoid noise
147+
return [];
119148
}
120149

121150
const sidebarConfig = JSON.parse(fs.readFileSync(sidebarPath, 'utf8'));
151+
152+
// Handle different Docusaurus sidebar structures (root 'docs' key or first key)
122153
let rootItems = sidebarConfig.docs || Object.values(sidebarConfig)[0] || [];
123154

155+
// Normalize object-style sidebars (older Docusaurus versions) to array format
124156
if (!Array.isArray(rootItems) && typeof rootItems === 'object') {
125157
const normalized = [];
158+
126159
for (const [label, items] of Object.entries(rootItems)) {
127160
normalized.push({
128161
type: 'category',
129162
label: label,
130163
items: items,
131164
});
132165
}
166+
133167
rootItems = normalized;
134168
}
135169

@@ -141,12 +175,18 @@ function generateForVersion(
141175
baseUrl
142176
);
143177

178+
// 1. Generate Summary (llms.txt)
144179
let llmsTxt = `# React Navigation ${version}\n\n`;
180+
145181
llmsTxt += `> Routing and navigation for your React Native apps.\n\n`;
146182
llmsTxt += `## Documentation\n`;
147-
148183
llmsTxt += sidebarContent;
149184

185+
const summaryFilename = `${outputPrefix}.txt`;
186+
187+
fs.writeFileSync(path.join(outDir, summaryFilename), llmsTxt);
188+
189+
// 2. Generate Full Content (llms-full.txt)
150190
let llmsFullTxt = `# React Navigation ${version} Documentation\n\n`;
151191

152192
docs.forEach((doc) => {
@@ -155,9 +195,9 @@ function generateForVersion(
155195
llmsFullTxt += `${doc.content.trim()}\n\n---\n\n`;
156196
});
157197

158-
const summaryFilename = `${outputPrefix}.txt`;
159-
198+
// Determine full filename (e.g., llms-v8.x -> llms-full-v8.x.txt)
160199
let fullFilename;
200+
161201
if (outputPrefix === 'llms') {
162202
fullFilename = 'llms-full.txt';
163203
} else {
@@ -168,55 +208,47 @@ function generateForVersion(
168208
}
169209
}
170210

171-
fs.writeFileSync(path.join(outDir, summaryFilename), llmsTxt);
172-
console.log(`[generate-llms-txt] Wrote ${summaryFilename}`);
173-
174211
fs.writeFileSync(path.join(outDir, fullFilename), llmsFullTxt);
175-
console.log(`[generate-llms-txt] Wrote ${fullFilename}`);
212+
213+
return [summaryFilename, fullFilename];
176214
}
177215

178216
export default function (context, options) {
179217
return {
180-
name: 'generate-llms-txt',
218+
name: 'llms.txt',
181219
async postBuild({ siteDir, outDir }) {
182220
const { latestVersion, baseUrl } = options;
183221

184222
if (!latestVersion) {
185-
throw new Error(
186-
'[generate-llms-txt] "latestVersion" option is required.'
187-
);
188-
}
189-
if (!baseUrl) {
190-
throw new Error('[generate-llms-txt] "baseUrl" option is required.');
223+
throw new Error('[llms.txt] "latestVersion" option is required.');
191224
}
192225

193-
const versionsPath = path.join(siteDir, 'versions.json');
194-
let versions = [];
195-
if (fs.existsSync(versionsPath)) {
196-
versions = JSON.parse(fs.readFileSync(versionsPath, 'utf8'));
197-
} else {
198-
console.warn(
199-
'[generate-llms-txt] versions.json not found. Generating only for latestVersion.'
200-
);
201-
versions = [latestVersion];
226+
if (!baseUrl) {
227+
throw new Error('[llms.txt] "baseUrl" option is required.');
202228
}
203229

204-
console.log(`[generate-llms-txt] Found versions: ${versions.join(', ')}`);
205-
console.log(`[generate-llms-txt] Latest version: ${latestVersion}`);
230+
const generatedFiles = [];
206231

207232
versions.forEach((version) => {
208233
const isLatest = version === latestVersion;
234+
// Prefix: 'llms' for latest, 'llms-vX.x' for others
209235
const outputPrefix = isLatest ? 'llms' : `llms-v${version}`;
210236

211-
generateForVersion(
237+
const files = generateForVersion(
212238
siteDir,
213239
outDir,
214240
version,
215241
outputPrefix,
216242
isLatest,
217243
baseUrl
218244
);
245+
246+
generatedFiles.push(...files);
219247
});
248+
249+
console.log(
250+
`${util.styleText(['magenta', 'bold'], '[llms.txt]')} Wrote ${generatedFiles.length} files in build output.`
251+
);
220252
},
221253
};
222254
}

0 commit comments

Comments
 (0)