nuxt-ai-ready 1.7.13 → 1.8.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.
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { realpath, readFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve, isAbsolute, relative, sep } from 'node:path';
|
|
4
4
|
import { parseDocument } from 'yaml';
|
|
5
|
-
import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.
|
|
5
|
+
import { A as AGENT_SKILLS_SCHEMA } from '../shared/nuxt-ai-ready.B6l-68o5.mjs';
|
|
6
6
|
import 'node:module';
|
|
7
7
|
import '@nuxt/kit';
|
|
8
8
|
import 'defu';
|
|
@@ -5,7 +5,7 @@ import { colorize } from 'consola/utils';
|
|
|
5
5
|
import { resolveLocaleFromRoute } from 'nuxtseo-shared/i18n-runtime';
|
|
6
6
|
import { collectSitemap } from 'sitemapd/parse';
|
|
7
7
|
import { withLeadingSlash, withBase, joinURL } from 'ufo';
|
|
8
|
-
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.
|
|
8
|
+
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.B6l-68o5.mjs';
|
|
9
9
|
import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
|
|
10
10
|
import { toLogicalRoute, toDeployedRoute } from '../../dist/runtime/route-path.js';
|
|
11
11
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
|
|
@@ -137,38 +137,52 @@ async function initCrawler(state) {
|
|
|
137
137
|
}
|
|
138
138
|
state.initialized = true;
|
|
139
139
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
},
|
|
154
|
-
close: async () => {
|
|
155
|
-
sqlite2.close();
|
|
156
|
-
}
|
|
157
|
-
};
|
|
140
|
+
const PRERENDER_PRAGMAS = [
|
|
141
|
+
"PRAGMA synchronous = OFF",
|
|
142
|
+
"PRAGMA temp_store = MEMORY"
|
|
143
|
+
];
|
|
144
|
+
function createSqliteAdapter(sqlite) {
|
|
145
|
+
const statements = /* @__PURE__ */ new Map();
|
|
146
|
+
function prepare(sql) {
|
|
147
|
+
let statement = statements.get(sql);
|
|
148
|
+
if (!statement) {
|
|
149
|
+
statement = sqlite.prepare(sql);
|
|
150
|
+
statements.set(sql, statement);
|
|
151
|
+
}
|
|
152
|
+
return statement;
|
|
158
153
|
}
|
|
159
|
-
const { default: Database } = await import('better-sqlite3');
|
|
160
|
-
const sqlite = new Database(dbPath);
|
|
161
154
|
return {
|
|
162
|
-
all: async (sql, params = []) =>
|
|
163
|
-
first: async (sql, params = []) =>
|
|
155
|
+
all: async (sql, params = []) => prepare(sql).all(...params),
|
|
156
|
+
first: async (sql, params = []) => prepare(sql).get(...params),
|
|
164
157
|
exec: async (sql, params = []) => {
|
|
165
|
-
|
|
158
|
+
if (params.length) {
|
|
159
|
+
prepare(sql).run(...params);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
statements.clear();
|
|
163
|
+
sqlite.exec(sql);
|
|
166
164
|
},
|
|
167
165
|
close: async () => {
|
|
166
|
+
statements.clear();
|
|
168
167
|
sqlite.close();
|
|
169
168
|
}
|
|
170
169
|
};
|
|
171
170
|
}
|
|
171
|
+
async function createPrerenderDatabase(dbPath) {
|
|
172
|
+
const nodeVersion = Number.parseInt(process.versions.node?.split(".")[0] || "0");
|
|
173
|
+
if (nodeVersion >= 22) {
|
|
174
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
175
|
+
const sqlite2 = new DatabaseSync(dbPath);
|
|
176
|
+
for (const pragma of PRERENDER_PRAGMAS)
|
|
177
|
+
sqlite2.exec(pragma);
|
|
178
|
+
return createSqliteAdapter(sqlite2);
|
|
179
|
+
}
|
|
180
|
+
const { default: Database } = await import('better-sqlite3');
|
|
181
|
+
const sqlite = new Database(dbPath);
|
|
182
|
+
for (const pragma of PRERENDER_PRAGMAS)
|
|
183
|
+
sqlite.exec(pragma);
|
|
184
|
+
return createSqliteAdapter(sqlite);
|
|
185
|
+
}
|
|
172
186
|
function flattenHeadings(headings) {
|
|
173
187
|
return (headings || []).map((h) => Object.entries(h).map(([tag, text]) => `${tag}:${text}`).join("")).join("|");
|
|
174
188
|
}
|
|
@@ -224,6 +238,7 @@ async function processSitemapEntry(state, nuxt, nitro, entry) {
|
|
|
224
238
|
logger.debug(`Fetching markdown for ${route} \u2192 ${mdUrl}`);
|
|
225
239
|
const res = await globalThis.$fetch(mdUrl, {
|
|
226
240
|
headers: { "x-nitro-prerender": mdRoute },
|
|
241
|
+
retry: 0,
|
|
227
242
|
signal: AbortSignal.timeout(PRERENDER_PAGE_TIMEOUT)
|
|
228
243
|
}).catch((err) => {
|
|
229
244
|
if (err?.name === "TimeoutError" || err?.name === "AbortError")
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import 'defu';
|
|
|
7
7
|
import 'nuxt-site-config/kit';
|
|
8
8
|
import 'nuxtseo-shared/kit';
|
|
9
9
|
import 'pkg-types';
|
|
10
|
-
export { m as default } from './shared/nuxt-ai-ready.
|
|
10
|
+
export { m as default } from './shared/nuxt-ai-ready.B6l-68o5.mjs';
|
|
11
11
|
import '../dist/runtime/server/utils/discovery-response.js';
|
|
12
12
|
import 'node:url';
|
|
13
13
|
import 'pathe';
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -113,6 +113,19 @@ export interface ModuleOptions {
|
|
|
113
113
|
* @default undefined
|
|
114
114
|
*/
|
|
115
115
|
apiCatalog?: false | ApiCatalogConfig;
|
|
116
|
+
/**
|
|
117
|
+
* Read a page's Markdown from the installed content module instead of
|
|
118
|
+
* rendering the page and converting its HTML.
|
|
119
|
+
*
|
|
120
|
+
* Supports @nuxt/content v3 and @harlan-zw/comark-content. When a content
|
|
121
|
+
* module is present this skips one SSR render per route and keeps the source
|
|
122
|
+
* Markdown intact, so MDC blocks, tables and code fences survive.
|
|
123
|
+
*
|
|
124
|
+
* Set to false to convert the rendered HTML instead. Do that when a page's
|
|
125
|
+
* meaning depends on what its components render rather than on its source.
|
|
126
|
+
* @default true
|
|
127
|
+
*/
|
|
128
|
+
contentSource?: boolean;
|
|
116
129
|
/**
|
|
117
130
|
* Options to pass to mdream htmlToMarkdown function
|
|
118
131
|
*/
|
|
@@ -2,11 +2,11 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
2
2
|
import { readFile, mkdir, writeFile, access } from 'node:fs/promises';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, basename, join } from 'node:path';
|
|
5
|
-
import { useLogger, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, addServerImports,
|
|
5
|
+
import { hasNuxtModule, useNuxt, useLogger, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, addServerImports, addServerPlugin, addServerHandler, extendRouteRules, addPlugin, addImports } from '@nuxt/kit';
|
|
6
6
|
import defu from 'defu';
|
|
7
7
|
import { installNuxtSiteConfig, useSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
|
|
8
|
-
import { renderNitroTypeAugmentations, setupNitroRuntimeCompatibility
|
|
9
|
-
import {
|
|
8
|
+
import { resolveNuxtContentVersion, renderNitroTypeAugmentations, setupNitroRuntimeCompatibility } from 'nuxtseo-shared/kit';
|
|
9
|
+
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
11
|
import { MCP_SERVER_CARD_MEDIA_TYPE, AI_CATALOG_MEDIA_TYPE } from '../../dist/runtime/server/utils/discovery-response.js';
|
|
12
12
|
import { isAbsolute, join as join$1 } from 'pathe';
|
|
@@ -77,6 +77,93 @@ export async function readPageDataFromFilesystem() {
|
|
|
77
77
|
`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
const COMARK_MODULE = "@harlan-zw/comark-content";
|
|
81
|
+
const COMARK_MINIMUM = [0, 1, 2];
|
|
82
|
+
async function comarkSatisfiesMinimum() {
|
|
83
|
+
const url = useNuxt().options.rootDir;
|
|
84
|
+
const pkg = await readPackageJSON(COMARK_MODULE, { url }).catch(() => {
|
|
85
|
+
return null;
|
|
86
|
+
});
|
|
87
|
+
const parts = pkg?.version?.split(".").map((part) => Number.parseInt(part, 10));
|
|
88
|
+
if (!parts || parts.length < 3 || parts.some((part) => !Number.isFinite(part)))
|
|
89
|
+
return false;
|
|
90
|
+
for (const [index, minimum] of COMARK_MINIMUM.entries()) {
|
|
91
|
+
if (parts[index] !== minimum)
|
|
92
|
+
return parts[index] > minimum;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
async function resolveContentSource(enabled) {
|
|
97
|
+
if (!enabled)
|
|
98
|
+
return { _tag: "None" };
|
|
99
|
+
const contentVersion = await resolveNuxtContentVersion();
|
|
100
|
+
if (contentVersion && contentVersion.version === 3)
|
|
101
|
+
return { _tag: "NuxtContentV3" };
|
|
102
|
+
if (hasNuxtModule(COMARK_MODULE) && await comarkSatisfiesMinimum())
|
|
103
|
+
return { _tag: "ComarkContent" };
|
|
104
|
+
return { _tag: "None" };
|
|
105
|
+
}
|
|
106
|
+
const NUXT_CONTENT_V3_LOOKUP = `
|
|
107
|
+
import { queryCollection } from '@nuxt/content/server'
|
|
108
|
+
import manifest from '#content/manifest'
|
|
109
|
+
import { stringify } from 'minimark/stringify'
|
|
110
|
+
|
|
111
|
+
const pageCollections = Object.entries(manifest)
|
|
112
|
+
.filter(([, info]) => info.type === 'page')
|
|
113
|
+
.map(([name]) => name)
|
|
114
|
+
|
|
115
|
+
export async function lookupContentPage(event, path) {
|
|
116
|
+
if (!pageCollections.length) return null
|
|
117
|
+
const candidates = path === '/' ? ['/'] : [path, path.replace(/\\/$/, '')]
|
|
118
|
+
for (const collection of pageCollections) {
|
|
119
|
+
for (const candidate of candidates) {
|
|
120
|
+
const page = await queryCollection(event, collection).path(candidate).first().catch(() => null)
|
|
121
|
+
if (!page) continue
|
|
122
|
+
const markdown = stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
|
|
123
|
+
return {
|
|
124
|
+
markdown,
|
|
125
|
+
title: page.title,
|
|
126
|
+
description: page.description,
|
|
127
|
+
updatedAt: page.seo?.articleModifiedTime || page.updatedAt,
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
const COMARK_LOOKUP = `
|
|
135
|
+
import { queryCollection, queryCollectionManifest, renderPageMarkdown } from '@harlan-zw/comark-content/server'
|
|
136
|
+
|
|
137
|
+
let manifestPromise
|
|
138
|
+
|
|
139
|
+
export async function lookupContentPage(event, path) {
|
|
140
|
+
manifestPromise ??= queryCollectionManifest(event)
|
|
141
|
+
const manifest = await manifestPromise
|
|
142
|
+
for (const { name } of manifest) {
|
|
143
|
+
const page = await queryCollection(event, name).path(path).first()
|
|
144
|
+
if (!page) continue
|
|
145
|
+
return {
|
|
146
|
+
markdown: await renderPageMarkdown(page.body),
|
|
147
|
+
title: page.title,
|
|
148
|
+
description: page.description,
|
|
149
|
+
updatedAt: page.seo?.articleModifiedTime || page.updatedAt,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null
|
|
153
|
+
}
|
|
154
|
+
`;
|
|
155
|
+
const NO_CONTENT_LOOKUP = `export async function lookupContentPage() { return null }`;
|
|
156
|
+
function contentLookupModule(source) {
|
|
157
|
+
switch (source._tag) {
|
|
158
|
+
case "NuxtContentV3":
|
|
159
|
+
return NUXT_CONTENT_V3_LOOKUP;
|
|
160
|
+
case "ComarkContent":
|
|
161
|
+
return COMARK_LOOKUP;
|
|
162
|
+
case "None":
|
|
163
|
+
return NO_CONTENT_LOOKUP;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
80
167
|
const logger = useLogger("nuxt-ai-ready");
|
|
81
168
|
|
|
82
169
|
function resolveModuleEntryUrl(url) {
|
|
@@ -1043,8 +1130,7 @@ ${details}`);
|
|
|
1043
1130
|
if (mcpConfig.resources !== false)
|
|
1044
1131
|
(paths.resources ||= []).push(`${mcpRuntimeDir}/resources`);
|
|
1045
1132
|
});
|
|
1046
|
-
const
|
|
1047
|
-
const hasNuxtContentV3 = !!(contentVersion && contentVersion.version === 3);
|
|
1133
|
+
const contentSource = await resolveContentSource(config.contentSource !== false);
|
|
1048
1134
|
if (typeof config.contentSignal === "object") {
|
|
1049
1135
|
const robotsOpts = nuxt.options.robots || {};
|
|
1050
1136
|
nuxt.options.robots = robotsOpts;
|
|
@@ -1423,34 +1509,7 @@ export const logger = createModuleLogger('nuxt-ai-ready', ${!!config.debug})
|
|
|
1423
1509
|
mcp: { enabled: mcpAvailable, tools: mcpAvailable && config.mcp?.tools !== false && hasMcpSiteTools, resources: mcpAvailable && config.mcp?.resources !== false },
|
|
1424
1510
|
cron: !!config.cron
|
|
1425
1511
|
})}`;
|
|
1426
|
-
nitroConfig.virtual["#ai-ready-virtual/content-lookup.mjs"] =
|
|
1427
|
-
import { queryCollection } from '@nuxt/content/server'
|
|
1428
|
-
import manifest from '#content/manifest'
|
|
1429
|
-
import { stringify } from 'minimark/stringify'
|
|
1430
|
-
|
|
1431
|
-
const pageCollections = Object.entries(manifest)
|
|
1432
|
-
.filter(([, info]) => info.type === 'page')
|
|
1433
|
-
.map(([name]) => name)
|
|
1434
|
-
|
|
1435
|
-
export async function lookupContentPage(event, path) {
|
|
1436
|
-
if (!pageCollections.length) return null
|
|
1437
|
-
const candidates = path === '/' ? ['/'] : [path, path.replace(/\\/$/, '')]
|
|
1438
|
-
for (const collection of pageCollections) {
|
|
1439
|
-
for (const candidate of candidates) {
|
|
1440
|
-
const page = await queryCollection(event, collection).path(candidate).first().catch(() => null)
|
|
1441
|
-
if (!page) continue
|
|
1442
|
-
const markdown = stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
|
|
1443
|
-
return {
|
|
1444
|
-
markdown,
|
|
1445
|
-
title: page.title,
|
|
1446
|
-
description: page.description,
|
|
1447
|
-
updatedAt: page.seo?.articleModifiedTime || page.updatedAt,
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
return null
|
|
1452
|
-
}
|
|
1453
|
-
` : `export async function lookupContentPage() { return null }`;
|
|
1512
|
+
nitroConfig.virtual["#ai-ready-virtual/content-lookup.mjs"] = contentLookupModule(contentSource);
|
|
1454
1513
|
});
|
|
1455
1514
|
const database = refineDatabaseConfig(config.database || {}, nuxt.options.rootDir);
|
|
1456
1515
|
nuxt.options.runtimeConfig["nuxt-ai-ready"] = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@antfu/eslint-config": "^9.3.0",
|
|
84
84
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
85
|
+
"@harlan-zw/comark-content": "0.1.2",
|
|
85
86
|
"@libsql/client": "^0.17.4",
|
|
86
87
|
"@nuxt/content": "^3.15.2",
|
|
87
88
|
"@nuxt/module-builder": "^1.0.3",
|