nuxt-ai-ready 3.3.0 → 3.4.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/chunks/agent-skills.mjs +1 -1
- package/dist/chunks/prerender.mjs +35 -17
- package/dist/cli.mjs +259 -0
- package/dist/module.d.mts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/llms-txt-files.js +58 -3
- package/dist/runtime/llms-txt-format.js +5 -4
- package/dist/runtime/markdown-text.d.ts +2 -0
- package/dist/runtime/markdown-text.js +3 -0
- package/dist/runtime/server/db/drizzle/queries.d.ts +2 -6
- package/dist/runtime/server/db/drizzle/queries.js +30 -10
- package/dist/runtime/server/db/queries.d.ts +2 -6
- package/dist/runtime/server/db/queries.js +12 -7
- package/dist/runtime/server/db/schema/postgres.d.ts +30 -0
- package/dist/runtime/server/db/schema/postgres.js +1 -0
- package/dist/runtime/server/db/schema/sqlite.d.ts +32 -0
- package/dist/runtime/server/db/schema/sqlite.js +1 -0
- package/dist/runtime/server/db/schema-sql.d.ts +1 -1
- package/dist/runtime/server/db/schema-sql.js +2 -1
- package/dist/runtime/server/db/seed.d.ts +8 -0
- package/dist/runtime/server/db/seed.js +16 -0
- package/dist/runtime/server/db/shared.d.ts +3 -3
- package/dist/runtime/server/db/shared.js +19 -1
- package/dist/runtime/server/middleware/markdown.prerender.js +9 -1
- package/dist/runtime/server/plugins/content-signal-capture-nitro3.prerender.d.ts +2 -0
- package/dist/runtime/server/plugins/content-signal-capture-nitro3.prerender.js +13 -0
- package/dist/runtime/server/plugins/content-signal-capture.prerender.d.ts +2 -0
- package/dist/runtime/server/plugins/content-signal-capture.prerender.js +13 -0
- package/dist/runtime/server/plugins/sitemap-seeder.js +9 -1
- package/dist/runtime/server/routes/llms.txt.get.js +4 -2
- package/dist/runtime/server/utils/llms-full.js +31 -5
- package/dist/runtime/server/utils/negotiation-response.js +3 -0
- package/dist/runtime/server/utils/prerender-html.d.ts +3 -0
- package/dist/runtime/server/utils/prerender-html.js +12 -0
- package/dist/runtime/server/utils/runCron.js +1 -1
- package/dist/runtime/server/utils/sitemap-lastmod.d.ts +4 -0
- package/dist/runtime/server/utils/sitemap-lastmod.js +19 -0
- package/dist/runtime/server/utils/sitemap-md.js +3 -2
- package/dist/runtime/server/utils/sitemap.js +4 -2
- package/dist/runtime/server/utils.js +50 -2
- package/dist/shared/{nuxt-ai-ready.BwnSMqRn.mjs → nuxt-ai-ready.O6_9Egm8.mjs} +367 -246
- package/package.json +3 -3
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Mark dates added by this module without changing the public sitemap date. */
|
|
2
|
+
export declare function markIndexedSitemapLastmods(xml: string, enrichedUrls: ReadonlySet<string>): string;
|
|
3
|
+
/** The source crawl must never treat our indexed date as a CMS change. */
|
|
4
|
+
export declare function stripIndexedSitemapLastmods(body: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const INDEXED_LASTMOD_MARKER = "<!-- nuxt-ai-ready:indexed-lastmod -->";
|
|
2
|
+
export function markIndexedSitemapLastmods(xml, enrichedUrls) {
|
|
3
|
+
const escapedUrls = new Set([...enrichedUrls].map((url) => url.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'")));
|
|
4
|
+
return xml.replace(/<url>([\s\S]*?)<\/url>/g, (entry, body) => {
|
|
5
|
+
const loc = body.match(/<loc>([^<]*)<\/loc>/)?.[1];
|
|
6
|
+
return loc && escapedUrls.has(loc) ? entry.replace("<lastmod>", `${INDEXED_LASTMOD_MARKER}<lastmod>`) : entry;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function stripIndexedSitemapLastmods(body) {
|
|
10
|
+
let xml;
|
|
11
|
+
try {
|
|
12
|
+
xml = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
13
|
+
} catch {
|
|
14
|
+
return body;
|
|
15
|
+
}
|
|
16
|
+
if (!xml.includes(INDEXED_LASTMOD_MARKER))
|
|
17
|
+
return body;
|
|
18
|
+
return new TextEncoder().encode(xml.replace(/<!-- nuxt-ai-ready:indexed-lastmod -->\s*<lastmod>[^<]*<\/lastmod>/g, ""));
|
|
19
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { toMarkdownPath } from "../../markdown-path.js";
|
|
2
|
+
import { escapeMarkdownHtml } from "../../markdown-text.js";
|
|
2
3
|
import { toDeployedRoute } from "../../route-path.js";
|
|
3
4
|
export const SITEMAP_MD_ROUTE = "/sitemap.md";
|
|
4
5
|
function topSegment(route) {
|
|
5
6
|
return route.split("/").filter(Boolean)[0] || "";
|
|
6
7
|
}
|
|
7
8
|
function escapeTitle(title) {
|
|
8
|
-
return title.replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
9
|
+
return escapeMarkdownHtml(title).replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
9
10
|
}
|
|
10
11
|
function formatEntry(entry, resolveHref) {
|
|
11
12
|
const title = escapeTitle(entry.title?.trim() || entry.route);
|
|
@@ -35,7 +36,7 @@ export function buildSitemapMd(entries, options = {}) {
|
|
|
35
36
|
return 1;
|
|
36
37
|
return a.localeCompare(b);
|
|
37
38
|
});
|
|
38
|
-
const parts = [`# ${options.siteName || "Site"} Sitemap`, "", "All pages in Markdown format."];
|
|
39
|
+
const parts = [`# ${escapeMarkdownHtml(options.siteName || "Site")} Sitemap`, "", "All pages in Markdown format."];
|
|
39
40
|
for (const key of keys) {
|
|
40
41
|
parts.push("", `## ${key || "Root"}`);
|
|
41
42
|
for (const entry of groups.get(key))
|
|
@@ -7,6 +7,7 @@ import { logger } from "../logger.js";
|
|
|
7
7
|
import { getCfEnv, hasAssets } from "./cloudflare.js";
|
|
8
8
|
import { createUniversalContext } from "./context.js";
|
|
9
9
|
import { fetchRawWithEvent } from "./fetch.js";
|
|
10
|
+
import { stripIndexedSitemapLastmods } from "./sitemap-lastmod.js";
|
|
10
11
|
const FETCH_TIMEOUT = 15e3;
|
|
11
12
|
const SITEMAP_FALLBACK_ORIGIN = "http://localhost";
|
|
12
13
|
const SITEMAP_READER_LIMITS = {
|
|
@@ -221,11 +222,12 @@ function createLocalSitemapLoader(event, usePublicAsset, loadedBodies) {
|
|
|
221
222
|
bytesRead: read.result.bytesRead
|
|
222
223
|
};
|
|
223
224
|
}
|
|
224
|
-
|
|
225
|
+
const sourceBody = stripIndexedSitemapLastmods(read.result.body);
|
|
226
|
+
loadedBodies.set(request.url, sourceBody);
|
|
225
227
|
return {
|
|
226
228
|
_tag: "body",
|
|
227
229
|
url: request.url,
|
|
228
|
-
body:
|
|
230
|
+
body: sourceBody
|
|
229
231
|
};
|
|
230
232
|
};
|
|
231
233
|
}
|
|
@@ -3,11 +3,57 @@ import { useNitroApp } from "#nuxtseo/nitro";
|
|
|
3
3
|
import { stripLlmsTxtDirectiveHtml } from "../llms-txt-directive.js";
|
|
4
4
|
import { hoistMetaDescription } from "./utils/frontmatter.js";
|
|
5
5
|
const RE_NBSP = /\u00A0/g;
|
|
6
|
+
const JSON_LD_MAX_BYTES = 65536;
|
|
7
|
+
const JSON_LD_MAX_BLOCKS = 16;
|
|
8
|
+
const IMAGE_META_FIELDS = [
|
|
9
|
+
"og:image",
|
|
10
|
+
"og:image:url",
|
|
11
|
+
"og:image:secure_url",
|
|
12
|
+
"og:image:type",
|
|
13
|
+
"og:image:width",
|
|
14
|
+
"og:image:height",
|
|
15
|
+
"og:image:alt",
|
|
16
|
+
"twitter:image",
|
|
17
|
+
"twitter:image:src",
|
|
18
|
+
"twitter:image:alt"
|
|
19
|
+
];
|
|
20
|
+
function parseJsonLdBlock(text, remainingBytes) {
|
|
21
|
+
if (text.length > remainingBytes)
|
|
22
|
+
return { _tag: "Skipped" };
|
|
23
|
+
const content = text.trim();
|
|
24
|
+
if (!content)
|
|
25
|
+
return { _tag: "Skipped" };
|
|
26
|
+
let value;
|
|
27
|
+
try {
|
|
28
|
+
value = JSON.parse(text);
|
|
29
|
+
} catch {
|
|
30
|
+
return { _tag: "Skipped" };
|
|
31
|
+
}
|
|
32
|
+
const isObject = (item) => typeof item === "object" && item !== null && !Array.isArray(item);
|
|
33
|
+
if (!isObject(value) && !(Array.isArray(value) && value.every(isObject)))
|
|
34
|
+
return { _tag: "Skipped" };
|
|
35
|
+
const markdown = `\`\`\`json
|
|
36
|
+
${content.replace(RE_NBSP, "\\u00a0")}
|
|
37
|
+
\`\`\``;
|
|
38
|
+
const bytes = new TextEncoder().encode(markdown).byteLength + 2;
|
|
39
|
+
if (bytes > remainingBytes)
|
|
40
|
+
return { _tag: "Skipped" };
|
|
41
|
+
return { _tag: "Preserved", markdown, bytes };
|
|
42
|
+
}
|
|
6
43
|
function normalizeWhitespace(text) {
|
|
7
44
|
return text.replace(RE_NBSP, " ");
|
|
8
45
|
}
|
|
9
46
|
function buildMdreamOptions(url, mdreamOptions, meta, extractUpdatedAt, additionalFields) {
|
|
10
47
|
const extraction = {
|
|
48
|
+
"script[type]": (el) => {
|
|
49
|
+
if (el.attributes.type?.trim().toLowerCase() !== "application/ld+json" || meta.jsonLd.blocks.length >= JSON_LD_MAX_BLOCKS)
|
|
50
|
+
return;
|
|
51
|
+
const block = parseJsonLdBlock(el.textContent, JSON_LD_MAX_BYTES - meta.jsonLd.bytes);
|
|
52
|
+
if (block._tag === "Preserved") {
|
|
53
|
+
meta.jsonLd.blocks.push(block.markdown);
|
|
54
|
+
meta.jsonLd.bytes += block.bytes;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
11
57
|
'meta[name="keywords"]': (el) => {
|
|
12
58
|
meta.metaKeywords = el.attributes.content || "";
|
|
13
59
|
},
|
|
@@ -30,6 +76,7 @@ function buildMdreamOptions(url, mdreamOptions, meta, extractUpdatedAt, addition
|
|
|
30
76
|
};
|
|
31
77
|
const frontmatter = {
|
|
32
78
|
additionalFields,
|
|
79
|
+
metaFields: [...IMAGE_META_FIELDS],
|
|
33
80
|
onExtract: (fm) => {
|
|
34
81
|
if (fm.title)
|
|
35
82
|
meta.title = fm.title;
|
|
@@ -70,13 +117,14 @@ export function extractLastUpdated(html) {
|
|
|
70
117
|
return void 0;
|
|
71
118
|
}
|
|
72
119
|
export async function convertHtmlToMarkdown(html, url, mdreamOptions, opts) {
|
|
73
|
-
const meta = { title: "", description: "", metaKeywords: "", headings: [], textContent: [] };
|
|
120
|
+
const meta = { title: "", description: "", metaKeywords: "", headings: [], textContent: [], jsonLd: { blocks: [], bytes: 0 } };
|
|
74
121
|
const options = buildMdreamOptions(url, mdreamOptions, meta, opts.extractUpdatedAt ?? false, opts.additionalFrontmatter ?? {});
|
|
75
122
|
const nitroApp = useNitroApp();
|
|
76
123
|
await nitroApp.hooks.callHook("ai-ready:mdreamConfig", options);
|
|
124
|
+
const convertedMarkdown = hoistMetaDescription(htmlToMarkdown(stripLlmsTxtDirectiveHtml(html), options));
|
|
77
125
|
const context = {
|
|
78
126
|
html,
|
|
79
|
-
markdown:
|
|
127
|
+
markdown: [convertedMarkdown, ...meta.jsonLd.blocks].join("\n\n"),
|
|
80
128
|
route: opts.hooks.route,
|
|
81
129
|
title: meta.title,
|
|
82
130
|
description: meta.description,
|