rspress-plugin-api-extractor 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/api-extracted-package.js +426 -0
- package/build-program.js +120 -0
- package/build-stages.js +664 -0
- package/category-resolver.js +50 -0
- package/code-post-processor.js +38 -0
- package/config-helpers.js +106 -0
- package/config-utils.js +258 -0
- package/content-hash.js +79 -0
- package/errors.js +29 -0
- package/formatter.js +69 -0
- package/hide-cut-transformer.js +96 -0
- package/index.d.ts +901 -831
- package/index.js +4 -6128
- package/layers/ConfigServiceLive.js +377 -0
- package/layers/ObservabilityLive.js +136 -0
- package/layers/PathDerivationServiceLive.js +16 -0
- package/layers/SnapshotServiceLive.js +94 -0
- package/layers/TypeRegistryServiceLive.js +46 -0
- package/llms-processing.js +270 -0
- package/llms-program.js +262 -0
- package/loader.js +186 -0
- package/markdown/cross-linker.js +156 -0
- package/markdown/helpers.js +364 -0
- package/markdown/index.js +11 -0
- package/markdown/page-generators/class-page.js +357 -0
- package/markdown/page-generators/enum-page.js +152 -0
- package/markdown/page-generators/function-page.js +127 -0
- package/markdown/page-generators/index-pages.js +27 -0
- package/markdown/page-generators/interface-page.js +307 -0
- package/markdown/page-generators/namespace-page.js +280 -0
- package/markdown/page-generators/type-alias-page.js +110 -0
- package/markdown/page-generators/variable-page.js +110 -0
- package/markdown/shiki-utils.js +48 -0
- package/migrations/001_create_snapshots.js +25 -0
- package/model-loader.js +95 -0
- package/multi-entry-resolver.js +70 -0
- package/og-resolver.js +271 -0
- package/package.json +64 -73
- package/path-derivation.js +48 -0
- package/plugin.js +218 -0
- package/prettier-formatter.js +73 -0
- package/public/tsconfig/rspress.json +44 -0
- package/remark-api-codeblocks.js +130 -0
- package/remark-with-api.js +172 -0
- package/route-collisions.js +52 -0
- package/runtime/components/ApiExample/index.js +29 -25
- package/runtime/components/ApiLlmsPackageActions/index.js +252 -323
- package/runtime/components/ApiLlmsViewOptions/index.js +269 -340
- package/runtime/components/ApiMember/index.js +49 -47
- package/runtime/components/ApiSignature/index.js +32 -28
- package/runtime/components/EnumMembersTable/{index_module.css → index.css} +18 -19
- package/runtime/components/EnumMembersTable/index.js +36 -67
- package/runtime/components/EnumMembersTable/index.module.js +9 -6
- package/runtime/components/ExampleBlock/{index_module.css → index.css} +2 -3
- package/runtime/components/ExampleBlock/index.js +23 -28
- package/runtime/components/ExampleBlock/index.module.js +8 -5
- package/runtime/components/MarkdownContent/index.js +26 -18
- package/runtime/components/MarkdownText/index.js +28 -22
- package/runtime/components/MemberSignature/{index_module.css → index.css} +5 -6
- package/runtime/components/MemberSignature/index.js +46 -46
- package/runtime/components/MemberSignature/index.module.js +9 -6
- package/runtime/components/ParametersTable/{index_module.css → index.css} +19 -20
- package/runtime/components/ParametersTable/index.js +36 -67
- package/runtime/components/ParametersTable/index.module.js +9 -6
- package/runtime/components/SignatureBlock/{index_module.css → index.css} +5 -6
- package/runtime/components/SignatureBlock/index.js +30 -29
- package/runtime/components/SignatureBlock/index.module.js +9 -6
- package/runtime/components/SignatureCode/{index_module.css → index.css} +9 -10
- package/runtime/components/SignatureCode/index.js +40 -32
- package/runtime/components/SignatureCode/index.module.js +9 -6
- package/runtime/components/SignatureToolbar/{index_module.css → index.css} +18 -20
- package/runtime/components/SignatureToolbar/index.js +48 -51
- package/runtime/components/SignatureToolbar/index.module.js +13 -10
- package/runtime/components/buttons/ButtonGroup.js +13 -6
- package/runtime/components/buttons/CopyCodeButton.js +40 -38
- package/runtime/components/buttons/WrapSignatureButton.js +20 -16
- package/runtime/components/buttons/{index_module.css → index.css} +5 -6
- package/runtime/components/buttons/index.module.js +8 -5
- package/runtime/components/icons/CheckIcon/index.js +20 -17
- package/runtime/components/icons/CopyIcon/index.js +20 -17
- package/runtime/components/icons/UnwrapIcon/index.js +22 -18
- package/runtime/components/icons/WrapIcon/index.js +20 -17
- package/runtime/components/shared/_twoslash.css +3 -10
- package/runtime/components/shared/variables.css +0 -3
- package/runtime/hooks/useWrapToggle.js +32 -9
- package/runtime/index.d.ts +513 -173
- package/runtime/index.js +11 -9
- package/runtime/utils/decode-hast.js +31 -16
- package/runtime/utils/hast-renderer.js +21 -7
- package/schemas/config.js +199 -0
- package/schemas/index.js +5 -0
- package/schemas/opengraph.js +26 -0
- package/schemas/performance.js +19 -0
- package/serve.js +133 -0
- package/services/ConfigService.js +7 -0
- package/services/PathDerivationService.js +7 -0
- package/services/SnapshotService.js +7 -0
- package/services/TypeRegistryService.js +7 -0
- package/shiki-transformer.js +758 -0
- package/tsconfig-parser.js +127 -0
- package/tsdoc-metadata.json +11 -11
- package/twoslash-patterns.js +87 -0
- package/twoslash-transformer.js +316 -0
- package/type-reference-extractor.js +201 -0
- package/typescript-config.js +168 -0
- package/vfs-registry.js +121 -0
- package/0~llms-program.js +0 -344
- package/runtime/components/ApiLlmsPackageActions/index.module.js +0 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
//#region src/llms-processing.ts
|
|
2
|
+
/** Pre-compiled regex for parsing llms.txt link lines. */
|
|
3
|
+
const LLMS_TXT_LINE_RE = /^-\s+\[([^\]]+)\]\(([^)]+)\)(?::\s*(.+))?$/;
|
|
4
|
+
/**
|
|
5
|
+
* Parse a single line from llms.txt format.
|
|
6
|
+
*
|
|
7
|
+
* Recognizes the pattern: `- [title](url): description`
|
|
8
|
+
* The description portion (`: description`) is optional.
|
|
9
|
+
*
|
|
10
|
+
* @param line - A single line from an llms.txt file
|
|
11
|
+
* @returns Parsed entry or null for non-link lines (headers, empty lines, plain text)
|
|
12
|
+
*/
|
|
13
|
+
function parseLlmsTxtLine(line) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (trimmed === "") return null;
|
|
16
|
+
const match = LLMS_TXT_LINE_RE.exec(trimmed);
|
|
17
|
+
if (!match) return null;
|
|
18
|
+
const title = match[1];
|
|
19
|
+
const url = match[2];
|
|
20
|
+
const rawDescription = match[3];
|
|
21
|
+
return {
|
|
22
|
+
title,
|
|
23
|
+
url,
|
|
24
|
+
description: rawDescription ? rawDescription.trim() : void 0
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Filter API page entries from global llms.txt content.
|
|
29
|
+
*
|
|
30
|
+
* Removes lines whose parsed URL is in the `apiRoutes` set.
|
|
31
|
+
* Appends pointer lines for per-package llms files when `pointers` is non-empty.
|
|
32
|
+
*
|
|
33
|
+
* @param content - Full llms.txt content string
|
|
34
|
+
* @param apiRoutes - Set of API route paths to remove
|
|
35
|
+
* @param pointers - Per-package pointer entries to append
|
|
36
|
+
* @returns Filtered llms.txt content
|
|
37
|
+
*/
|
|
38
|
+
function filterLlmsTxt(content, apiRoutes, pointers) {
|
|
39
|
+
const lines = content.split("\n");
|
|
40
|
+
const filtered = [];
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const entry = parseLlmsTxtLine(line);
|
|
43
|
+
if (entry && apiRoutes.has(entry.url)) continue;
|
|
44
|
+
filtered.push(line);
|
|
45
|
+
}
|
|
46
|
+
let result = filtered.join("\n");
|
|
47
|
+
if (pointers.length > 0) {
|
|
48
|
+
result += "\n\n";
|
|
49
|
+
for (const pointer of pointers) result += `- For ${pointer.name} API docs, see [${pointer.name} llms.txt](${pointer.llmsTxtUrl})\n`;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate a structured global llms.txt that groups pages by package scope.
|
|
55
|
+
*
|
|
56
|
+
* Output format:
|
|
57
|
+
* ```
|
|
58
|
+
* # {site title}
|
|
59
|
+
*
|
|
60
|
+
* ## Others
|
|
61
|
+
* - [Blog Post](/blog/post.md)
|
|
62
|
+
*
|
|
63
|
+
* ## Packages
|
|
64
|
+
*
|
|
65
|
+
* ### {name} {version}
|
|
66
|
+
* {description}
|
|
67
|
+
* - [Guide Page](/pkg/guides/guide.md)
|
|
68
|
+
* - [API Reference](/pkg/llms-api.txt)
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @param content - Original RSPress-generated llms.txt content
|
|
72
|
+
* @param apiRoutes - Set of API route paths to exclude as individual entries
|
|
73
|
+
* @param packages - Package scope metadata
|
|
74
|
+
* @returns Restructured llms.txt content
|
|
75
|
+
*/
|
|
76
|
+
function generateStructuredLlmsTxt(content, apiRoutes, packages) {
|
|
77
|
+
const lines = content.split("\n");
|
|
78
|
+
let title = "";
|
|
79
|
+
for (const line of lines) if (line.startsWith("# ")) {
|
|
80
|
+
title = line;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const allEntries = [];
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const entry = parseLlmsTxtLine(line);
|
|
86
|
+
if (entry && !apiRoutes.has(entry.url)) allEntries.push(entry);
|
|
87
|
+
}
|
|
88
|
+
const packageEntries = /* @__PURE__ */ new Map();
|
|
89
|
+
const others = [];
|
|
90
|
+
for (const entry of allEntries) {
|
|
91
|
+
let matched = false;
|
|
92
|
+
for (const pkg of packages) {
|
|
93
|
+
const base = pkg.packageRoute.endsWith("/") ? pkg.packageRoute : `${pkg.packageRoute}/`;
|
|
94
|
+
if (entry.url.startsWith(base) || entry.url === pkg.packageRoute) {
|
|
95
|
+
const existing = packageEntries.get(pkg.packageName) ?? [];
|
|
96
|
+
existing.push(entry);
|
|
97
|
+
packageEntries.set(pkg.packageName, existing);
|
|
98
|
+
matched = true;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!matched) others.push(entry);
|
|
103
|
+
}
|
|
104
|
+
const output = [];
|
|
105
|
+
if (title) {
|
|
106
|
+
output.push(title);
|
|
107
|
+
output.push("");
|
|
108
|
+
}
|
|
109
|
+
if (others.length > 0) {
|
|
110
|
+
output.push("## Others");
|
|
111
|
+
output.push("");
|
|
112
|
+
for (const entry of others) output.push(formatEntry(entry));
|
|
113
|
+
output.push("");
|
|
114
|
+
}
|
|
115
|
+
const packagesWithEntries = packages;
|
|
116
|
+
if (packagesWithEntries.length > 0) {
|
|
117
|
+
output.push("## Packages");
|
|
118
|
+
output.push("");
|
|
119
|
+
for (const pkg of packagesWithEntries) {
|
|
120
|
+
const versionSuffix = pkg.version ? ` ${pkg.version}` : "";
|
|
121
|
+
output.push(`### ${pkg.name}${versionSuffix}`);
|
|
122
|
+
output.push("");
|
|
123
|
+
if (pkg.description) {
|
|
124
|
+
output.push(pkg.description);
|
|
125
|
+
output.push("");
|
|
126
|
+
}
|
|
127
|
+
const entries = packageEntries.get(pkg.packageName) ?? [];
|
|
128
|
+
for (const entry of entries) output.push(formatEntry(entry));
|
|
129
|
+
output.push(`- [API Reference](${pkg.llmsApiTxtUrl})`);
|
|
130
|
+
output.push("");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return output.join("\n");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parse llms-full.txt content into sections delimited by frontmatter blocks.
|
|
137
|
+
*
|
|
138
|
+
* Each section has the format:
|
|
139
|
+
* ```
|
|
140
|
+
* ---
|
|
141
|
+
* url: /path/to/page
|
|
142
|
+
* ---
|
|
143
|
+
*
|
|
144
|
+
* Content here...
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
function parseSections(content) {
|
|
148
|
+
if (content.trim() === "") return [];
|
|
149
|
+
const sections = [];
|
|
150
|
+
const frontmatterPattern = /^---\nurl:\s*(.+)\n---$/gm;
|
|
151
|
+
let match = frontmatterPattern.exec(content);
|
|
152
|
+
const boundaries = [];
|
|
153
|
+
while (match !== null) {
|
|
154
|
+
boundaries.push({
|
|
155
|
+
url: match[1].trim(),
|
|
156
|
+
start: match.index,
|
|
157
|
+
fmEnd: match.index + match[0].length
|
|
158
|
+
});
|
|
159
|
+
match = frontmatterPattern.exec(content);
|
|
160
|
+
}
|
|
161
|
+
for (let i = 0; i < boundaries.length; i++) {
|
|
162
|
+
const boundary = boundaries[i];
|
|
163
|
+
const nextStart = i + 1 < boundaries.length ? boundaries[i + 1].start : content.length;
|
|
164
|
+
const sectionContent = content.slice(boundary.start, nextStart);
|
|
165
|
+
sections.push({
|
|
166
|
+
url: boundary.url,
|
|
167
|
+
raw: sectionContent.trimEnd()
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return sections;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Filter API page content sections from global llms-full.txt.
|
|
174
|
+
*
|
|
175
|
+
* Sections are delimited by `---\nurl: {path}\n---` frontmatter blocks.
|
|
176
|
+
* Removes entire sections whose URL matches a known API route.
|
|
177
|
+
*
|
|
178
|
+
* @param content - Full llms-full.txt content string
|
|
179
|
+
* @param apiRoutes - Set of API route paths to remove
|
|
180
|
+
* @returns Filtered llms-full.txt content
|
|
181
|
+
*/
|
|
182
|
+
function filterLlmsFullTxt(content, apiRoutes) {
|
|
183
|
+
if (content.trim() === "") return "";
|
|
184
|
+
const kept = parseSections(content).filter((section) => !apiRoutes.has(section.url));
|
|
185
|
+
if (kept.length === 0) return "";
|
|
186
|
+
return kept.map((section) => section.raw).join("\n\n\n");
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Format a single llms.txt link entry.
|
|
190
|
+
*/
|
|
191
|
+
function formatEntry(entry) {
|
|
192
|
+
if (entry.description) return `- [${entry.title}](${entry.url}): ${entry.description}`;
|
|
193
|
+
return `- [${entry.title}](${entry.url})`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Generate a per-package llms.txt index.
|
|
197
|
+
*
|
|
198
|
+
* Output format:
|
|
199
|
+
* ```
|
|
200
|
+
* # {name}
|
|
201
|
+
*
|
|
202
|
+
* ## Guides
|
|
203
|
+
*
|
|
204
|
+
* - [Guide Title](/path): Description
|
|
205
|
+
*
|
|
206
|
+
* ## API Reference
|
|
207
|
+
*
|
|
208
|
+
* - [ApiItem](/path): Description
|
|
209
|
+
* ```
|
|
210
|
+
*
|
|
211
|
+
* Sections with no entries are omitted.
|
|
212
|
+
*
|
|
213
|
+
* @param input - Package name, guide pages, and API pages
|
|
214
|
+
* @returns Generated llms.txt content
|
|
215
|
+
*/
|
|
216
|
+
function generatePackageLlmsTxt(input) {
|
|
217
|
+
const parts = [
|
|
218
|
+
`# ${input.name}`,
|
|
219
|
+
"",
|
|
220
|
+
`> API documentation for the ${input.packageName} package`
|
|
221
|
+
];
|
|
222
|
+
if (input.guidePages.length > 0) {
|
|
223
|
+
parts.push("");
|
|
224
|
+
parts.push("## Guides");
|
|
225
|
+
parts.push("");
|
|
226
|
+
for (const page of input.guidePages) parts.push(formatEntry(page));
|
|
227
|
+
}
|
|
228
|
+
if (input.apiPages.length > 0) {
|
|
229
|
+
parts.push("");
|
|
230
|
+
parts.push("## API Reference");
|
|
231
|
+
parts.push("");
|
|
232
|
+
for (const page of input.apiPages) parts.push(formatEntry(page));
|
|
233
|
+
}
|
|
234
|
+
parts.push("");
|
|
235
|
+
return parts.join("\n");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Concatenate page contents with frontmatter delimiters.
|
|
239
|
+
*
|
|
240
|
+
* Used for llms-full.txt, llms-docs.txt, and llms-api.txt generation
|
|
241
|
+
* (pass different page sets for each).
|
|
242
|
+
*
|
|
243
|
+
* Output format:
|
|
244
|
+
* ```
|
|
245
|
+
* ---
|
|
246
|
+
* url: /path/to/page
|
|
247
|
+
* ---
|
|
248
|
+
*
|
|
249
|
+
* Content here...
|
|
250
|
+
*
|
|
251
|
+
*
|
|
252
|
+
* ---
|
|
253
|
+
* url: /path/to/next
|
|
254
|
+
* ---
|
|
255
|
+
*
|
|
256
|
+
* More content...
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* @param pages - Array of page URLs and their markdown content
|
|
260
|
+
* @returns Concatenated content with frontmatter delimiters
|
|
261
|
+
*/
|
|
262
|
+
function generatePackageLlmsFullTxt(pages) {
|
|
263
|
+
if (pages.length === 0) return "";
|
|
264
|
+
const sections = [];
|
|
265
|
+
for (const page of pages) sections.push(`---\nurl: ${page.url}\n---\n\n${page.content}`);
|
|
266
|
+
return sections.join("\n\n\n");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
export { filterLlmsFullTxt, filterLlmsTxt, generatePackageLlmsFullTxt, generatePackageLlmsTxt, generateStructuredLlmsTxt, parseLlmsTxtLine };
|
package/llms-program.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { filterLlmsFullTxt, filterLlmsTxt, generatePackageLlmsFullTxt, generatePackageLlmsTxt, generateStructuredLlmsTxt, parseLlmsTxtLine } from "./llms-processing.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { FileSystem } from "@effect/platform";
|
|
5
|
+
|
|
6
|
+
//#region src/llms-program.ts
|
|
7
|
+
/**
|
|
8
|
+
* Effect program for post-processing LLMs text files in afterBuild.
|
|
9
|
+
*
|
|
10
|
+
* Wires the pure processing functions from llms-processing.ts into the
|
|
11
|
+
* plugin lifecycle, handling file I/O via \@effect/platform FileSystem.
|
|
12
|
+
*
|
|
13
|
+
* Responsibilities:
|
|
14
|
+
* 1. Collect all API routes from build results
|
|
15
|
+
* 2. Post-process global llms.txt (filter API entries, append pointers)
|
|
16
|
+
* 3. Post-process global llms-full.txt (remove API sections)
|
|
17
|
+
* 4. Generate per-package files (llms.txt, llms-full.txt, llms-docs.txt, llms-api.txt)
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Convert generated file relative paths to route URLs for matching against llms.txt entries.
|
|
21
|
+
*
|
|
22
|
+
* The generatedFiles Set stores paths relative to the output dir, like "class/pipeline.mdx".
|
|
23
|
+
* The llms.txt entries have URLs like "/api/class/pipeline", so we:
|
|
24
|
+
* - Replace .mdx extension with .md (RSPress llms.txt URLs use .md extension)
|
|
25
|
+
* - Prepend the baseRoute (e.g., "/api/")
|
|
26
|
+
* - Normalize trailing slashes
|
|
27
|
+
*/
|
|
28
|
+
function buildApiRoutes(buildResults) {
|
|
29
|
+
const apiRoutes = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const result of buildResults) {
|
|
31
|
+
const base = result.baseRoute.endsWith("/") ? result.baseRoute : `${result.baseRoute}/`;
|
|
32
|
+
for (const relPath of result.generatedFiles) {
|
|
33
|
+
if (!relPath.endsWith(".mdx")) continue;
|
|
34
|
+
const routeUrl = `${base}${relPath.replace(/\.mdx$/, ".md")}`;
|
|
35
|
+
apiRoutes.add(routeUrl);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return apiRoutes;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Discover version/locale prefixes from build results' base routes.
|
|
42
|
+
*
|
|
43
|
+
* RSPress generates global llms files at version/locale prefixed paths:
|
|
44
|
+
* - dist/llms.txt (default)
|
|
45
|
+
* - dist/v1/llms.txt (versioned)
|
|
46
|
+
* - dist/zh/llms.txt (i18n)
|
|
47
|
+
*
|
|
48
|
+
* We examine the base routes to extract prefixes. Base routes like:
|
|
49
|
+
* - "/api" results in prefix ""
|
|
50
|
+
* - "/v1/api" results in prefix "v1"
|
|
51
|
+
* - "/zh/api" results in prefix "zh"
|
|
52
|
+
*
|
|
53
|
+
* Returns unique prefixes including "" (root) which always exists.
|
|
54
|
+
*/
|
|
55
|
+
function discoverPrefixes(buildResults) {
|
|
56
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
57
|
+
prefixes.add("");
|
|
58
|
+
for (const result of buildResults) {
|
|
59
|
+
const segments = result.baseRoute.split("/").filter(Boolean);
|
|
60
|
+
if (segments.length > 1) {
|
|
61
|
+
const prefixSegments = segments.slice(0, -1);
|
|
62
|
+
prefixes.add(prefixSegments.join("/"));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return prefixes;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build package pointers for a given prefix context.
|
|
69
|
+
*
|
|
70
|
+
* Each build result produces a per-package llms.txt file. The pointer
|
|
71
|
+
* URL is relative to the site root and includes any prefix.
|
|
72
|
+
*/
|
|
73
|
+
function buildPackagePointers(buildResults, prefix, packageRoutes) {
|
|
74
|
+
const pointers = [];
|
|
75
|
+
for (const result of buildResults) {
|
|
76
|
+
if (prefix !== "" && !result.baseRoute.startsWith(`/${prefix}/`)) continue;
|
|
77
|
+
const displayName = result.apiName ?? result.packageName;
|
|
78
|
+
const pkgRoute = packageRoutes.get(result.packageName) ?? result.baseRoute;
|
|
79
|
+
const base = pkgRoute.endsWith("/") ? pkgRoute : `${pkgRoute}/`;
|
|
80
|
+
pointers.push({
|
|
81
|
+
name: displayName,
|
|
82
|
+
llmsTxtUrl: `${base}llms.txt`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return pointers;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Collect API page entries from the global llms.txt for a specific package.
|
|
89
|
+
*
|
|
90
|
+
* Parses the global llms.txt to find entries whose URLs match this package's
|
|
91
|
+
* generated API routes, building the LlmsTxtEntry array for per-package files.
|
|
92
|
+
*/
|
|
93
|
+
function collectApiEntries(globalLlmsTxtContent, result) {
|
|
94
|
+
const base = result.baseRoute.endsWith("/") ? result.baseRoute : `${result.baseRoute}/`;
|
|
95
|
+
const entries = [];
|
|
96
|
+
for (const line of globalLlmsTxtContent.split("\n")) {
|
|
97
|
+
const entry = parseLlmsTxtLine(line);
|
|
98
|
+
if (!entry) continue;
|
|
99
|
+
if (entry.url.startsWith(base)) entries.push(entry);
|
|
100
|
+
}
|
|
101
|
+
return entries;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Collect guide page entries from the global llms.txt that are NOT API pages.
|
|
105
|
+
*
|
|
106
|
+
* Guide pages are entries in the global llms.txt that are under the package's
|
|
107
|
+
* route but not in the API routes set. Uses packageRoute (e.g., "/kitchensink")
|
|
108
|
+
* instead of prefix to avoid matching entries from other packages.
|
|
109
|
+
*/
|
|
110
|
+
function collectGuideEntries(globalLlmsTxtContent, apiRoutes, packageRoute) {
|
|
111
|
+
const base = packageRoute.endsWith("/") ? packageRoute : `${packageRoute}/`;
|
|
112
|
+
const entries = [];
|
|
113
|
+
for (const line of globalLlmsTxtContent.split("\n")) {
|
|
114
|
+
const entry = parseLlmsTxtLine(line);
|
|
115
|
+
if (!entry) continue;
|
|
116
|
+
if ((entry.url === packageRoute || entry.url.startsWith(base)) && !apiRoutes.has(entry.url)) entries.push(entry);
|
|
117
|
+
}
|
|
118
|
+
return entries;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Extract page content sections from llms-full.txt that match a URL predicate.
|
|
122
|
+
*
|
|
123
|
+
* Sections are delimited by `---\nurl: {path}\n---` frontmatter blocks.
|
|
124
|
+
* Returns content (without frontmatter) for sections whose URL satisfies the predicate.
|
|
125
|
+
*/
|
|
126
|
+
function extractSections(globalLlmsFullContent, urlPredicate) {
|
|
127
|
+
const pages = [];
|
|
128
|
+
if (!globalLlmsFullContent) return pages;
|
|
129
|
+
const frontmatterPattern = /^---\nurl:\s*(.+)\n---$/gm;
|
|
130
|
+
let match = frontmatterPattern.exec(globalLlmsFullContent);
|
|
131
|
+
const boundaries = [];
|
|
132
|
+
while (match !== null) {
|
|
133
|
+
boundaries.push({
|
|
134
|
+
url: match[1].trim(),
|
|
135
|
+
start: match.index,
|
|
136
|
+
fmEnd: match.index + match[0].length
|
|
137
|
+
});
|
|
138
|
+
match = frontmatterPattern.exec(globalLlmsFullContent);
|
|
139
|
+
}
|
|
140
|
+
for (let i = 0; i < boundaries.length; i++) {
|
|
141
|
+
const boundary = boundaries[i];
|
|
142
|
+
if (!urlPredicate(boundary.url)) continue;
|
|
143
|
+
const nextStart = i + 1 < boundaries.length ? boundaries[i + 1].start : globalLlmsFullContent.length;
|
|
144
|
+
const content = globalLlmsFullContent.slice(boundary.fmEnd, nextStart).trim();
|
|
145
|
+
pages.push({
|
|
146
|
+
url: boundary.url,
|
|
147
|
+
content
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return pages;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Read llms-full.txt sections and collect page content for API pages.
|
|
154
|
+
*
|
|
155
|
+
* Parses the global llms-full.txt to extract full markdown content for
|
|
156
|
+
* pages matching this package's API routes.
|
|
157
|
+
*/
|
|
158
|
+
function collectApiPageContent(globalLlmsFullContent, result) {
|
|
159
|
+
const base = result.baseRoute.endsWith("/") ? result.baseRoute : `${result.baseRoute}/`;
|
|
160
|
+
return extractSections(globalLlmsFullContent, (url) => url.startsWith(base));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Process LLMs text files after the RSPress build.
|
|
164
|
+
*
|
|
165
|
+
* This Effect program:
|
|
166
|
+
* 1. Builds the set of all API route URLs from build results
|
|
167
|
+
* 2. Discovers version/locale prefixes from base routes
|
|
168
|
+
* 3. For each prefix, post-processes global llms.txt and llms-full.txt
|
|
169
|
+
* 4. When scopes is enabled, generates per-package LLMs files
|
|
170
|
+
*
|
|
171
|
+
* Requires FileSystem from \@effect/platform.
|
|
172
|
+
*/
|
|
173
|
+
function processLlmsFiles(input) {
|
|
174
|
+
return Effect.gen(function* () {
|
|
175
|
+
const fs = yield* FileSystem.FileSystem;
|
|
176
|
+
const { outDir, buildResults, llmsPlugin, packageRoutes } = input;
|
|
177
|
+
if (buildResults.length === 0) return;
|
|
178
|
+
const apiRoutes = buildApiRoutes(buildResults);
|
|
179
|
+
yield* Effect.logDebug(`Built ${apiRoutes.size} API routes for LLMs filtering`);
|
|
180
|
+
if (apiRoutes.size === 0) return;
|
|
181
|
+
const prefixes = discoverPrefixes(buildResults);
|
|
182
|
+
yield* Effect.forEach([...prefixes], (prefix) => processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin, packageRoutes), { concurrency: "unbounded" });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Process global and per-package LLMs files for a single prefix.
|
|
187
|
+
*/
|
|
188
|
+
function processPrefix(fs, outDir, prefix, buildResults, apiRoutes, llmsPlugin, packageRoutes) {
|
|
189
|
+
return Effect.gen(function* () {
|
|
190
|
+
const prefixDir = prefix ? path.join(outDir, prefix) : outDir;
|
|
191
|
+
const llmsTxtPath = path.join(prefixDir, "llms.txt");
|
|
192
|
+
if (!(yield* fs.exists(llmsTxtPath).pipe(Effect.orElseSucceed(() => false)))) return;
|
|
193
|
+
const llmsTxtContent = yield* fs.readFileString(llmsTxtPath).pipe(Effect.orDie);
|
|
194
|
+
const llmsFullTxtPath = path.join(prefixDir, "llms-full.txt");
|
|
195
|
+
const llmsFullTxtContent = (yield* fs.exists(llmsFullTxtPath).pipe(Effect.orElseSucceed(() => false))) ? yield* fs.readFileString(llmsFullTxtPath).pipe(Effect.orDie) : "";
|
|
196
|
+
if (llmsPlugin.scopes) {
|
|
197
|
+
const structuredLlmsTxt = generateStructuredLlmsTxt(llmsTxtContent, apiRoutes, buildResults.map((r) => {
|
|
198
|
+
const pkgRoute = packageRoutes.get(r.packageName) ?? r.baseRoute;
|
|
199
|
+
return {
|
|
200
|
+
name: r.apiName ?? r.packageName,
|
|
201
|
+
packageName: r.packageName,
|
|
202
|
+
version: r.packageVersion,
|
|
203
|
+
description: r.packageDescription,
|
|
204
|
+
packageRoute: pkgRoute,
|
|
205
|
+
llmsApiTxtUrl: `${pkgRoute.endsWith("/") ? pkgRoute : `${pkgRoute}/`}llms-api.txt`
|
|
206
|
+
};
|
|
207
|
+
}));
|
|
208
|
+
yield* fs.writeFileString(llmsTxtPath, structuredLlmsTxt).pipe(Effect.orDie);
|
|
209
|
+
} else {
|
|
210
|
+
const filteredLlmsTxt = filterLlmsTxt(llmsTxtContent, apiRoutes, buildPackagePointers(buildResults, prefix, packageRoutes));
|
|
211
|
+
yield* fs.writeFileString(llmsTxtPath, filteredLlmsTxt).pipe(Effect.orDie);
|
|
212
|
+
}
|
|
213
|
+
if (llmsFullTxtContent) {
|
|
214
|
+
const filteredLlmsFullTxt = filterLlmsFullTxt(llmsFullTxtContent, apiRoutes);
|
|
215
|
+
yield* fs.writeFileString(llmsFullTxtPath, filteredLlmsFullTxt).pipe(Effect.orDie);
|
|
216
|
+
}
|
|
217
|
+
if (llmsPlugin.scopes) {
|
|
218
|
+
const prefixResults = prefix === "" ? [...buildResults] : buildResults.filter((r) => r.baseRoute.startsWith(`/${prefix}/`));
|
|
219
|
+
yield* Effect.forEach(prefixResults, (result) => generatePerPackageFiles(fs, outDir, result, llmsTxtContent, llmsFullTxtContent, apiRoutes, llmsPlugin, packageRoutes.get(result.packageName) ?? result.baseRoute), { concurrency: "unbounded" });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Generate per-package LLMs files (llms.txt, llms-full.txt, llms-docs.txt, llms-api.txt).
|
|
225
|
+
*/
|
|
226
|
+
function generatePerPackageFiles(fs, outDir, result, globalLlmsTxtContent, globalLlmsFullContent, apiRoutes, llmsPlugin, packageRoute) {
|
|
227
|
+
return Effect.gen(function* () {
|
|
228
|
+
const pkgRouteSegment = packageRoute.replace(/^\//, "");
|
|
229
|
+
const packageLlmsDir = pkgRouteSegment ? path.join(outDir, pkgRouteSegment) : outDir;
|
|
230
|
+
yield* fs.makeDirectory(packageLlmsDir, { recursive: true }).pipe(Effect.orDie);
|
|
231
|
+
const displayName = result.apiName ?? result.packageName;
|
|
232
|
+
const apiEntries = collectApiEntries(globalLlmsTxtContent, result);
|
|
233
|
+
const guideEntries = collectGuideEntries(globalLlmsTxtContent, apiRoutes, packageRoute);
|
|
234
|
+
const packageLlmsTxt = generatePackageLlmsTxt({
|
|
235
|
+
name: displayName,
|
|
236
|
+
packageName: result.packageName,
|
|
237
|
+
guidePages: guideEntries,
|
|
238
|
+
apiPages: apiEntries
|
|
239
|
+
});
|
|
240
|
+
yield* fs.writeFileString(path.join(packageLlmsDir, "llms.txt"), packageLlmsTxt).pipe(Effect.orDie);
|
|
241
|
+
const apiPageContent = collectApiPageContent(globalLlmsFullContent, result);
|
|
242
|
+
const guideRouteUrls = new Set(guideEntries.map((e) => e.url));
|
|
243
|
+
const guidePageContent = globalLlmsFullContent ? extractSections(globalLlmsFullContent, (url) => guideRouteUrls.has(url)) : [];
|
|
244
|
+
const fullPageContent = [...guidePageContent, ...apiPageContent];
|
|
245
|
+
if (fullPageContent.length > 0) {
|
|
246
|
+
const packageLlmsFullTxt = generatePackageLlmsFullTxt(fullPageContent);
|
|
247
|
+
yield* fs.writeFileString(path.join(packageLlmsDir, "llms-full.txt"), packageLlmsFullTxt).pipe(Effect.orDie);
|
|
248
|
+
}
|
|
249
|
+
if (llmsPlugin.apiTxt && apiPageContent.length > 0) {
|
|
250
|
+
const apiTxtContent = generatePackageLlmsFullTxt(apiPageContent);
|
|
251
|
+
yield* fs.writeFileString(path.join(packageLlmsDir, "llms-api.txt"), apiTxtContent).pipe(Effect.orDie);
|
|
252
|
+
}
|
|
253
|
+
if (guidePageContent.length > 0) {
|
|
254
|
+
const docsTxtContent = generatePackageLlmsFullTxt(guidePageContent);
|
|
255
|
+
yield* fs.writeFileString(path.join(packageLlmsDir, "llms-docs.txt"), docsTxtContent).pipe(Effect.orDie);
|
|
256
|
+
}
|
|
257
|
+
yield* Effect.logDebug(`Generated LLMs files for ${displayName} in ${packageLlmsDir}`);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
export { processLlmsFiles };
|