Skip to content

Commit bd5323f

Browse files
committed
llms.txt
1 parent 47692aa commit bd5323f

File tree

1 file changed

+72
-39
lines changed

1 file changed

+72
-39
lines changed

scripts/generate-llms.js

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,81 @@
11
const fs = require('fs');
22
const path = require('path');
33

4-
function getPositionForItem(itemPath) {
5-
const stat = fs.statSync(itemPath);
6-
if (stat.isDirectory()) {
7-
const categoryFile = path.join(itemPath, '_category_.json');
8-
if (fs.existsSync(categoryFile)) {
9-
const category = JSON.parse(fs.readFileSync(categoryFile, 'utf8'));
10-
return category.position || Infinity;
11-
}
12-
return Infinity;
13-
} else if (itemPath.endsWith('.md')) {
14-
const { frontmatter } = extractFrontmatterAndContent(itemPath);
15-
return getSidebarPosition(frontmatter);
16-
}
17-
return Infinity;
4+
const DOC_EXTS = new Set(['.md', '.mdx']);
5+
6+
function ensureDir(dir) {
7+
fs.mkdirSync(dir, { recursive: true });
8+
}
9+
10+
function isDocFile(p) {
11+
return DOC_EXTS.has(path.extname(p));
1812
}
1913

2014
function extractFrontmatterAndContent(filePath) {
21-
const content = fs.readFileSync(filePath, 'utf8');
22-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
23-
if (!frontmatterMatch) {
24-
return { frontmatter: '', content: content };
25-
}
26-
const frontmatter = frontmatterMatch[1];
27-
const body = frontmatterMatch[2];
28-
return { frontmatter, content: body };
15+
const raw = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''); // strip BOM
16+
// more tolerant: frontmatter is optional; allow \r\n
17+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
18+
if (!match) return { frontmatter: '', content: raw };
19+
return { frontmatter: match[1], content: match[2] };
2920
}
3021

3122
function getSidebarPosition(frontmatter) {
32-
const match = frontmatter.match(/sidebar_position:\s*(\d+)/);
33-
return match ? parseInt(match[1], 10) : Infinity;
23+
const m = frontmatter.match(/sidebar_position:\s*(\d+)/);
24+
return m ? parseInt(m[1], 10) : Infinity;
3425
}
3526

36-
function collectContent(dirPath) {
37-
const items = fs.readdirSync(dirPath).map(item => path.join(dirPath, item));
27+
function getCategoryPosition(dirPath) {
28+
const categoryFile = path.join(dirPath, '_category_.json');
29+
if (!fs.existsSync(categoryFile)) return Infinity;
30+
try {
31+
const category = JSON.parse(fs.readFileSync(categoryFile, 'utf8'));
32+
return Number.isFinite(category.position) ? category.position : Infinity;
33+
} catch {
34+
return Infinity;
35+
}
36+
}
3837

39-
// Filter to directories and .md files
40-
const validItems = items.filter(item => {
41-
const stat = fs.statSync(item);
42-
return stat.isDirectory() || item.endsWith('.md');
38+
function getPositionForItem(itemPath) {
39+
const stat = fs.statSync(itemPath);
40+
if (stat.isDirectory()) return getCategoryPosition(itemPath);
41+
if (isDocFile(itemPath)) {
42+
const { frontmatter } = extractFrontmatterAndContent(itemPath);
43+
return getSidebarPosition(frontmatter);
44+
}
45+
return Infinity;
46+
}
47+
48+
function collectContent(rootDir, dirPath = rootDir) {
49+
const items = fs.readdirSync(dirPath).map(name => path.join(dirPath, name));
50+
51+
const validItems = items.filter(p => {
52+
const stat = fs.statSync(p);
53+
return stat.isDirectory() || isDocFile(p);
4354
});
4455

45-
// Sort by position
46-
validItems.sort((a, b) => getPositionForItem(a) - getPositionForItem(b));
56+
validItems.sort((a, b) => {
57+
const pa = getPositionForItem(a);
58+
const pb = getPositionForItem(b);
59+
if (pa !== pb) return pa - pb;
60+
// deterministic tie-breaker
61+
return a.localeCompare(b);
62+
});
4763

4864
let combined = '';
65+
4966
for (const item of validItems) {
5067
const stat = fs.statSync(item);
68+
5169
if (stat.isDirectory()) {
52-
combined += collectContent(item);
53-
} else if (item.endsWith('.md')) {
70+
combined += collectContent(rootDir, item);
71+
continue;
72+
}
73+
74+
if (isDocFile(item)) {
5475
const { content } = extractFrontmatterAndContent(item);
55-
combined += content + '\n\n';
76+
const rel = path.relative(rootDir, item).replace(/\\/g, '/');
77+
combined += `\n<!-- SOURCE: ${rel} -->\n`;
78+
combined += content.trimEnd() + '\n';
5679
}
5780
}
5881

@@ -64,11 +87,21 @@ function main() {
6487
const buildDir = path.join(__dirname, '..', 'build');
6588
const outputFile = path.join(buildDir, 'llms.txt');
6689

67-
const combinedContent = collectContent(docsDir);
90+
ensureDir(buildDir);
91+
92+
const header = `# Durable Workflow – LLM Documentation Bundle
93+
# Source: https://durable-workflow.com
94+
# Generated from /docs
95+
# Purpose: AI-assisted reasoning, code generation, and Q&A
96+
# Canonical URL: https://durable-workflow.com/llms.txt
97+
98+
`;
6899

69-
fs.writeFileSync(outputFile, combinedContent, 'utf8');
100+
const combinedContent = collectContent(docsDir).trimStart() + '\n';
101+
const fullContent = header + combinedContent;
102+
fs.writeFileSync(outputFile, fullContent, 'utf8');
70103

71-
console.log('llms.txt generated successfully');
104+
console.log('llms.txt generated successfully:', outputFile);
72105
}
73106

74-
main();
107+
main();

0 commit comments

Comments
 (0)