nuxt-ai-ready 0.4.0 → 0.4.2
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/dist/module.json +1 -1
- package/dist/module.mjs +24 -6
- package/dist/runtime/llms-txt-utils.js +133 -19
- package/dist/runtime/server/utils/pageData.d.ts +2 -2
- package/dist/runtime/server/utils/pageData.js +20 -6
- package/package.json +1 -1
- package/dist/runtime/server/mcp/resources/pages.d.ts +0 -17
- package/dist/runtime/server/mcp/tools/list-pages.d.ts +0 -16
- package/dist/runtime/server/mcp/tools/search-pages-fuzzy.d.ts +0 -3
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -99,6 +99,7 @@ function normalizeLlmsTxtConfig(config) {
|
|
|
99
99
|
function createCrawlerState(pageDataPath, llmsFullTxtPath, siteInfo, llmsTxtConfig) {
|
|
100
100
|
return {
|
|
101
101
|
prerenderedRoutes: /* @__PURE__ */ new Set(),
|
|
102
|
+
errorRoutes: /* @__PURE__ */ new Set(),
|
|
102
103
|
totalProcessingTime: 0,
|
|
103
104
|
initialized: false,
|
|
104
105
|
jsonlInitialized: false,
|
|
@@ -221,7 +222,7 @@ async function crawlSitemapEntries(state, nuxt, nitro, entries) {
|
|
|
221
222
|
const res = await globalThis.$fetch(mdUrl, {
|
|
222
223
|
headers: { "x-nitro-prerender": mdRoute }
|
|
223
224
|
}).catch((err) => {
|
|
224
|
-
logger.debug(`
|
|
225
|
+
logger.debug(`Skipping ${route}: ${err.message}`);
|
|
225
226
|
return null;
|
|
226
227
|
});
|
|
227
228
|
if (!res)
|
|
@@ -230,7 +231,7 @@ async function crawlSitemapEntries(state, nuxt, nitro, entries) {
|
|
|
230
231
|
await processMarkdownRoute(state, nuxt, route, parsed, lastmod);
|
|
231
232
|
crawled++;
|
|
232
233
|
}
|
|
233
|
-
logger.debug(`Sitemap crawl complete: ${crawled} crawled, ${skipped} skipped
|
|
234
|
+
logger.debug(`Sitemap crawl complete: ${crawled} crawled, ${skipped} skipped`);
|
|
234
235
|
return crawled;
|
|
235
236
|
}
|
|
236
237
|
async function crawlSitemapContent(state, nuxt, nitro, sitemapContent) {
|
|
@@ -296,6 +297,12 @@ function setupPrerenderHandler(pageDataPath, siteInfo, llmsTxtConfig) {
|
|
|
296
297
|
const state = createCrawlerState(pageDataPath, llmsFullTxtPath, siteInfo, llmsTxtConfig);
|
|
297
298
|
let initPromise = null;
|
|
298
299
|
nitro.hooks.hook("prerender:generate", async (route) => {
|
|
300
|
+
if (route.error) {
|
|
301
|
+
const pageRoute2 = route.route.replace(/\.(html|md)$/, "").replace(/\/index$/, "") || "/";
|
|
302
|
+
state.errorRoutes.add(pageRoute2);
|
|
303
|
+
logger.debug(`Detected error page: ${pageRoute2}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
299
306
|
if (!route.fileName?.endsWith(".md"))
|
|
300
307
|
return;
|
|
301
308
|
let pageRoute = route.route.replace(/\.md$/, "");
|
|
@@ -341,6 +348,13 @@ function setupPrerenderHandler(pageDataPath, siteInfo, llmsTxtConfig) {
|
|
|
341
348
|
state.totalProcessingTime += Date.now() - pageStartTime;
|
|
342
349
|
});
|
|
343
350
|
async function writeLlmsFiles() {
|
|
351
|
+
if (state.pageDataPath && state.errorRoutes.size > 0) {
|
|
352
|
+
for (const route of state.errorRoutes) {
|
|
353
|
+
await appendFile(state.pageDataPath, `${JSON.stringify({ route, _error: true })}
|
|
354
|
+
`, "utf-8");
|
|
355
|
+
}
|
|
356
|
+
logger.debug(`Wrote ${state.errorRoutes.size} error routes to page data`);
|
|
357
|
+
}
|
|
344
358
|
const llmsStats = await prerenderRoute(nitro, "/llms.txt");
|
|
345
359
|
const llmsFullStats = await stat(state.llmsFullTxtPath);
|
|
346
360
|
const kb = (b) => (b / 1024).toFixed(1);
|
|
@@ -536,14 +550,18 @@ import { readFile } from 'node:fs/promises'
|
|
|
536
550
|
|
|
537
551
|
export async function readPageDataFromFilesystem() {
|
|
538
552
|
if (!import.meta.prerender) {
|
|
539
|
-
return
|
|
553
|
+
return { pages: [], errorRoutes: [] }
|
|
540
554
|
}
|
|
541
555
|
const data = await readFile(${JSON.stringify(pageDataPath)}, 'utf-8').catch(() => null)
|
|
542
|
-
if (!data) return []
|
|
543
|
-
|
|
556
|
+
if (!data) return { pages: [], errorRoutes: [] }
|
|
557
|
+
const entries = data.trim().split('\\n').filter(Boolean).map(line => JSON.parse(line))
|
|
558
|
+
const pages = entries.filter(e => !e._error)
|
|
559
|
+
const errorRoutes = entries.filter(e => e._error).map(e => e.route)
|
|
560
|
+
return { pages, errorRoutes }
|
|
544
561
|
}
|
|
545
562
|
`;
|
|
546
|
-
nitroConfig.virtual["#ai-ready-virtual/page-data.mjs"] = `export const pages = []
|
|
563
|
+
nitroConfig.virtual["#ai-ready-virtual/page-data.mjs"] = `export const pages = []
|
|
564
|
+
export const errorRoutes = []`;
|
|
547
565
|
});
|
|
548
566
|
nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
|
|
549
567
|
version: version || "0.0.0",
|
|
@@ -1,7 +1,123 @@
|
|
|
1
1
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
-
import { getPages } from "./server/utils/pageData.js";
|
|
3
|
+
import { getErrorRoutes, getPages } from "./server/utils/pageData.js";
|
|
4
4
|
import { fetchSitemapUrls } from "./server/utils/sitemap.js";
|
|
5
|
+
function getGroupPrefix(url, depth) {
|
|
6
|
+
const segments = url.split("/").filter(Boolean);
|
|
7
|
+
if (segments.length === 0)
|
|
8
|
+
return "/";
|
|
9
|
+
if (depth === 1 || segments.length === 1)
|
|
10
|
+
return `/${segments[0]}`;
|
|
11
|
+
return `/${segments[0]}/${segments[1]}`;
|
|
12
|
+
}
|
|
13
|
+
function getPathSegments(pathname) {
|
|
14
|
+
return pathname.split("/").filter((s) => Boolean(s));
|
|
15
|
+
}
|
|
16
|
+
function sortPagesByPath(pages) {
|
|
17
|
+
const twoSegmentCount = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const page of pages) {
|
|
19
|
+
const prefix = getGroupPrefix(page.pathname, 2);
|
|
20
|
+
twoSegmentCount.set(prefix, (twoSegmentCount.get(prefix) || 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
const segmentHasNested = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const page of pages) {
|
|
24
|
+
const segments = getPathSegments(page.pathname);
|
|
25
|
+
const firstSegment = segments[0] || "";
|
|
26
|
+
if (!segmentHasNested.has(firstSegment))
|
|
27
|
+
segmentHasNested.set(firstSegment, false);
|
|
28
|
+
if (segments.length > 1)
|
|
29
|
+
segmentHasNested.set(firstSegment, true);
|
|
30
|
+
}
|
|
31
|
+
return pages.sort((a, b) => {
|
|
32
|
+
const segmentsA = getPathSegments(a.pathname);
|
|
33
|
+
const segmentsB = getPathSegments(b.pathname);
|
|
34
|
+
const firstSegmentA = segmentsA[0] || "";
|
|
35
|
+
const firstSegmentB = segmentsB[0] || "";
|
|
36
|
+
const twoSegPrefixA = getGroupPrefix(a.pathname, 2);
|
|
37
|
+
const twoSegPrefixB = getGroupPrefix(b.pathname, 2);
|
|
38
|
+
const twoSegCountA = twoSegmentCount.get(twoSegPrefixA) || 0;
|
|
39
|
+
const twoSegCountB = twoSegmentCount.get(twoSegPrefixB) || 0;
|
|
40
|
+
let groupKeyA = twoSegCountA > 1 ? twoSegPrefixA : `/${firstSegmentA}`;
|
|
41
|
+
let groupKeyB = twoSegCountB > 1 ? twoSegPrefixB : `/${firstSegmentB}`;
|
|
42
|
+
const isRootLevelA = segmentsA.length <= 1;
|
|
43
|
+
const isRootLevelB = segmentsB.length <= 1;
|
|
44
|
+
const hasNestedA = segmentHasNested.get(firstSegmentA);
|
|
45
|
+
const hasNestedB = segmentHasNested.get(firstSegmentB);
|
|
46
|
+
if (isRootLevelA && !hasNestedA)
|
|
47
|
+
groupKeyA = "";
|
|
48
|
+
if (isRootLevelB && !hasNestedB)
|
|
49
|
+
groupKeyB = "";
|
|
50
|
+
if (groupKeyA === "" && groupKeyB !== "")
|
|
51
|
+
return -1;
|
|
52
|
+
if (groupKeyA !== "" && groupKeyB === "")
|
|
53
|
+
return 1;
|
|
54
|
+
if (groupKeyA !== groupKeyB)
|
|
55
|
+
return groupKeyA.localeCompare(groupKeyB);
|
|
56
|
+
if (segmentsA.length === 0)
|
|
57
|
+
return -1;
|
|
58
|
+
if (segmentsB.length === 0)
|
|
59
|
+
return 1;
|
|
60
|
+
const minLen = Math.min(segmentsA.length, segmentsB.length);
|
|
61
|
+
for (let i = 0; i < minLen; i++) {
|
|
62
|
+
const cmp = segmentsA[i].localeCompare(segmentsB[i]);
|
|
63
|
+
if (cmp !== 0)
|
|
64
|
+
return cmp;
|
|
65
|
+
}
|
|
66
|
+
return segmentsA.length - segmentsB.length;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function getPageGroupKey(pathname, twoSegmentCount, segmentHasNested) {
|
|
70
|
+
const segments = getPathSegments(pathname);
|
|
71
|
+
const firstSegment = segments[0] || "";
|
|
72
|
+
const twoSegPrefix = getGroupPrefix(pathname, 2);
|
|
73
|
+
const twoSegCount = twoSegmentCount.get(twoSegPrefix) || 0;
|
|
74
|
+
let groupKey = twoSegCount > 1 ? twoSegPrefix : `/${firstSegment}`;
|
|
75
|
+
const isRootLevel = segments.length <= 1;
|
|
76
|
+
const hasNested = segmentHasNested.get(firstSegment);
|
|
77
|
+
if (isRootLevel && !hasNested)
|
|
78
|
+
groupKey = "";
|
|
79
|
+
return groupKey;
|
|
80
|
+
}
|
|
81
|
+
function formatPagesWithGroups(pages) {
|
|
82
|
+
if (pages.length === 0)
|
|
83
|
+
return [];
|
|
84
|
+
const twoSegmentCount = /* @__PURE__ */ new Map();
|
|
85
|
+
const segmentHasNested = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const page of pages) {
|
|
87
|
+
const prefix = getGroupPrefix(page.pathname, 2);
|
|
88
|
+
twoSegmentCount.set(prefix, (twoSegmentCount.get(prefix) || 0) + 1);
|
|
89
|
+
const segments = getPathSegments(page.pathname);
|
|
90
|
+
const firstSegment = segments[0] || "";
|
|
91
|
+
if (!segmentHasNested.has(firstSegment))
|
|
92
|
+
segmentHasNested.set(firstSegment, false);
|
|
93
|
+
if (segments.length > 1)
|
|
94
|
+
segmentHasNested.set(firstSegment, true);
|
|
95
|
+
}
|
|
96
|
+
const lines = [];
|
|
97
|
+
let currentGroup = "";
|
|
98
|
+
let segmentGroupIndex = 0;
|
|
99
|
+
let urlsInCurrentGroup = 0;
|
|
100
|
+
for (const page of pages) {
|
|
101
|
+
const groupKey = getPageGroupKey(page.pathname, twoSegmentCount, segmentHasNested);
|
|
102
|
+
if (groupKey !== currentGroup) {
|
|
103
|
+
if (urlsInCurrentGroup > 0) {
|
|
104
|
+
const shouldAddBlankLine = segmentGroupIndex === 0 || segmentGroupIndex >= 1 && segmentGroupIndex <= 2 && urlsInCurrentGroup > 1;
|
|
105
|
+
if (shouldAddBlankLine)
|
|
106
|
+
lines.push("");
|
|
107
|
+
}
|
|
108
|
+
currentGroup = groupKey;
|
|
109
|
+
segmentGroupIndex++;
|
|
110
|
+
urlsInCurrentGroup = 0;
|
|
111
|
+
}
|
|
112
|
+
urlsInCurrentGroup++;
|
|
113
|
+
const descText = page.description ? `: ${page.description.substring(0, 160)}${page.description.length > 160 ? "..." : ""}` : "";
|
|
114
|
+
if (page.title && page.title !== page.pathname)
|
|
115
|
+
lines.push(`- [${page.title}](${page.pathname})${descText}`);
|
|
116
|
+
else
|
|
117
|
+
lines.push(`- ${page.pathname}${descText}`);
|
|
118
|
+
}
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
5
121
|
function normalizeLink(link) {
|
|
6
122
|
const parts = [];
|
|
7
123
|
parts.push(`- [${link.title}](${link.href})`);
|
|
@@ -71,43 +187,41 @@ Canonical Origin: ${siteConfig.url}`);
|
|
|
71
187
|
}
|
|
72
188
|
const pages = await getPages();
|
|
73
189
|
const urls = await fetchSitemapUrls(event);
|
|
190
|
+
const errorRoutes = await getErrorRoutes();
|
|
74
191
|
const devModeHint = import.meta.dev && pages.size === 0 ? " (dev mode - run `nuxi generate` for page titles)" : "";
|
|
75
192
|
const prerendered = [];
|
|
193
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
76
194
|
for (const [pathname, page] of pages) {
|
|
77
|
-
prerendered.push({ pathname, title: page.title });
|
|
195
|
+
prerendered.push({ pathname, title: page.title, description: page.description });
|
|
196
|
+
seenPaths.add(pathname);
|
|
78
197
|
}
|
|
79
198
|
const other = [];
|
|
80
199
|
for (const url of urls) {
|
|
81
200
|
const pathname = url.loc.startsWith("http") ? new URL(url.loc).pathname : url.loc;
|
|
82
|
-
if (!
|
|
83
|
-
other.push(pathname);
|
|
201
|
+
if (!seenPaths.has(pathname) && !errorRoutes.has(pathname)) {
|
|
202
|
+
other.push({ pathname });
|
|
203
|
+
seenPaths.add(pathname);
|
|
84
204
|
}
|
|
85
205
|
}
|
|
86
|
-
|
|
206
|
+
const sortedPrerendered = sortPagesByPath(prerendered);
|
|
207
|
+
const sortedOther = sortPagesByPath(other);
|
|
208
|
+
if (sortedPrerendered.length > 0 && sortedOther.length > 0) {
|
|
87
209
|
parts.push(`## Prerendered Pages${devModeHint}
|
|
88
210
|
`);
|
|
89
|
-
|
|
90
|
-
parts.push(title && title !== pathname ? `- [${title}](${pathname})` : `- ${pathname}`);
|
|
91
|
-
}
|
|
211
|
+
parts.push(...formatPagesWithGroups(sortedPrerendered));
|
|
92
212
|
parts.push("");
|
|
93
213
|
parts.push("## Other Pages\n");
|
|
94
|
-
|
|
95
|
-
parts.push(`- ${pathname}`);
|
|
96
|
-
}
|
|
214
|
+
parts.push(...formatPagesWithGroups(sortedOther));
|
|
97
215
|
parts.push("");
|
|
98
|
-
} else if (
|
|
216
|
+
} else if (sortedPrerendered.length > 0) {
|
|
99
217
|
parts.push(`## Pages${devModeHint}
|
|
100
218
|
`);
|
|
101
|
-
|
|
102
|
-
parts.push(title && title !== pathname ? `- [${title}](${pathname})` : `- ${pathname}`);
|
|
103
|
-
}
|
|
219
|
+
parts.push(...formatPagesWithGroups(sortedPrerendered));
|
|
104
220
|
parts.push("");
|
|
105
|
-
} else if (
|
|
221
|
+
} else if (sortedOther.length > 0) {
|
|
106
222
|
parts.push(`## Pages${devModeHint}
|
|
107
223
|
`);
|
|
108
|
-
|
|
109
|
-
parts.push(`- ${pathname}`);
|
|
110
|
-
}
|
|
224
|
+
parts.push(...formatPagesWithGroups(sortedOther));
|
|
111
225
|
parts.push("");
|
|
112
226
|
}
|
|
113
227
|
return parts.join("\n");
|
|
@@ -12,8 +12,6 @@ export interface PageData extends PageEntry {
|
|
|
12
12
|
}
|
|
13
13
|
/** Read page data - returns page data indexed by route */
|
|
14
14
|
export declare function getPages(): Promise<Map<string, PageEntry>>;
|
|
15
|
-
/** Get all page data including markdown (prerender only) */
|
|
16
|
-
export declare function readPrerenderedPageData(): Promise<Map<string, PageData>>;
|
|
17
15
|
/** Page list item for MCP tools/resources */
|
|
18
16
|
export interface PageListItem {
|
|
19
17
|
route: string;
|
|
@@ -23,3 +21,5 @@ export interface PageListItem {
|
|
|
23
21
|
}
|
|
24
22
|
/** Get pages as flat list for MCP consumption */
|
|
25
23
|
export declare function getPagesList(): Promise<PageListItem[]>;
|
|
24
|
+
/** Get error routes detected during prerender */
|
|
25
|
+
export declare function getErrorRoutes(): Promise<Set<string>>;
|
|
@@ -2,17 +2,21 @@ export async function getPages() {
|
|
|
2
2
|
if (import.meta.dev)
|
|
3
3
|
return /* @__PURE__ */ new Map();
|
|
4
4
|
if (import.meta.prerender) {
|
|
5
|
-
|
|
5
|
+
const data = await readPrerenderedData();
|
|
6
|
+
return data.pages;
|
|
6
7
|
}
|
|
7
8
|
const m = await import("#ai-ready-virtual/page-data.mjs");
|
|
8
9
|
return m.pages?.length ? new Map(m.pages.map((p) => [p.route, p])) : /* @__PURE__ */ new Map();
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
async function readPrerenderedData() {
|
|
11
12
|
if (!import.meta.prerender)
|
|
12
|
-
return /* @__PURE__ */ new Map();
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
return
|
|
13
|
+
return { pages: /* @__PURE__ */ new Map(), errorRoutes: /* @__PURE__ */ new Set() };
|
|
14
|
+
const m = await import("#ai-ready-virtual/read-page-data.mjs");
|
|
15
|
+
const data = await m.readPageDataFromFilesystem();
|
|
16
|
+
return {
|
|
17
|
+
pages: data.pages?.length ? new Map(data.pages.map((p) => [p.route, p])) : /* @__PURE__ */ new Map(),
|
|
18
|
+
errorRoutes: new Set(data.errorRoutes || [])
|
|
19
|
+
};
|
|
16
20
|
}
|
|
17
21
|
export async function getPagesList() {
|
|
18
22
|
const pages = await getPages();
|
|
@@ -23,3 +27,13 @@ export async function getPagesList() {
|
|
|
23
27
|
headings: p.headings || void 0
|
|
24
28
|
}));
|
|
25
29
|
}
|
|
30
|
+
export async function getErrorRoutes() {
|
|
31
|
+
if (import.meta.dev)
|
|
32
|
+
return /* @__PURE__ */ new Set();
|
|
33
|
+
if (import.meta.prerender) {
|
|
34
|
+
const data = await readPrerenderedData();
|
|
35
|
+
return data.errorRoutes;
|
|
36
|
+
}
|
|
37
|
+
const m = await import("#ai-ready-virtual/page-data.mjs");
|
|
38
|
+
return new Set(m.errorRoutes || []);
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
declare const _default: {
|
|
2
|
-
uri: string;
|
|
3
|
-
name: string;
|
|
4
|
-
description: string;
|
|
5
|
-
metadata: {
|
|
6
|
-
mimeType: string;
|
|
7
|
-
};
|
|
8
|
-
cache: "1h";
|
|
9
|
-
handler(uri: URL): Promise<{
|
|
10
|
-
contents: {
|
|
11
|
-
uri: string;
|
|
12
|
-
mimeType: string;
|
|
13
|
-
text: string;
|
|
14
|
-
}[];
|
|
15
|
-
}>;
|
|
16
|
-
};
|
|
17
|
-
export default _default;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lists all pages with metadata
|
|
3
|
-
*/
|
|
4
|
-
declare const _default: {
|
|
5
|
-
name: string;
|
|
6
|
-
description: string;
|
|
7
|
-
inputSchema: {};
|
|
8
|
-
cache: "1h";
|
|
9
|
-
handler(): Promise<{
|
|
10
|
-
content: {
|
|
11
|
-
type: "text";
|
|
12
|
-
text: string;
|
|
13
|
-
}[];
|
|
14
|
-
}>;
|
|
15
|
-
};
|
|
16
|
-
export default _default;
|