nuxt-ai-ready 0.4.3 → 0.5.0
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 +48 -10
- package/dist/runtime/server/routes/__ai-ready-debug.get.d.ts +46 -0
- package/dist/runtime/server/routes/__ai-ready-debug.get.js +113 -0
- package/dist/runtime/server/utils/pageData.d.ts +4 -4
- package/dist/runtime/server/utils/pageData.js +27 -24
- package/package.json +5 -5
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, mkdir, writeFile, stat, access } from 'node:fs/promises';
|
|
1
|
+
import { appendFile, mkdir, writeFile, readFile, stat, access } from 'node:fs/promises';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { useLogger, useNuxt, defineNuxtModule, createResolver, addTypeTemplate, hasNuxtModule, addServerHandler, addPlugin } from '@nuxt/kit';
|
|
4
4
|
import defu from 'defu';
|
|
@@ -227,7 +227,13 @@ async function crawlSitemapEntries(state, nuxt, nitro, entries) {
|
|
|
227
227
|
});
|
|
228
228
|
if (!res)
|
|
229
229
|
continue;
|
|
230
|
-
|
|
230
|
+
let parsed;
|
|
231
|
+
try {
|
|
232
|
+
parsed = JSON.parse(res);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.debug(`Skipping ${route}: Response is not JSON (likely HTML instead of markdown conversion)`, err);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
231
237
|
await processMarkdownRoute(state, nuxt, route, parsed, lastmod);
|
|
232
238
|
crawled++;
|
|
233
239
|
}
|
|
@@ -355,6 +361,26 @@ function setupPrerenderHandler(pageDataPath, siteInfo, llmsTxtConfig) {
|
|
|
355
361
|
}
|
|
356
362
|
logger.debug(`Wrote ${state.errorRoutes.size} error routes to page data`);
|
|
357
363
|
}
|
|
364
|
+
if (state.pageDataPath) {
|
|
365
|
+
const jsonlContent = await readFile(state.pageDataPath, "utf-8").catch(() => "");
|
|
366
|
+
if (jsonlContent) {
|
|
367
|
+
const entries = jsonlContent.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
368
|
+
const pages = entries.filter((e) => !e._error).map((p) => ({
|
|
369
|
+
route: p.route,
|
|
370
|
+
title: p.title,
|
|
371
|
+
description: p.description,
|
|
372
|
+
headings: p.headings,
|
|
373
|
+
updatedAt: p.updatedAt
|
|
374
|
+
}));
|
|
375
|
+
const errorRoutes = entries.filter((e) => e._error).map((e) => e.route);
|
|
376
|
+
const jsonContent = JSON.stringify({ pages, errorRoutes });
|
|
377
|
+
const serverAssetsDir = join(nitro.options.output.serverDir, "chunks/raw/ai-ready-data");
|
|
378
|
+
await mkdir(serverAssetsDir, { recursive: true });
|
|
379
|
+
const serverAssetsJsonPath = join(serverAssetsDir, "pages.json");
|
|
380
|
+
await writeFile(serverAssetsJsonPath, jsonContent, "utf-8");
|
|
381
|
+
logger.debug(`Wrote ${pages.length} pages to server assets`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
358
384
|
const llmsStats = await prerenderRoute(nitro, "/llms.txt");
|
|
359
385
|
const llmsFullStats = await stat(state.llmsFullTxtPath);
|
|
360
386
|
const kb = (b) => (b / 1024).toFixed(1);
|
|
@@ -474,14 +500,17 @@ declare module 'nitropack/types' {
|
|
|
474
500
|
}
|
|
475
501
|
|
|
476
502
|
declare module '#ai-ready-virtual/read-page-data.mjs' {
|
|
477
|
-
export function readPageDataFromFilesystem(): Promise<
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
503
|
+
export function readPageDataFromFilesystem(): Promise<{
|
|
504
|
+
pages: Array<{
|
|
505
|
+
route: string
|
|
506
|
+
title: string
|
|
507
|
+
description: string
|
|
508
|
+
headings: string
|
|
509
|
+
updatedAt: string
|
|
510
|
+
markdown: string
|
|
511
|
+
}>
|
|
512
|
+
errorRoutes: string[]
|
|
513
|
+
}>
|
|
485
514
|
}
|
|
486
515
|
|
|
487
516
|
declare module '#ai-ready-virtual/page-data.mjs' {
|
|
@@ -562,6 +591,12 @@ export async function readPageDataFromFilesystem() {
|
|
|
562
591
|
`;
|
|
563
592
|
nitroConfig.virtual["#ai-ready-virtual/page-data.mjs"] = `export const pages = []
|
|
564
593
|
export const errorRoutes = []`;
|
|
594
|
+
nitroConfig.serverAssets = nitroConfig.serverAssets || [];
|
|
595
|
+
nitroConfig.serverAssets.push({
|
|
596
|
+
baseName: "ai-ready-data",
|
|
597
|
+
dir: "./.nitro/ai-ready-data"
|
|
598
|
+
// Placeholder, actual data written during prerender
|
|
599
|
+
});
|
|
565
600
|
});
|
|
566
601
|
nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
|
|
567
602
|
version: version || "0.0.0",
|
|
@@ -591,6 +626,9 @@ export const errorRoutes = []`;
|
|
|
591
626
|
}
|
|
592
627
|
addServerHandler({ route: "/llms.txt", handler: resolve("./runtime/server/routes/llms.txt.get") });
|
|
593
628
|
addServerHandler({ route: "/llms-full.txt", handler: resolve("./runtime/server/routes/llms-full.txt.get") });
|
|
629
|
+
if (config.debug) {
|
|
630
|
+
addServerHandler({ route: "/__ai-ready-debug", handler: resolve("./runtime/server/routes/__ai-ready-debug.get") });
|
|
631
|
+
}
|
|
594
632
|
const isStatic = nuxt.options.nitro.static || nuxt.options._generate || false;
|
|
595
633
|
const hasPrerenderedRoutes = nuxt.options.nitro.prerender?.routes?.length;
|
|
596
634
|
const isSPA = nuxt.options.ssr === false;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
interface DebugInfo {
|
|
2
|
+
version: string;
|
|
3
|
+
environment: {
|
|
4
|
+
isDev: boolean;
|
|
5
|
+
isPrerender: boolean;
|
|
6
|
+
mode: 'development' | 'prerender' | 'production';
|
|
7
|
+
};
|
|
8
|
+
config: {
|
|
9
|
+
debug: boolean;
|
|
10
|
+
cacheMaxAgeSeconds: number;
|
|
11
|
+
mdreamOptions: unknown;
|
|
12
|
+
};
|
|
13
|
+
pageData: {
|
|
14
|
+
source: string;
|
|
15
|
+
pageCount: number;
|
|
16
|
+
pages: Array<{
|
|
17
|
+
route: string;
|
|
18
|
+
title: string;
|
|
19
|
+
hasDescription: boolean;
|
|
20
|
+
hasHeadings: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
errorRoutes: string[];
|
|
23
|
+
};
|
|
24
|
+
jsonFile: {
|
|
25
|
+
available: boolean;
|
|
26
|
+
pageCount: number;
|
|
27
|
+
source: string;
|
|
28
|
+
};
|
|
29
|
+
virtualModules: {
|
|
30
|
+
pageDataModule: {
|
|
31
|
+
available: boolean;
|
|
32
|
+
pagesCount: number;
|
|
33
|
+
errorRoutesCount: number;
|
|
34
|
+
};
|
|
35
|
+
readPageDataModule: {
|
|
36
|
+
available: boolean;
|
|
37
|
+
note: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
diagnostics: {
|
|
41
|
+
issues: string[];
|
|
42
|
+
suggestions: string[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<DebugInfo>>;
|
|
46
|
+
export default _default;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createError, eventHandler, setHeader } from "h3";
|
|
2
|
+
import { useRuntimeConfig, useStorage } from "nitropack/runtime";
|
|
3
|
+
import { getErrorRoutes, getPages, getPagesList } from "../utils/pageData.js";
|
|
4
|
+
export default eventHandler(async (event) => {
|
|
5
|
+
const runtimeConfig = useRuntimeConfig(event)["nuxt-ai-ready"];
|
|
6
|
+
if (!runtimeConfig.debug) {
|
|
7
|
+
throw createError({
|
|
8
|
+
statusCode: 404,
|
|
9
|
+
message: "Not Found"
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const isDev = import.meta.dev;
|
|
13
|
+
const isPrerender = import.meta.prerender ?? false;
|
|
14
|
+
let mode;
|
|
15
|
+
if (isDev) {
|
|
16
|
+
mode = "development";
|
|
17
|
+
} else if (isPrerender) {
|
|
18
|
+
mode = "prerender";
|
|
19
|
+
} else {
|
|
20
|
+
mode = "production";
|
|
21
|
+
}
|
|
22
|
+
const pages = await getPages();
|
|
23
|
+
const pagesList = await getPagesList();
|
|
24
|
+
const errorRoutes = await getErrorRoutes();
|
|
25
|
+
let source;
|
|
26
|
+
if (isDev) {
|
|
27
|
+
source = "empty (dev mode returns empty Map)";
|
|
28
|
+
} else if (isPrerender) {
|
|
29
|
+
source = "#ai-ready-virtual/read-page-data.mjs (reads from filesystem)";
|
|
30
|
+
} else {
|
|
31
|
+
source = "useStorage('assets:ai-ready-data') (server assets)";
|
|
32
|
+
}
|
|
33
|
+
let pageDataModuleInfo = { available: false, pagesCount: 0, errorRoutesCount: 0 };
|
|
34
|
+
let readPageDataModuleInfo = { available: false, note: "" };
|
|
35
|
+
try {
|
|
36
|
+
const m = await import("#ai-ready-virtual/page-data.mjs");
|
|
37
|
+
pageDataModuleInfo = {
|
|
38
|
+
available: true,
|
|
39
|
+
pagesCount: Array.isArray(m.pages) ? m.pages.length : 0,
|
|
40
|
+
errorRoutesCount: Array.isArray(m.errorRoutes) ? m.errorRoutes.length : 0
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
pageDataModuleInfo.available = false;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const m = await import("#ai-ready-virtual/read-page-data.mjs");
|
|
47
|
+
readPageDataModuleInfo = {
|
|
48
|
+
available: typeof m.readPageDataFromFilesystem === "function",
|
|
49
|
+
note: isPrerender ? "Active - reading from filesystem" : "Available but only works during prerender"
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
readPageDataModuleInfo = { available: false, note: "Module not available" };
|
|
53
|
+
}
|
|
54
|
+
const storage = useStorage("assets:ai-ready-data");
|
|
55
|
+
const storageData = await storage.getItem("pages.json");
|
|
56
|
+
const jsonFileStatus = {
|
|
57
|
+
available: !!storageData,
|
|
58
|
+
pageCount: storageData?.pages?.length ?? 0,
|
|
59
|
+
source: "useStorage('assets:ai-ready-data')"
|
|
60
|
+
};
|
|
61
|
+
const issues = [];
|
|
62
|
+
const suggestions = [];
|
|
63
|
+
if (mode === "development") {
|
|
64
|
+
issues.push("Development mode: page data is intentionally empty");
|
|
65
|
+
suggestions.push("Run `nuxi generate` or `nuxi build --prerender` to generate page data");
|
|
66
|
+
} else if (mode === "production" && pages.size === 0) {
|
|
67
|
+
if (!jsonFileStatus.available) {
|
|
68
|
+
issues.push("Production mode with no page data - server assets not found");
|
|
69
|
+
suggestions.push("Run `nuxi generate` or `nuxi build --prerender` to generate the page data");
|
|
70
|
+
} else {
|
|
71
|
+
issues.push("Server assets exist but returned empty page data");
|
|
72
|
+
suggestions.push("Check if pages were prerendered correctly");
|
|
73
|
+
}
|
|
74
|
+
} else if (mode === "prerender" && pages.size === 0) {
|
|
75
|
+
issues.push("Prerender mode but no pages found");
|
|
76
|
+
suggestions.push("Check if page-data.jsonl exists in .nuxt/.data/ai-ready/");
|
|
77
|
+
}
|
|
78
|
+
const debugInfo = {
|
|
79
|
+
version: runtimeConfig.version || "unknown",
|
|
80
|
+
environment: {
|
|
81
|
+
isDev,
|
|
82
|
+
isPrerender,
|
|
83
|
+
mode
|
|
84
|
+
},
|
|
85
|
+
config: {
|
|
86
|
+
debug: runtimeConfig.debug,
|
|
87
|
+
cacheMaxAgeSeconds: runtimeConfig.cacheMaxAgeSeconds,
|
|
88
|
+
mdreamOptions: runtimeConfig.mdreamOptions
|
|
89
|
+
},
|
|
90
|
+
pageData: {
|
|
91
|
+
source,
|
|
92
|
+
pageCount: pages.size,
|
|
93
|
+
pages: pagesList.map((p) => ({
|
|
94
|
+
route: p.route,
|
|
95
|
+
title: p.title,
|
|
96
|
+
hasDescription: !!p.description,
|
|
97
|
+
hasHeadings: !!p.headings
|
|
98
|
+
})),
|
|
99
|
+
errorRoutes: Array.from(errorRoutes)
|
|
100
|
+
},
|
|
101
|
+
jsonFile: jsonFileStatus,
|
|
102
|
+
virtualModules: {
|
|
103
|
+
pageDataModule: pageDataModuleInfo,
|
|
104
|
+
readPageDataModule: readPageDataModuleInfo
|
|
105
|
+
},
|
|
106
|
+
diagnostics: {
|
|
107
|
+
issues,
|
|
108
|
+
suggestions
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
setHeader(event, "Content-Type", "application/json; charset=utf-8");
|
|
112
|
+
return debugInfo;
|
|
113
|
+
});
|
|
@@ -10,8 +10,6 @@ export interface PageEntry {
|
|
|
10
10
|
export interface PageData extends PageEntry {
|
|
11
11
|
markdown: string;
|
|
12
12
|
}
|
|
13
|
-
/** Read page data - returns page data indexed by route */
|
|
14
|
-
export declare function getPages(): Promise<Map<string, PageEntry>>;
|
|
15
13
|
/** Page list item for MCP tools/resources */
|
|
16
14
|
export interface PageListItem {
|
|
17
15
|
route: string;
|
|
@@ -19,7 +17,9 @@ export interface PageListItem {
|
|
|
19
17
|
description: string;
|
|
20
18
|
headings?: string;
|
|
21
19
|
}
|
|
22
|
-
/**
|
|
23
|
-
export declare function
|
|
20
|
+
/** Read page data - returns page data indexed by route */
|
|
21
|
+
export declare function getPages(): Promise<Map<string, PageEntry>>;
|
|
24
22
|
/** Get error routes detected during prerender */
|
|
25
23
|
export declare function getErrorRoutes(): Promise<Set<string>>;
|
|
24
|
+
/** Get pages as flat list for MCP consumption */
|
|
25
|
+
export declare function getPagesList(): Promise<PageListItem[]>;
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
+
import { useStorage } from "nitropack/runtime";
|
|
1
2
|
export async function getPages() {
|
|
2
3
|
if (import.meta.dev)
|
|
3
4
|
return /* @__PURE__ */ new Map();
|
|
4
|
-
if (import.meta.prerender)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
const m = await import("#ai-ready-virtual/page-data.mjs");
|
|
9
|
-
return m.pages?.length ? new Map(m.pages.map((p) => [p.route, p])) : /* @__PURE__ */ new Map();
|
|
5
|
+
if (import.meta.prerender)
|
|
6
|
+
return (await readPrerenderedData()).pages;
|
|
7
|
+
return (await readServerAssets()).pages;
|
|
10
8
|
}
|
|
11
|
-
async function
|
|
12
|
-
if (
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
};
|
|
9
|
+
export async function getErrorRoutes() {
|
|
10
|
+
if (import.meta.dev)
|
|
11
|
+
return /* @__PURE__ */ new Set();
|
|
12
|
+
if (import.meta.prerender)
|
|
13
|
+
return (await readPrerenderedData()).errorRoutes;
|
|
14
|
+
return (await readServerAssets()).errorRoutes;
|
|
20
15
|
}
|
|
21
16
|
export async function getPagesList() {
|
|
22
17
|
const pages = await getPages();
|
|
@@ -27,13 +22,21 @@ export async function getPagesList() {
|
|
|
27
22
|
headings: p.headings || void 0
|
|
28
23
|
}));
|
|
29
24
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
async function readServerAssets() {
|
|
26
|
+
const storage = useStorage("assets:ai-ready-data");
|
|
27
|
+
const data = await storage.getItem("pages.json");
|
|
28
|
+
if (!data)
|
|
29
|
+
return { pages: /* @__PURE__ */ new Map(), errorRoutes: /* @__PURE__ */ new Set() };
|
|
30
|
+
return {
|
|
31
|
+
pages: new Map(data.pages?.map((p) => [p.route, p]) || []),
|
|
32
|
+
errorRoutes: new Set(data.errorRoutes || [])
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function readPrerenderedData() {
|
|
36
|
+
const m = await import("#ai-ready-virtual/read-page-data.mjs");
|
|
37
|
+
const data = await m.readPageDataFromFilesystem();
|
|
38
|
+
return {
|
|
39
|
+
pages: new Map(data.pages?.map((p) => [p.route, p]) || []),
|
|
40
|
+
errorRoutes: new Set(data.errorRoutes || [])
|
|
41
|
+
};
|
|
39
42
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
"unstorage": "^1.17.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@antfu/eslint-config": "^6.7.
|
|
53
|
+
"@antfu/eslint-config": "^6.7.3",
|
|
54
54
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
55
55
|
"@headlessui/vue": "^1.7.23",
|
|
56
|
-
"@nuxt/content": "^3.
|
|
56
|
+
"@nuxt/content": "^3.10.0",
|
|
57
57
|
"@nuxt/devtools-ui-kit": "^3.1.1",
|
|
58
58
|
"@nuxt/module-builder": "^1.0.2",
|
|
59
59
|
"@nuxt/test-utils": "^3.21.0",
|
|
60
60
|
"@nuxtjs/color-mode": "^4.0.0",
|
|
61
61
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
|
62
62
|
"@nuxtjs/i18n": "^10.2.1",
|
|
63
|
-
"@nuxtjs/mcp-toolkit": "^0.
|
|
63
|
+
"@nuxtjs/mcp-toolkit": "^0.6.0",
|
|
64
64
|
"@nuxtjs/robots": "^5.6.7",
|
|
65
65
|
"@nuxtjs/sitemap": "7.5.0",
|
|
66
66
|
"@vitest/coverage-v8": "^4.0.16",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"vitest": "^4.0.16",
|
|
80
80
|
"vue": "^3.5.26",
|
|
81
81
|
"vue-router": "^4.6.4",
|
|
82
|
-
"vue-tsc": "^3.1
|
|
82
|
+
"vue-tsc": "^3.2.1",
|
|
83
83
|
"zod": "^4.2.1"
|
|
84
84
|
},
|
|
85
85
|
"resolutions": {
|