nuxt-ai-ready 3.0.1 → 3.0.3
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/chunks/agent-skills.mjs +1 -1
- package/dist/chunks/prerender.mjs +18 -16
- package/dist/module.d.mts +16 -16
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/plugins/link-header-nitro3.js +10 -9
- package/dist/runtime/server/plugins/link-header.js +2 -0
- package/dist/shared/{nuxt-ai-ready.C34F0LJo.mjs → nuxt-ai-ready.CsGXtlrU.mjs} +225 -10
- package/package.json +1 -1
- package/skills/nuxt-ai-ready/SKILL.md +1 -1
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { realpath, readFile, readdir } from 'node:fs/promises';
|
|
3
3
|
import { join, 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.CsGXtlrU.mjs';
|
|
6
6
|
import 'node:module';
|
|
7
7
|
import 'node:url';
|
|
8
8
|
import '@nuxt/kit';
|
|
@@ -6,7 +6,7 @@ import { resolveLocaleFromRoute } from 'nuxtseo-shared/i18n-runtime';
|
|
|
6
6
|
import { createPrerenderFetch } from 'nuxtseo-shared/prerender';
|
|
7
7
|
import { collectSitemap } from 'sitemapd/parse';
|
|
8
8
|
import { withLeadingSlash, joinURL, withBase } from 'ufo';
|
|
9
|
-
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.
|
|
9
|
+
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.CsGXtlrU.mjs';
|
|
10
10
|
import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
|
|
11
11
|
import { toDeployedRoute, toLogicalRoute } from '../../dist/runtime/route-path.js';
|
|
12
12
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
|
|
@@ -299,7 +299,7 @@ async function prerenderRoute(nitro, route, fetch) {
|
|
|
299
299
|
nitro._prerenderedRoutes.push(_route);
|
|
300
300
|
return stat(filePath);
|
|
301
301
|
}
|
|
302
|
-
function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, extras
|
|
302
|
+
function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, extras) {
|
|
303
303
|
const nuxt = useNuxt();
|
|
304
304
|
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
305
305
|
const llmsFullTxtPath = join(nitro.options.output.publicDir, "llms-full.txt");
|
|
@@ -390,20 +390,22 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, extras
|
|
|
390
390
|
logger.debug(`Writing pages.json: ${publicJsonPath} (${(jsonContent.length / 1024).toFixed(1)}kb)`);
|
|
391
391
|
await writeFile(publicJsonPath, jsonContent, "utf-8");
|
|
392
392
|
logger.debug(`Wrote ${pages.length} pages to __ai-ready/pages.json`);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
393
|
+
if (extras.runtimeDatabase === "Enabled") {
|
|
394
|
+
const dumpData = await exportDbDump(state.db);
|
|
395
|
+
const dumpPath = join(publicDataDir, "pages.dump");
|
|
396
|
+
logger.debug(`Writing pages.dump: ${dumpPath} (${(dumpData.length / 1024).toFixed(1)}kb)`);
|
|
397
|
+
await writeFile(dumpPath, dumpData, "utf-8");
|
|
398
|
+
logger.debug(`Created database dump at __ai-ready/pages.dump (${(dumpData.length / 1024).toFixed(1)}kb compressed)`);
|
|
399
|
+
const buildId = Date.now().toString(36);
|
|
400
|
+
const metaContent = JSON.stringify({
|
|
401
|
+
buildId,
|
|
402
|
+
pageCount: pages.length,
|
|
403
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
404
|
+
});
|
|
405
|
+
logger.debug(`Writing pages.meta.json (${(metaContent.length / 1024).toFixed(1)}kb)`);
|
|
406
|
+
await writeFile(join(publicDataDir, "pages.meta.json"), metaContent, "utf-8");
|
|
407
|
+
logger.debug(`Wrote build metadata: buildId=${buildId}`);
|
|
408
|
+
}
|
|
407
409
|
}
|
|
408
410
|
if (state.dbPath && state.llmsTxtConfig?.markdownLinks) {
|
|
409
411
|
const availabilityFile = join(dirname(state.dbPath), MARKDOWN_LINK_AVAILABILITY_FILE);
|
package/dist/module.d.mts
CHANGED
|
@@ -6,6 +6,22 @@ import { ResolvedWebMcpToolsConfig } from '../dist/runtime/site-tool-config.js';
|
|
|
6
6
|
export { GetPageMarkdownToolOptions, ListPagesToolOptions, McpSiteToolAttachmentOptions, SearchPagesToolOptions, SiteToolOptions, SiteToolsConfig, WebMcpSiteToolAttachmentOptions } from '../dist/runtime/site-tool-config.js';
|
|
7
7
|
import { AiCatalog } from '../dist/runtime/server/utils/discovery-response.js';
|
|
8
8
|
|
|
9
|
+
type DatabaseType = 'sqlite' | 'bun' | 'd1' | 'libsql' | 'neon' | 'postgres';
|
|
10
|
+
/**
|
|
11
|
+
* Database state after parsing. Every consumer reads `_tag` first, so a
|
|
12
|
+
* disabled database can never carry a driver, a path, or credentials.
|
|
13
|
+
*/
|
|
14
|
+
type ResolvedDatabase = {
|
|
15
|
+
_tag: 'Disabled';
|
|
16
|
+
} | {
|
|
17
|
+
_tag: 'Enabled';
|
|
18
|
+
type: DatabaseType;
|
|
19
|
+
filename?: string;
|
|
20
|
+
bindingName?: string;
|
|
21
|
+
url?: string;
|
|
22
|
+
authToken?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
9
25
|
interface ParsedMarkdownResult {
|
|
10
26
|
markdown: string;
|
|
11
27
|
title: string;
|
|
@@ -31,22 +47,6 @@ interface ResolvedApiCatalogConfig {
|
|
|
31
47
|
};
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
type DatabaseType = 'sqlite' | 'bun' | 'd1' | 'libsql' | 'neon' | 'postgres';
|
|
35
|
-
/**
|
|
36
|
-
* Database state after parsing. Every consumer reads `_tag` first, so a
|
|
37
|
-
* disabled database can never carry a driver, a path, or credentials.
|
|
38
|
-
*/
|
|
39
|
-
type ResolvedDatabase = {
|
|
40
|
-
_tag: 'Disabled';
|
|
41
|
-
} | {
|
|
42
|
-
_tag: 'Enabled';
|
|
43
|
-
type: DatabaseType;
|
|
44
|
-
filename?: string;
|
|
45
|
-
bindingName?: string;
|
|
46
|
-
url?: string;
|
|
47
|
-
authToken?: string;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
50
|
interface ResolvedWebMcpConfig {
|
|
51
51
|
tools: ResolvedWebMcpToolsConfig;
|
|
52
52
|
exposedTo?: string[];
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import '@nuxt/kit';
|
|
|
7
7
|
import 'defu';
|
|
8
8
|
import 'nuxt-site-config/kit';
|
|
9
9
|
import 'nuxtseo-shared/kit';
|
|
10
|
-
export { m as default } from './shared/nuxt-ai-ready.
|
|
10
|
+
export { m as default } from './shared/nuxt-ai-ready.CsGXtlrU.mjs';
|
|
11
11
|
import './shared/nuxt-ai-ready.B3hAuOLQ.mjs';
|
|
12
12
|
import '../dist/runtime/markdown-path.js';
|
|
13
13
|
import '../dist/runtime/route-path.js';
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { defineNitroPlugin } from "#nuxtseo/nitro";
|
|
2
|
-
import {
|
|
2
|
+
import { STATUS_AWARE_LINK_HEADER } from "./link-header.js";
|
|
3
3
|
function readHeader(event) {
|
|
4
4
|
return event.context[STATUS_AWARE_LINK_HEADER];
|
|
5
5
|
}
|
|
6
|
+
function setLinkUnlessImmutable(response, value) {
|
|
7
|
+
try {
|
|
8
|
+
response.headers.set("link", value);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (!(error instanceof TypeError))
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
6
14
|
export default defineNitroPlugin((nitro) => {
|
|
7
15
|
const hooks = nitro.hooks;
|
|
8
|
-
hooks.hook("error", (_error, { event }) => {
|
|
9
|
-
if (!event)
|
|
10
|
-
return;
|
|
11
|
-
const header = readHeader(event);
|
|
12
|
-
if (header)
|
|
13
|
-
event.req.headers.set(ERROR_LINK_HEADER, header.error);
|
|
14
|
-
});
|
|
15
16
|
hooks.hook("response", (response, event) => {
|
|
16
17
|
const header = readHeader(event);
|
|
17
18
|
if (header)
|
|
18
|
-
response
|
|
19
|
+
setLinkUnlessImmutable(response, response.status >= 400 ? header.error : header.success);
|
|
19
20
|
});
|
|
20
21
|
});
|
|
@@ -18,6 +18,8 @@ export default defineNitroPlugin((nitro) => {
|
|
|
18
18
|
event.node.req.headers[ERROR_LINK_HEADER] = header.error;
|
|
19
19
|
});
|
|
20
20
|
nitro.hooks.hook("beforeResponse", (event) => {
|
|
21
|
+
if (event.node.res.headersSent)
|
|
22
|
+
return;
|
|
21
23
|
const header = event.context[STATUS_AWARE_LINK_HEADER];
|
|
22
24
|
if (!header)
|
|
23
25
|
return;
|
|
@@ -3,7 +3,7 @@ import { readFile, mkdir, writeFile, access } from 'node:fs/promises';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, basename, join, resolve, relative } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
-
import { hasNuxtModule, useNuxt, useLogger, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, getLayerDirectories, tryResolveModule, directoryToURL, addServerImports, addServerHandler, extendRouteRules, getNitroVersion,
|
|
6
|
+
import { hasNuxtModule, useNuxt, useLogger, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, getLayerDirectories, tryResolveModule, directoryToURL, addServerImports, addServerHandler, extendRouteRules, getNitroVersion, addPlugin, addNitroPlugin, addImports } from '@nuxt/kit';
|
|
7
7
|
import defu$1, { defu } from 'defu';
|
|
8
8
|
import { useSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
|
|
9
9
|
import { resolveNuxtContentVersion, renderNitroTypeAugmentations, setupRuntimeAliases, setupNitroRuntimeCompatibility } from 'nuxtseo-shared/kit';
|
|
@@ -756,14 +756,81 @@ const DATABASE_CONSUMER_LABELS = {
|
|
|
756
756
|
mcp: "MCP site tools (`@nuxtjs/mcp-toolkit`)",
|
|
757
757
|
webmcp: "WebMCP site tools (`aiReady.webmcp`)"
|
|
758
758
|
};
|
|
759
|
-
|
|
759
|
+
const RE_SEMVER = /^v?(\d+)\.(\d+)\.(\d+)(?:-([\da-z-]+(?:\.[\da-z-]+)*))?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?$/i;
|
|
760
|
+
const RE_NUMERIC = /^\d+$/;
|
|
761
|
+
function parseSemVer(input) {
|
|
762
|
+
const match = RE_SEMVER.exec(input.trim());
|
|
763
|
+
if (!match)
|
|
764
|
+
return void 0;
|
|
765
|
+
return {
|
|
766
|
+
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
767
|
+
prerelease: match[4]?.split(".") ?? []
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function compareCore(a, b) {
|
|
771
|
+
for (let i = 0; i < 3; i++) {
|
|
772
|
+
if (a[i] !== b[i])
|
|
773
|
+
return a[i] < b[i] ? -1 : 1;
|
|
774
|
+
}
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
function comparePrerelease(a, b) {
|
|
778
|
+
if (a.length === 0 && b.length === 0)
|
|
779
|
+
return 0;
|
|
780
|
+
if (a.length === 0)
|
|
781
|
+
return 1;
|
|
782
|
+
if (b.length === 0)
|
|
783
|
+
return -1;
|
|
784
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
785
|
+
const left = a[i];
|
|
786
|
+
const right = b[i];
|
|
787
|
+
if (left === void 0)
|
|
788
|
+
return -1;
|
|
789
|
+
if (right === void 0)
|
|
790
|
+
return 1;
|
|
791
|
+
if (left === right)
|
|
792
|
+
continue;
|
|
793
|
+
const leftNumeric = RE_NUMERIC.test(left);
|
|
794
|
+
const rightNumeric = RE_NUMERIC.test(right);
|
|
795
|
+
if (leftNumeric && rightNumeric)
|
|
796
|
+
return Number(left) < Number(right) ? -1 : 1;
|
|
797
|
+
if (leftNumeric !== rightNumeric)
|
|
798
|
+
return leftNumeric ? -1 : 1;
|
|
799
|
+
return left < right ? -1 : 1;
|
|
800
|
+
}
|
|
801
|
+
return 0;
|
|
802
|
+
}
|
|
803
|
+
function matchCaretRange(version, range) {
|
|
804
|
+
const installed = parseSemVer(version);
|
|
805
|
+
const minimum = range.trim().startsWith("^") ? parseSemVer(range.trim().slice(1)) : void 0;
|
|
806
|
+
if (!installed || !minimum)
|
|
807
|
+
return { _tag: "Unparsed" };
|
|
808
|
+
const [major, minor, patch] = minimum.core;
|
|
809
|
+
const ceiling = major > 0 ? [major + 1, 0, 0] : minor > 0 ? [0, minor + 1, 0] : [0, 0, patch + 1];
|
|
810
|
+
const sameCore = compareCore(installed.core, minimum.core) === 0;
|
|
811
|
+
const satisfied = (compareCore(installed.core, minimum.core) > 0 || sameCore && comparePrerelease(installed.prerelease, minimum.prerelease) >= 0) && compareCore(installed.core, ceiling) < 0 && (installed.prerelease.length === 0 || sameCore && minimum.prerelease.length > 0);
|
|
812
|
+
return satisfied ? { _tag: "Satisfied" } : { _tag: "Unsatisfied" };
|
|
813
|
+
}
|
|
814
|
+
function checkDrizzleInstall(install, peerRange) {
|
|
815
|
+
if (install._tag === "Missing")
|
|
816
|
+
return { _tag: "Missing" };
|
|
817
|
+
if (!install.version || !peerRange)
|
|
818
|
+
return void 0;
|
|
819
|
+
return matchCaretRange(install.version, peerRange)._tag === "Unsatisfied" ? { _tag: "OutOfRange", version: install.version } : void 0;
|
|
820
|
+
}
|
|
821
|
+
function formatDrizzleMessage(input) {
|
|
760
822
|
const consumers = Object.keys(DATABASE_CONSUMER_LABELS).filter((consumer) => input.requested[consumer]);
|
|
761
823
|
const subject = consumers.length === 0 ? "The `aiReady.database` option turns on the database." : `These features need the database: ${consumers.map((consumer) => DATABASE_CONSUMER_LABELS[consumer]).join(", ")}.`;
|
|
762
824
|
const version = input.peerRange?.replace(/^\^/, "");
|
|
763
|
-
const
|
|
825
|
+
const command = `npx nypm add drizzle-orm${version ? `@${version}` : ""}`;
|
|
826
|
+
const lines = input.problem._tag === "Missing" ? [
|
|
764
827
|
subject,
|
|
765
828
|
"The database needs `drizzle-orm`. This app does not install it.",
|
|
766
|
-
`Install it:
|
|
829
|
+
`Install it: ${command}`
|
|
830
|
+
] : [
|
|
831
|
+
subject,
|
|
832
|
+
`The database needs \`drizzle-orm\` ${input.peerRange}. This app installs ${input.problem.version}.`,
|
|
833
|
+
`Upgrade it: ${command}`
|
|
767
834
|
];
|
|
768
835
|
if (consumers.length > 0 && consumers.every((consumer) => consumer === "mcp" || consumer === "webmcp"))
|
|
769
836
|
lines.push("If you do not need the site tools, set `aiReady.database: false`.");
|
|
@@ -1176,6 +1243,137 @@ function mergeStaticDiscoveryHeaders(contents, baseURL, entries) {
|
|
|
1176
1243
|
}
|
|
1177
1244
|
return entries.reduce((headers, entry) => ensureStaticHeader(headers, route, "Link", entry, "append"), contents);
|
|
1178
1245
|
}
|
|
1246
|
+
function parseHeaderLine(line) {
|
|
1247
|
+
const colon = line.indexOf(":");
|
|
1248
|
+
if (!/^[\t ]/.test(line) || colon < 0)
|
|
1249
|
+
return void 0;
|
|
1250
|
+
const name = line.slice(0, colon).trim();
|
|
1251
|
+
if (!name || name.startsWith("!") || /\s/.test(name))
|
|
1252
|
+
return void 0;
|
|
1253
|
+
const eol = line.endsWith("\r\n") ? "\r\n" : line.endsWith("\n") ? "\n" : "";
|
|
1254
|
+
return {
|
|
1255
|
+
name,
|
|
1256
|
+
value: line.slice(colon + 1, line.length - eol.length).trim(),
|
|
1257
|
+
withValue: (value) => `${line.slice(0, colon + 1)} ${value}${eol}`
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
const RE_DETACH_LINE = /^[\t ]+![\t ]*([^\s:]+)/;
|
|
1261
|
+
function patternToRegExp(pattern) {
|
|
1262
|
+
const source = pattern.split(/(\*|:\w+)/).map((part) => part === "*" ? ".*" : part.startsWith(":") ? "[^/]+" : escapeRegExp(part)).join("");
|
|
1263
|
+
return new RegExp(`^${source}$`);
|
|
1264
|
+
}
|
|
1265
|
+
function coversPattern(broad, narrow) {
|
|
1266
|
+
if (broad === narrow || !broad.startsWith("/") || !narrow.startsWith("/"))
|
|
1267
|
+
return false;
|
|
1268
|
+
if (!/[*:]/.test(narrow))
|
|
1269
|
+
return patternToRegExp(broad).test(narrow);
|
|
1270
|
+
return broad.indexOf("*") === broad.length - 1 && !broad.includes(":") && narrow.startsWith(broad.slice(0, -1));
|
|
1271
|
+
}
|
|
1272
|
+
function splitLinkEntries(value) {
|
|
1273
|
+
const entries = [];
|
|
1274
|
+
let current = "";
|
|
1275
|
+
let inUrl = false;
|
|
1276
|
+
let inQuote = false;
|
|
1277
|
+
for (const char of value) {
|
|
1278
|
+
if (char === "<" && !inQuote)
|
|
1279
|
+
inUrl = true;
|
|
1280
|
+
else if (char === ">" && !inQuote)
|
|
1281
|
+
inUrl = false;
|
|
1282
|
+
else if (char === '"' && !inUrl)
|
|
1283
|
+
inQuote = !inQuote;
|
|
1284
|
+
if (char === "," && !inUrl && !inQuote) {
|
|
1285
|
+
entries.push(current.trim());
|
|
1286
|
+
current = "";
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
current += char;
|
|
1290
|
+
}
|
|
1291
|
+
entries.push(current.trim());
|
|
1292
|
+
return entries.filter(Boolean);
|
|
1293
|
+
}
|
|
1294
|
+
function headerTokens(name, value) {
|
|
1295
|
+
if (name === "link")
|
|
1296
|
+
return splitLinkEntries(value);
|
|
1297
|
+
if (name === "vary")
|
|
1298
|
+
return value.split(",").map((token) => token.trim()).filter(Boolean);
|
|
1299
|
+
return [value.trim()];
|
|
1300
|
+
}
|
|
1301
|
+
function tokenKey(name, token) {
|
|
1302
|
+
return name === "vary" ? token.toLowerCase() : token;
|
|
1303
|
+
}
|
|
1304
|
+
function dedupeStaticHeaders(contents, names) {
|
|
1305
|
+
const owned = new Set(names.map((name) => name.toLowerCase()));
|
|
1306
|
+
const blocks = splitHeaderBlocks(contents);
|
|
1307
|
+
const detached = /* @__PURE__ */ new Set();
|
|
1308
|
+
const parsed = blocks.map((block) => {
|
|
1309
|
+
const headers = [];
|
|
1310
|
+
for (const line of block.text.split(/(?<=\n)/).slice(block.route === null ? 0 : 1)) {
|
|
1311
|
+
const detach = line.match(RE_DETACH_LINE);
|
|
1312
|
+
if (detach) {
|
|
1313
|
+
detached.add(detach[1].toLowerCase());
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
const header = parseHeaderLine(line);
|
|
1317
|
+
if (!header)
|
|
1318
|
+
continue;
|
|
1319
|
+
const name = header.name.toLowerCase();
|
|
1320
|
+
if (owned.has(name))
|
|
1321
|
+
headers.push({ name, tokens: headerTokens(name, header.value) });
|
|
1322
|
+
}
|
|
1323
|
+
return headers;
|
|
1324
|
+
});
|
|
1325
|
+
const lastRuleIndex = /* @__PURE__ */ new Map();
|
|
1326
|
+
blocks.forEach((block, index) => {
|
|
1327
|
+
if (block.route !== null)
|
|
1328
|
+
lastRuleIndex.set(block.route, index);
|
|
1329
|
+
});
|
|
1330
|
+
let changed = false;
|
|
1331
|
+
const output = blocks.map((block) => {
|
|
1332
|
+
const route = block.route;
|
|
1333
|
+
if (route === null)
|
|
1334
|
+
return block.text;
|
|
1335
|
+
const provided = /* @__PURE__ */ new Map();
|
|
1336
|
+
lastRuleIndex.forEach((index, pattern) => {
|
|
1337
|
+
if (!coversPattern(pattern, route))
|
|
1338
|
+
return;
|
|
1339
|
+
for (const { name, tokens } of parsed[index]) {
|
|
1340
|
+
if (detached.has(name))
|
|
1341
|
+
continue;
|
|
1342
|
+
const values = provided.get(name) ?? /* @__PURE__ */ new Set();
|
|
1343
|
+
tokens.forEach((token) => values.add(tokenKey(name, token)));
|
|
1344
|
+
provided.set(name, values);
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
if (provided.size === 0)
|
|
1348
|
+
return block.text;
|
|
1349
|
+
const [routeLine, ...lines] = block.text.split(/(?<=\n)/);
|
|
1350
|
+
let headerLines = 0;
|
|
1351
|
+
const kept = lines.flatMap((line) => {
|
|
1352
|
+
const header = parseHeaderLine(line);
|
|
1353
|
+
if (!header) {
|
|
1354
|
+
if (RE_DETACH_LINE.test(line))
|
|
1355
|
+
headerLines++;
|
|
1356
|
+
return [line];
|
|
1357
|
+
}
|
|
1358
|
+
headerLines++;
|
|
1359
|
+
const name = header.name.toLowerCase();
|
|
1360
|
+
const covered = provided.get(name);
|
|
1361
|
+
if (!covered)
|
|
1362
|
+
return [line];
|
|
1363
|
+
const tokens = headerTokens(name, header.value);
|
|
1364
|
+
const remaining = tokens.filter((token) => !covered.has(tokenKey(name, token)));
|
|
1365
|
+
if (remaining.length === tokens.length)
|
|
1366
|
+
return [line];
|
|
1367
|
+
changed = true;
|
|
1368
|
+
if (remaining.length > 0)
|
|
1369
|
+
return [header.withValue(remaining.join(", "))];
|
|
1370
|
+
headerLines--;
|
|
1371
|
+
return [];
|
|
1372
|
+
});
|
|
1373
|
+
return headerLines === 0 ? kept.join("") : `${routeLine}${kept.join("")}`;
|
|
1374
|
+
});
|
|
1375
|
+
return changed ? output.join("") : contents;
|
|
1376
|
+
}
|
|
1179
1377
|
const CLOUDFLARE_STATIC_HEADER_RULE_LIMIT = 100;
|
|
1180
1378
|
const RE_EXACT_MARKDOWN_ROUTE = /^\/[^*\s]*\.md[\t ]*\r?$/;
|
|
1181
1379
|
function splitHeaderBlocks(contents) {
|
|
@@ -1449,8 +1647,10 @@ ${details}`);
|
|
|
1449
1647
|
const databaseEnabled = database._tag === "Enabled";
|
|
1450
1648
|
if (databaseEnabled) {
|
|
1451
1649
|
const drizzle = await tryResolveModule("drizzle-orm", directoryToURL(nuxt.options.rootDir));
|
|
1452
|
-
|
|
1453
|
-
|
|
1650
|
+
const peerRange = peerDependencies["drizzle-orm"];
|
|
1651
|
+
const problem = checkDrizzleInstall(drizzle ? { _tag: "Installed", version: (await readPackageMetadata(drizzle)).version } : { _tag: "Missing" }, peerRange);
|
|
1652
|
+
if (problem)
|
|
1653
|
+
throw new Error(`[nuxt-ai-ready] ${formatDrizzleMessage({ problem, requested: databaseRequested, peerRange })}`);
|
|
1454
1654
|
}
|
|
1455
1655
|
if (!databaseEnabled)
|
|
1456
1656
|
logger.debug("Database disabled. Page storage, runtime indexing and site tools are off.");
|
|
@@ -2018,11 +2218,17 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2018
2218
|
apiCatalog: apiCatalogConfig
|
|
2019
2219
|
};
|
|
2020
2220
|
if (prerenderEnabled) {
|
|
2021
|
-
addNitroPlugin(resolve$1("./runtime/server/plugins/html-capture.prerender"));
|
|
2022
2221
|
addServerHandler({
|
|
2023
2222
|
middleware: true,
|
|
2223
|
+
env: "prerender",
|
|
2024
2224
|
handler: resolve$1("./runtime/server/middleware/markdown.prerender")
|
|
2025
2225
|
});
|
|
2226
|
+
const htmlCapturePlugin = resolve$1("./runtime/server/plugins/html-capture.prerender");
|
|
2227
|
+
nuxt.hook("nitro:init", (nitro) => {
|
|
2228
|
+
nitro.hooks.hook("prerender:config", (prerenderConfig) => {
|
|
2229
|
+
prerenderConfig.plugins = [...prerenderConfig.plugins || [], htmlCapturePlugin];
|
|
2230
|
+
});
|
|
2231
|
+
});
|
|
2026
2232
|
addPlugin({
|
|
2027
2233
|
mode: "server",
|
|
2028
2234
|
src: resolve$1("./runtime/app/plugins/md-hints.prerender")
|
|
@@ -2127,7 +2333,8 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2127
2333
|
}, mergedLlmsTxt, {
|
|
2128
2334
|
ftsTokenizer,
|
|
2129
2335
|
i18n: i18nConfig,
|
|
2130
|
-
artifactRoutes: agentSkillsResult._tag === "Enabled" ? Object.keys(agentSkillsResult.localArtifacts) : []
|
|
2336
|
+
artifactRoutes: agentSkillsResult._tag === "Enabled" ? Object.keys(agentSkillsResult.localArtifacts) : [],
|
|
2337
|
+
runtimeDatabase: database._tag
|
|
2131
2338
|
});
|
|
2132
2339
|
}
|
|
2133
2340
|
if (databaseEnabled) {
|
|
@@ -2183,6 +2390,13 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2183
2390
|
}
|
|
2184
2391
|
});
|
|
2185
2392
|
}
|
|
2393
|
+
let sharedMarkdownRuleNoted = false;
|
|
2394
|
+
const noteSharedMarkdownRule = (total, dropped) => {
|
|
2395
|
+
if (sharedMarkdownRuleNoted)
|
|
2396
|
+
return;
|
|
2397
|
+
sharedMarkdownRuleNoted = true;
|
|
2398
|
+
logger.info(`Cloudflare allows ${CLOUDFLARE_STATIC_HEADER_RULE_LIMIT} _headers rules. One rule per Markdown page would make ${total}. The ${dropped} Markdown pages share the /*.md rule, which sends no per-page canonical Link header.`);
|
|
2399
|
+
};
|
|
2186
2400
|
nuxt.hooks.hook("nitro:build:before", (nitro) => {
|
|
2187
2401
|
const isCloudflarePreset = String(nitro.options.preset || "").startsWith("cloudflare");
|
|
2188
2402
|
nitro.hooks.hook("prerender:done", async () => {
|
|
@@ -2214,7 +2428,7 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2214
2428
|
);
|
|
2215
2429
|
if (plan._tag === "skip") {
|
|
2216
2430
|
applyStaticMarkdownHeaderPlan(nitro.options.routeRules, plan);
|
|
2217
|
-
|
|
2431
|
+
noteSharedMarkdownRule(plan.total, plan.dropped);
|
|
2218
2432
|
return;
|
|
2219
2433
|
}
|
|
2220
2434
|
applyStaticMarkdownHeaderPlan(nitro.options.routeRules, plan);
|
|
@@ -2279,10 +2493,11 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2279
2493
|
if (staticDiscoverySupported && apiCatalogConfig && await hasArtifact(API_CATALOG_PATH))
|
|
2280
2494
|
discovery.push(`<${encodeURI(apiCatalogConfig.href)}>; rel="api-catalog"`);
|
|
2281
2495
|
mergedHeaders = mergeStaticDiscoveryHeaders(mergedHeaders, staticBaseURL, discovery);
|
|
2496
|
+
mergedHeaders = dedupeStaticHeaders(mergedHeaders, ["Content-Type", "Link", "Vary"]);
|
|
2282
2497
|
const budget = enforceStaticHeaderBudget(mergedHeaders, CLOUDFLARE_STATIC_HEADER_RULE_LIMIT);
|
|
2283
2498
|
if (budget.dropped > 0) {
|
|
2284
2499
|
mergedHeaders = budget.contents;
|
|
2285
|
-
|
|
2500
|
+
noteSharedMarkdownRule(budget.total + budget.dropped, budget.dropped);
|
|
2286
2501
|
}
|
|
2287
2502
|
if (budget.total > CLOUDFLARE_STATIC_HEADER_RULE_LIMIT)
|
|
2288
2503
|
logger.warn(`_headers still has ${budget.total} rules after dropping the .md rules. Cloudflare will reject the deploy until the other rules fit under ${CLOUDFLARE_STATIC_HEADER_RULE_LIMIT}.`);
|
package/package.json
CHANGED
|
@@ -99,7 +99,7 @@ Runtime indexing, database drivers, Cloudflare D1, and the server query helpers:
|
|
|
99
99
|
- **Root `skills/` folders require explicit publication.** Automatic discovery scans public directories only. Set `agentSkills.dir: 'skills'` to publish root skills intentionally.
|
|
100
100
|
- **The same hook name exists twice.** `ai-ready:page:markdown` is a Nuxt hook (`nuxt.config` `hooks`, prerender only) and a Nitro hook (`nitroApp.hooks`, every HTML conversion). They receive different context. The Nitro `ctx.event` is undefined when indexing runs outside a request.
|
|
101
101
|
- **`database: false` conflicts with `runtimeSync` and `cron`.** The build fails and names the option.
|
|
102
|
-
- **A database feature without `drizzle-orm` fails the build.** `runtimeSync`, `cron`, MCP site tools, WebMCP site tools, and an explicit `database` option turn on the database. The error names the feature and the install command. If MCP Toolkit serves only your own tools, set `database: false` instead.
|
|
102
|
+
- **A database feature without `drizzle-orm` 1.x fails the build.** `runtimeSync`, `cron`, MCP site tools, WebMCP site tools, and an explicit `database` option turn on the database. A `drizzle-orm` 0.x install fails too. The error names the feature and the install command. If MCP Toolkit serves only your own tools, set `database: false` instead.
|
|
103
103
|
|
|
104
104
|
## Version limits
|
|
105
105
|
|