Skip to content

Commit 022bd68

Browse files
docs: add plugin support to generate llms.txt from documentation (#1738)
1 parent 7924083 commit 022bd68

File tree

5 files changed

+121
-3
lines changed

5 files changed

+121
-3
lines changed

apps/docs-app/docs/contributors.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import styles from './contributors.module.css';
88

99
# Contributors
1010

11+
AnalogJS is maintained by a team of contributors and community.
12+
1113
## Analog core team
1214

1315
### Brandon Roberts

apps/docs-app/docs/experimental/sfc/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ sidebar_position: 1
44

55
# Analog SFCs
66

7+
Analog SFCs are a new file format for Single File Components (SFCs) that aims to simplify the authoring experience and provide Angular-compatible components and directives.
8+
79
> **Note:**
810
>
911
> This file format and API is experimental, is a community-driven initiative, and is not an officially proposed change to Angular. Use it at your own risk.

apps/docs-app/docs/getting-started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import TabItem from '@theme/TabItem';
77

88
# Getting Started
99

10+
Creating an Analog project can be done with minimal steps.
11+
1012
## System Requirements
1113

1214
Analog requires the following Node and Angular versions:

apps/docs-app/docs/integrations/nx/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import TabItem from '@theme/TabItem';
77

88
# Nx
99

10+
Analog provides integration with Nx monorepos and workspaces through a workspace preset and an application generator. An Analog application can be created as a standalone project or added to an existing Nx workspace.
11+
1012
## Overview
1113

1214
[Nx](https://nx.dev) is a smart, fast, extensible build system with first class monorepo support and powerful integrations.
1315

14-
Analog provides integration with Nx monorepos and workspaces through a workspace preset and an application generator. An Analog application can be created as a standalone project or added to an existing Nx workspace.
15-
1616
## Creating a Standalone Nx project
1717

1818
To scaffold a standalone Nx project, use the `create-nx-workspace` command with the `@analogjs/platform` preset.

apps/docs-app/docusaurus.config.js

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// @ts-check
22

3+
import fs from 'node:fs';
4+
import path from 'node:path';
35
import { themes } from 'prism-react-renderer';
46
themes.nightOwl['plain'].backgroundColor = '#0a1429';
57

@@ -8,6 +10,8 @@ const projectName = 'analog';
810
const title = 'Analog';
911
const url = 'https://analogjs.org';
1012

13+
const DOCUSAURUS_BASE_URL = process.env.DOCUSAURUS_BASE_URL ?? '/docs';
14+
1115
/** @type {import('@docusaurus/types').Config} */
1216
const config = {
1317
baseUrl: '/',
@@ -50,7 +54,115 @@ const config = {
5054
onBrokenLinks: 'throw',
5155
onBrokenMarkdownLinks: 'throw',
5256
organizationName,
53-
plugins: [],
57+
plugins: [
58+
// Adapted from https://github.com/prisma/docs/blob/22208d52e4168028dbbe8b020b10682e6b526e50/docusaurus.config.ts
59+
async function pluginLlmsTxt(context) {
60+
return {
61+
name: 'llms-txt-plugin',
62+
loadContent: async () => {
63+
const { siteDir } = context;
64+
const contentDir = path.join(siteDir, 'docs');
65+
const allMdx = [];
66+
67+
// recursive function to get all mdx files
68+
const getMdFiles = async (dir) => {
69+
const entries = await fs.promises.readdir(dir, {
70+
withFileTypes: true,
71+
});
72+
73+
for (const entry of entries) {
74+
const fullPath = path.join(dir, entry.name);
75+
if (entry.isDirectory()) {
76+
await getMdFiles(fullPath);
77+
} else if (entry.name.endsWith('.md')) {
78+
const content = await fs.promises.readFile(fullPath, 'utf8');
79+
80+
// extract title from frontmatter if it exists
81+
const titleMatch = content.match(/^#\s(.*?)$/m);
82+
83+
const title = titleMatch ? titleMatch[1] : '';
84+
85+
// Get the relative path for URL construction
86+
const relativePath = path.relative(contentDir, fullPath);
87+
88+
// Convert file path to URL path by:
89+
// 1. Removing numeric prefixes (like 100-, 01-, etc.)
90+
// 2. Removing the .md extension
91+
let urlPath = relativePath
92+
.replace(/^\d+-/, '')
93+
.replace(/\/\d+-/g, '/')
94+
.replace(/index\.md$/, '')
95+
.replace(/\.md$/, '');
96+
97+
// Construct the full URL
98+
const fullUrl = `https://analogjs.org/docs/${urlPath}`;
99+
100+
// strip frontmatter
101+
const contentWithoutFrontmatter = content.replace(
102+
/^---\n[\s\S]*?\n---\n/,
103+
'',
104+
);
105+
106+
// combine title and content with URL
107+
const contentWithTitle = title
108+
? `# ${title}\n\nURL: ${fullUrl}\n${contentWithoutFrontmatter}`
109+
: contentWithoutFrontmatter;
110+
111+
allMdx.push(contentWithTitle);
112+
}
113+
}
114+
};
115+
116+
await getMdFiles(contentDir);
117+
return { allMdx };
118+
},
119+
postBuild: async ({ content, routes, outDir }) => {
120+
const { allMdx } = content;
121+
122+
// Write concatenated MDX content
123+
const concatenatedPath = path.join(outDir, 'llms-full.txt');
124+
await fs.promises.writeFile(
125+
concatenatedPath,
126+
allMdx.join('\n---\n\n'),
127+
);
128+
129+
// we need to dig down several layers:
130+
// find PluginRouteConfig marked by plugin.name === "docusaurus-plugin-content-docs"
131+
const docsPluginRouteConfig = routes.filter(
132+
(route) => route.plugin.name === 'docusaurus-plugin-content-docs',
133+
)[0];
134+
135+
// docsPluginRouteConfig has a routes property has a record with the path "/" that contains all docs routes.
136+
const allDocsRouteConfig = docsPluginRouteConfig.routes?.filter(
137+
(route) => route.path === DOCUSAURUS_BASE_URL,
138+
)[0];
139+
140+
// A little type checking first
141+
if (!allDocsRouteConfig?.props?.version) {
142+
return;
143+
}
144+
145+
// this route config has a `props` property that contains the current documentation.
146+
const currentVersionDocsRoutes =
147+
allDocsRouteConfig.props.version.docs;
148+
149+
// for every single docs route we now parse a path (which is the key) and a title
150+
const docsRecords = Object.entries(currentVersionDocsRoutes)
151+
.filter(([path, rec]) => !!rec.title && !!path)
152+
.map(([path, record]) => {
153+
return `- [${record.title}](${url}${DOCUSAURUS_BASE_URL}/${path.replace('/index', '')}): ${record.description || record.title}`;
154+
});
155+
156+
// Build up llms.txt file
157+
const llmsTxt = `# ${context.siteConfig.title}\n\n## Docs\n\n${docsRecords.join('\n')}\n`;
158+
159+
// Write llms.txt file
160+
const llmsTxtPath = path.join(outDir, 'llms.txt');
161+
await fs.promises.writeFile(llmsTxtPath, llmsTxt);
162+
},
163+
};
164+
},
165+
],
54166
presets: [
55167
[
56168
'classic',

0 commit comments

Comments
 (0)