nuxt-ai-ready 3.1.0 → 3.2.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 +8 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/llms-txt-directive.d.ts +15 -0
- package/dist/runtime/llms-txt-directive.js +23 -0
- package/dist/runtime/llms-txt-utils.js +3 -0
- package/dist/runtime/markdown-links.d.ts +10 -0
- package/dist/runtime/markdown-links.js +52 -0
- package/dist/runtime/server/middleware/markdown.js +8 -1
- package/dist/runtime/server/middleware/markdown.prerender.js +2 -1
- package/dist/runtime/server/plugins/llms-txt-directive-html.d.ts +3 -0
- package/dist/runtime/server/plugins/llms-txt-directive-html.js +8 -0
- package/dist/runtime/server/routes/sitemap.md.get.js +4 -2
- package/dist/runtime/server/utils.js +2 -1
- package/dist/runtime/types.d.ts +17 -1
- package/dist/shared/{nuxt-ai-ready.Dmvy7CIJ.mjs → nuxt-ai-ready.DH9mSayy.mjs} +55 -18
- package/package.json +1 -1
- package/skills/nuxt-ai-ready/SKILL.md +3 -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.DH9mSayy.mjs';
|
|
6
6
|
import 'node:module';
|
|
7
7
|
import 'node:url';
|
|
8
8
|
import '@nuxt/kit';
|
|
@@ -6,7 +6,9 @@ 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.DH9mSayy.mjs';
|
|
10
|
+
import { insertLlmsTxtDirective } from '../../dist/runtime/llms-txt-directive.js';
|
|
11
|
+
import { absoluteSiteUrl } from '../../dist/runtime/markdown-links.js';
|
|
10
12
|
import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
|
|
11
13
|
import { toDeployedRoute, toLogicalRoute } from '../../dist/runtime/route-path.js';
|
|
12
14
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
|
|
@@ -347,7 +349,8 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, extras)
|
|
|
347
349
|
await initPromise;
|
|
348
350
|
const parsed = JSON.parse(route.contents || "{}");
|
|
349
351
|
await processMarkdownRoute(state, nuxt, pageRoute, parsed);
|
|
350
|
-
|
|
352
|
+
const withSitemap = options.sitemapMd === false ? parsed.markdown : appendSitemapSection(parsed.markdown, absoluteSiteUrl(toDeployedRoute(SITEMAP_MD_ROUTE, nitro.options.baseURL), siteInfo?.url));
|
|
353
|
+
route.contents = llmsTxtConfig?.directive?.markdown === false ? withSitemap : insertLlmsTxtDirective(withSitemap, absoluteSiteUrl(toDeployedRoute("/llms.txt", nitro.options.baseURL), siteInfo?.url));
|
|
351
354
|
route.contentType = "text/markdown; charset=utf-8";
|
|
352
355
|
state.totalProcessingTime += Date.now() - pageStartTime;
|
|
353
356
|
});
|
|
@@ -409,10 +412,12 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, extras)
|
|
|
409
412
|
}
|
|
410
413
|
if (state.dbPath && state.llmsTxtConfig?.markdownLinks) {
|
|
411
414
|
const availabilityFile = join(dirname(state.dbPath), MARKDOWN_LINK_AVAILABILITY_FILE);
|
|
415
|
+
const options2 = nitro.options;
|
|
416
|
+
const ignoreBaseDir = options2.srcDir || (typeof options2.serverDir === "string" ? options2.serverDir : void 0) || options2.rootDir;
|
|
412
417
|
const paths = await findOutputMarkdownPaths(
|
|
413
418
|
nitro.options.output.publicDir,
|
|
414
419
|
nitro.options.baseURL,
|
|
415
|
-
|
|
420
|
+
ignoreBaseDir,
|
|
416
421
|
nitro.options.ignore,
|
|
417
422
|
nitro.options.publicAssets
|
|
418
423
|
);
|
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.DH9mSayy.mjs';
|
|
11
11
|
import './shared/nuxt-ai-ready.B3hAuOLQ.mjs';
|
|
12
12
|
import '../dist/runtime/route-path.js';
|
|
13
13
|
import '../dist/runtime/server/utils/sitemap-md.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The line that points a reader of one page at the whole site's index. */
|
|
2
|
+
export declare function llmsTxtDirective(llmsTxtUrl: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Add the llms.txt directive near the top of a page's Markdown, after any
|
|
5
|
+
* frontmatter so YAML parsers still find it first. An agent that lands on one
|
|
6
|
+
* page then knows where the full index is.
|
|
7
|
+
*/
|
|
8
|
+
export declare function insertLlmsTxtDirective(markdown: string, llmsTxtUrl: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* The HTML equivalent: a link at the top of the body, hidden visually but
|
|
11
|
+
* kept for screen readers and agents that read the raw HTML.
|
|
12
|
+
*/
|
|
13
|
+
export declare function llmsTxtDirectiveHtml(llmsTxtPath: string): string;
|
|
14
|
+
/** Remove the injected HTML link, so a page's Markdown carries the directive once. */
|
|
15
|
+
export declare function stripLlmsTxtDirectiveHtml(html: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const RE_FRONTMATTER = /^\uFEFF?---[\t ]*\r?\n(?:[\s\S]*?\r?\n)?---[\t ]*(?:\r?\n|$)/;
|
|
2
|
+
export function llmsTxtDirective(llmsTxtUrl) {
|
|
3
|
+
return `> Index of every page on this site: [llms.txt](${llmsTxtUrl})`;
|
|
4
|
+
}
|
|
5
|
+
export function insertLlmsTxtDirective(markdown, llmsTxtUrl) {
|
|
6
|
+
const directive = llmsTxtDirective(llmsTxtUrl);
|
|
7
|
+
const frontmatter = markdown.match(RE_FRONTMATTER)?.[0] ?? "";
|
|
8
|
+
const body = markdown.slice(frontmatter.length).replace(/^(?:\r?\n)+/, "");
|
|
9
|
+
if (body === directive || body.startsWith(`${directive}
|
|
10
|
+
`) || body.startsWith(`${directive}\r
|
|
11
|
+
`))
|
|
12
|
+
return markdown;
|
|
13
|
+
return `${frontmatter}${frontmatter ? "\n" : ""}${directive}
|
|
14
|
+
|
|
15
|
+
${body}`;
|
|
16
|
+
}
|
|
17
|
+
const RE_DIRECTIVE_HTML = /<a [^>]*data-llms-txt-directive[^>]*>[^<]*<\/a>/g;
|
|
18
|
+
export function llmsTxtDirectiveHtml(llmsTxtPath) {
|
|
19
|
+
return `<a href="${llmsTxtPath}" tabindex="-1" style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0" data-llms-txt-directive>Index of every page on this site: llms.txt</a>`;
|
|
20
|
+
}
|
|
21
|
+
export function stripLlmsTxtDirectiveHtml(html) {
|
|
22
|
+
return html.replace(RE_DIRECTIVE_HTML, "");
|
|
23
|
+
}
|
|
@@ -4,6 +4,7 @@ import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
|
4
4
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
5
5
|
import { withSiteTrailingSlash, withSiteUrl } from "#site-config/server/composables/utils";
|
|
6
6
|
import { formatLlmsTxtPageLink, normalizeLlmsTxtConfig } from "./llms-txt-format.js";
|
|
7
|
+
import { absoluteSiteUrl } from "./markdown-links.js";
|
|
7
8
|
import { hasMarkdownTwin, toMarkdownPath } from "./markdown-path.js";
|
|
8
9
|
import { normalizePersistedRoute, toDeployedRoute, toLogicalRoute } from "./route-path.js";
|
|
9
10
|
import { queryPages } from "./server/db/queries.js";
|
|
@@ -207,6 +208,8 @@ Canonical Origin: ${canonicalSiteUrl}`);
|
|
|
207
208
|
return deployedPathname;
|
|
208
209
|
};
|
|
209
210
|
}
|
|
211
|
+
const resolvePagePath = resolvePageHref;
|
|
212
|
+
resolvePageHref = (pathname) => absoluteSiteUrl(resolvePagePath(pathname), canonicalSiteUrl);
|
|
210
213
|
for (const page of [...prerendered, ...other])
|
|
211
214
|
page.href = resolvePageHref(page.pathname);
|
|
212
215
|
if (i18nRuntime._tag === "enabled") {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve root-relative link and image destinations against a site origin.
|
|
3
|
+
*
|
|
4
|
+
* A Markdown page read on its own, by an agent or from `llms-full.txt`, has
|
|
5
|
+
* no page URL to resolve `/docs/api` against, so such a link loses its
|
|
6
|
+
* target. Fenced code and inline code spans keep their text.
|
|
7
|
+
*/
|
|
8
|
+
export declare function absoluteMarkdownLinks(markdown: string, siteUrl: string): string;
|
|
9
|
+
/** A deployed path on the site's origin, or the path itself when no site URL is known. */
|
|
10
|
+
export declare function absoluteSiteUrl(path: string, siteUrl: string | undefined): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const RE_FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
2
|
+
const RE_INLINE_ROOT_LINK = /\]\((<?)\/(?!\/)/g;
|
|
3
|
+
const RE_REFERENCE_ROOT_LINK = /^( {0,3}\[[^\]]+\]:[\t ]*<?)\/(?!\/)/;
|
|
4
|
+
function closesFence(line, fence) {
|
|
5
|
+
const trimmed = line.trim();
|
|
6
|
+
return trimmed.length >= fence.length && trimmed[0] === fence[0] && [...trimmed].every((char) => char === fence[0]);
|
|
7
|
+
}
|
|
8
|
+
function backtickRun(line, start) {
|
|
9
|
+
let end = start;
|
|
10
|
+
while (line[end] === "`")
|
|
11
|
+
end++;
|
|
12
|
+
return end - start;
|
|
13
|
+
}
|
|
14
|
+
function rewriteOutsideCodeSpans(line, rewrite) {
|
|
15
|
+
let output = "";
|
|
16
|
+
let last = 0;
|
|
17
|
+
let index = line.indexOf("`");
|
|
18
|
+
while (index !== -1) {
|
|
19
|
+
const length = backtickRun(line, index);
|
|
20
|
+
let close = line.indexOf("`", index + length);
|
|
21
|
+
while (close !== -1 && backtickRun(line, close) !== length)
|
|
22
|
+
close = line.indexOf("`", close + backtickRun(line, close));
|
|
23
|
+
if (close === -1) {
|
|
24
|
+
index = line.indexOf("`", index + length);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
output += rewrite(line.slice(last, index)) + line.slice(index, close + length);
|
|
28
|
+
last = close + length;
|
|
29
|
+
index = line.indexOf("`", last);
|
|
30
|
+
}
|
|
31
|
+
return output + rewrite(line.slice(last));
|
|
32
|
+
}
|
|
33
|
+
export function absoluteMarkdownLinks(markdown, siteUrl) {
|
|
34
|
+
const origin = new URL(siteUrl).origin;
|
|
35
|
+
let fence;
|
|
36
|
+
return markdown.split(/(?<=\n)/).map((line) => {
|
|
37
|
+
if (fence) {
|
|
38
|
+
if (closesFence(line, fence))
|
|
39
|
+
fence = void 0;
|
|
40
|
+
return line;
|
|
41
|
+
}
|
|
42
|
+
const opening = line.match(RE_FENCE);
|
|
43
|
+
if (opening) {
|
|
44
|
+
fence = opening[1];
|
|
45
|
+
return line;
|
|
46
|
+
}
|
|
47
|
+
return rewriteOutsideCodeSpans(line, (text) => text.replace(RE_REFERENCE_ROOT_LINK, `$1${origin}/`).replace(RE_INLINE_ROOT_LINK, `]($1${origin}/`));
|
|
48
|
+
}).join("");
|
|
49
|
+
}
|
|
50
|
+
export function absoluteSiteUrl(path, siteUrl) {
|
|
51
|
+
return siteUrl ? new URL(path, new URL(siteUrl).origin).href : path;
|
|
52
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getRequestURL, setHeader, setResponseStatus } from "#nuxtseo/h3";
|
|
2
2
|
import { useNitroApp, useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
3
|
import { resolveLocaleAlternateUrl } from "../../i18n-url.js";
|
|
4
|
+
import { insertLlmsTxtDirective } from "../../llms-txt-directive.js";
|
|
5
|
+
import { absoluteMarkdownLinks } from "../../markdown-links.js";
|
|
4
6
|
import { logger } from "../logger.js";
|
|
5
7
|
import { computeLocaleAlternates, resolveLocaleFromRoute } from "../utils/i18n.js";
|
|
6
8
|
import { resolveMarkdownRedirect } from "../utils/markdown-redirect.js";
|
|
@@ -49,7 +51,12 @@ export default defineEventHandler(async (event) => {
|
|
|
49
51
|
const ctx = buildNegotiationContext(event, decision.path);
|
|
50
52
|
const { path, config, resolvePath, resolveUrl, routeContext } = ctx;
|
|
51
53
|
const canonicalUrl = resolveUrl(path);
|
|
52
|
-
const finalizeMarkdown = (markdown) =>
|
|
54
|
+
const finalizeMarkdown = (markdown) => {
|
|
55
|
+
const withSitemap = config.sitemapMd === false ? markdown : appendSitemapSection(markdown, resolvePath(SITEMAP_MD_ROUTE));
|
|
56
|
+
const llmsTxt = config.llmsTxt;
|
|
57
|
+
const withDirective = llmsTxt?.directive?.markdown === false ? withSitemap : insertLlmsTxtDirective(withSitemap, resolveUrl("/llms.txt"));
|
|
58
|
+
return absoluteMarkdownLinks(withDirective, canonicalUrl);
|
|
59
|
+
};
|
|
53
60
|
const [
|
|
54
61
|
{ tryGetContentMarkdown },
|
|
55
62
|
{ fetchRawWithEvent },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createError, defineEventHandler } from "#nuxtseo/h3";
|
|
2
2
|
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
3
|
import { withSiteUrl } from "#site-config/server/composables/utils";
|
|
4
|
+
import { absoluteMarkdownLinks } from "../../markdown-links.js";
|
|
4
5
|
import { toDeployedRoute } from "../../route-path.js";
|
|
5
6
|
import { logger } from "../logger.js";
|
|
6
7
|
import { convertHtmlToMarkdown, extractLastUpdated } from "../utils.js";
|
|
@@ -52,7 +53,7 @@ export default defineEventHandler(async (event) => {
|
|
|
52
53
|
last_updated: contentPage.updatedAt
|
|
53
54
|
});
|
|
54
55
|
const markdown = `${frontmatter}
|
|
55
|
-
${contentPage.markdown}`;
|
|
56
|
+
${absoluteMarkdownLinks(contentPage.markdown, canonicalUrl)}`;
|
|
56
57
|
const headings = extractHeadingsFromMarkdown(contentPage.markdown);
|
|
57
58
|
const keywords2 = extractKeywords(contentPage.markdown, "");
|
|
58
59
|
return JSON.stringify({
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineNitroPlugin, useRuntimeConfig } from "#nuxtseo/nitro";
|
|
2
|
+
import { llmsTxtDirectiveHtml } from "../../llms-txt-directive.js";
|
|
3
|
+
import { toDeployedRoute } from "../../route-path.js";
|
|
4
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
5
|
+
nitroApp.hooks.hook("render:html", (html, { event }) => {
|
|
6
|
+
html.bodyPrepend.unshift(llmsTxtDirectiveHtml(toDeployedRoute("/llms.txt", useRuntimeConfig(event).app.baseURL)));
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -2,6 +2,7 @@ import { eventHandler, setHeader } from "#nuxtseo/h3";
|
|
|
2
2
|
import { defineCachedFunction, useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
3
|
import { getSiteConfig } from "#site-config/server/composables";
|
|
4
4
|
import { publicCacheControl } from "../../cache-control.js";
|
|
5
|
+
import { absoluteSiteUrl } from "../../markdown-links.js";
|
|
5
6
|
import { toMarkdownPath } from "../../markdown-path.js";
|
|
6
7
|
import { toDeployedRoute } from "../../route-path.js";
|
|
7
8
|
import { queryPages } from "../db/queries.js";
|
|
@@ -17,11 +18,12 @@ async function buildSitemapMarkdown(event) {
|
|
|
17
18
|
);
|
|
18
19
|
}
|
|
19
20
|
const baseURL = useRuntimeConfig(event).app.baseURL;
|
|
21
|
+
const siteConfig = getSiteConfig(event);
|
|
20
22
|
return buildSitemapMd(
|
|
21
23
|
pages.map((page) => ({ route: page.route, title: page.title, updatedAt: page.updatedAt })),
|
|
22
24
|
{
|
|
23
|
-
siteName:
|
|
24
|
-
resolveHref: (route) => toDeployedRoute(toMarkdownPath(route), baseURL)
|
|
25
|
+
siteName: siteConfig.name,
|
|
26
|
+
resolveHref: (route) => absoluteSiteUrl(toDeployedRoute(toMarkdownPath(route), baseURL), siteConfig.url)
|
|
25
27
|
}
|
|
26
28
|
);
|
|
27
29
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { htmlToMarkdown } from "mdream";
|
|
2
2
|
import { useNitroApp } from "#nuxtseo/nitro";
|
|
3
|
+
import { stripLlmsTxtDirectiveHtml } from "../llms-txt-directive.js";
|
|
3
4
|
import { hoistMetaDescription } from "./utils/frontmatter.js";
|
|
4
5
|
const RE_NBSP = /\u00A0/g;
|
|
5
6
|
function normalizeWhitespace(text) {
|
|
@@ -75,7 +76,7 @@ export async function convertHtmlToMarkdown(html, url, mdreamOptions, opts) {
|
|
|
75
76
|
await nitroApp.hooks.callHook("ai-ready:mdreamConfig", options);
|
|
76
77
|
const context = {
|
|
77
78
|
html,
|
|
78
|
-
markdown: hoistMetaDescription(htmlToMarkdown(html, options)),
|
|
79
|
+
markdown: hoistMetaDescription(htmlToMarkdown(stripLlmsTxtDirectiveHtml(html), options)),
|
|
79
80
|
route: opts.hooks.route,
|
|
80
81
|
title: meta.title,
|
|
81
82
|
description: meta.description,
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -529,9 +529,25 @@ export interface LlmsTxtConfig {
|
|
|
529
529
|
* Rewrite automatically generated page links to their Markdown representation
|
|
530
530
|
* when the final static output contains the file or the deployment retains
|
|
531
531
|
* the runtime Markdown handler.
|
|
532
|
-
* @default
|
|
532
|
+
* @default true
|
|
533
533
|
*/
|
|
534
534
|
markdownLinks?: boolean;
|
|
535
|
+
/**
|
|
536
|
+
* Point each page at llms.txt, so an agent that lands on one page finds the
|
|
537
|
+
* whole site's index.
|
|
538
|
+
*/
|
|
539
|
+
directive?: {
|
|
540
|
+
/**
|
|
541
|
+
* Add a blockquote line after the frontmatter of every page's `.md` response.
|
|
542
|
+
* @default true
|
|
543
|
+
*/
|
|
544
|
+
markdown?: boolean;
|
|
545
|
+
/**
|
|
546
|
+
* Add a visually hidden link to `/llms.txt` at the top of every HTML page's body.
|
|
547
|
+
* @default false
|
|
548
|
+
*/
|
|
549
|
+
html?: boolean;
|
|
550
|
+
};
|
|
535
551
|
/** The sections of the documentation */
|
|
536
552
|
sections?: LlmsTxtSection[];
|
|
537
553
|
/** Additional heading-free preamble notes rendered before file-list sections */
|
|
@@ -666,18 +666,42 @@ function cloudflareStaticFallbackPaths(existing, pages, environments = []) {
|
|
|
666
666
|
const patterns = [...existing || [], ...environments.flatMap((routing) => Array.isArray(routing) ? routing : [])];
|
|
667
667
|
return pagePaths(pages).filter((path) => !patterns.some((pattern) => matchesPattern(path, pattern.startsWith("!") ? pattern.slice(1) : pattern)));
|
|
668
668
|
}
|
|
669
|
-
function
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
669
|
+
function parentDirectories(path) {
|
|
670
|
+
const segments = path.split("/").filter(Boolean);
|
|
671
|
+
return segments.slice(0, -1).map((_, index) => `/${segments.slice(0, index + 1).join("/")}/`);
|
|
672
|
+
}
|
|
673
|
+
function fitWorkerFirstBudget(paths, budget) {
|
|
674
|
+
let remaining = [...paths];
|
|
675
|
+
const globs = [];
|
|
676
|
+
while (remaining.length + globs.length > budget) {
|
|
677
|
+
const counts = /* @__PURE__ */ new Map();
|
|
678
|
+
for (const path of remaining) {
|
|
679
|
+
for (const directory2 of parentDirectories(path))
|
|
680
|
+
counts.set(directory2, (counts.get(directory2) ?? 0) + 1);
|
|
681
|
+
}
|
|
682
|
+
let best;
|
|
683
|
+
for (const [directory2, count] of counts) {
|
|
684
|
+
if (!best || count > best[1] || count === best[1] && directory2.length > best[0].length)
|
|
685
|
+
best = [directory2, count];
|
|
686
|
+
}
|
|
687
|
+
if (!best || best[1] < 2)
|
|
688
|
+
return void 0;
|
|
689
|
+
const [directory] = best;
|
|
690
|
+
globs.push(`${directory}*`);
|
|
691
|
+
remaining = remaining.filter((path) => !path.startsWith(directory) || path === directory);
|
|
676
692
|
}
|
|
677
|
-
|
|
678
|
-
|
|
693
|
+
return [...remaining, ...globs];
|
|
694
|
+
}
|
|
695
|
+
function cloudflareWorkerFirstAdditions(existing, pages) {
|
|
696
|
+
if (existing === true || existing === false || pages.length === 0)
|
|
697
|
+
return [];
|
|
698
|
+
const patterns = existing || [];
|
|
699
|
+
const paths = cloudflareStaticFallbackPaths(existing, pages).filter((path) => !patterns.includes(path));
|
|
700
|
+
const additions = fitWorkerFirstBudget(paths, MAX_WORKER_FIRST_PATTERNS - patterns.length);
|
|
701
|
+
if (!additions) {
|
|
702
|
+
throw new Error("Cloudflare Markdown negotiation exceeds 100 worker-first patterns, even with directory globs. Configure broader assets.run_worker_first patterns, or disable aiReady.contentNegotiation.");
|
|
679
703
|
}
|
|
680
|
-
return
|
|
704
|
+
return additions;
|
|
681
705
|
}
|
|
682
706
|
|
|
683
707
|
const CACHE_GUIDE = "https://nuxtseo.com/docs/ai-ready/guides/markdown#cache-safety";
|
|
@@ -1219,6 +1243,14 @@ function createMcpServerCardEtag(card) {
|
|
|
1219
1243
|
return `"${createHash("sha256").update(JSON.stringify(card)).digest("hex")}"`;
|
|
1220
1244
|
}
|
|
1221
1245
|
|
|
1246
|
+
function opaqueJsonModule(exports) {
|
|
1247
|
+
const encode = (value) => JSON.stringify(Buffer.from(JSON.stringify(value), "utf8").toString("base64"));
|
|
1248
|
+
return [
|
|
1249
|
+
"const decode = value => JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(value), char => char.charCodeAt(0))))",
|
|
1250
|
+
...Object.entries(exports).map(([name, value]) => `export const ${name} = decode(${encode(value)})`)
|
|
1251
|
+
].join("\n");
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1222
1254
|
function escapeRegExp(value) {
|
|
1223
1255
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1224
1256
|
}
|
|
@@ -1828,14 +1860,19 @@ ${details}`);
|
|
|
1828
1860
|
}))
|
|
1829
1861
|
});
|
|
1830
1862
|
}
|
|
1863
|
+
const llmsTxtDirective = {
|
|
1864
|
+
markdown: config.llmsTxt?.directive?.markdown !== false,
|
|
1865
|
+
html: config.llmsTxt?.directive?.html === true
|
|
1866
|
+
};
|
|
1831
1867
|
const mergedLlmsTxt = config.llmsTxt ? {
|
|
1832
|
-
markdownLinks: config.llmsTxt.markdownLinks ??
|
|
1868
|
+
markdownLinks: config.llmsTxt.markdownLinks ?? true,
|
|
1869
|
+
directive: llmsTxtDirective,
|
|
1833
1870
|
sections: [
|
|
1834
1871
|
...defaultLlmsTxtSections,
|
|
1835
1872
|
...config.llmsTxt.sections || []
|
|
1836
1873
|
],
|
|
1837
1874
|
notes: config.llmsTxt.notes
|
|
1838
|
-
} : { markdownLinks:
|
|
1875
|
+
} : { markdownLinks: true, directive: llmsTxtDirective, sections: defaultLlmsTxtSections };
|
|
1839
1876
|
const llmsTxtPayload = {
|
|
1840
1877
|
sections: mergedLlmsTxt.sections || [],
|
|
1841
1878
|
notes: typeof mergedLlmsTxt.notes === "string" ? [mergedLlmsTxt.notes] : mergedLlmsTxt.notes || []
|
|
@@ -2160,8 +2197,7 @@ export { unavailable as computeLocaleAlternates, unavailable as localePath, unav
|
|
|
2160
2197
|
`;
|
|
2161
2198
|
};
|
|
2162
2199
|
nitroConfig.virtual["#ai-ready-virtual/site-tools.mjs"] = `export default ${JSON.stringify(siteToolsConfig)}`;
|
|
2163
|
-
nitroConfig.virtual["#ai-ready-virtual/agent-skills.mjs"] = agentSkillsResult._tag === "Enabled" ?
|
|
2164
|
-
export const localAgentSkillArtifacts = ${JSON.stringify(agentSkillsResult.localArtifacts)}` : "export const agentSkillsIndex = null\nexport const localAgentSkillArtifacts = {}";
|
|
2200
|
+
nitroConfig.virtual["#ai-ready-virtual/agent-skills.mjs"] = agentSkillsResult._tag === "Enabled" ? opaqueJsonModule({ agentSkillsIndex: agentSkillsResult.index, localAgentSkillArtifacts: agentSkillsResult.localArtifacts }) : "export const agentSkillsIndex = null\nexport const localAgentSkillArtifacts = {}";
|
|
2165
2201
|
const markdownLinkAvailabilityPath = join(dirname(buildDbPath), MARKDOWN_LINK_AVAILABILITY_FILE);
|
|
2166
2202
|
nitroConfig.virtual["#ai-ready-virtual/read-page-data.mjs"] = createBuildPageDataVirtual({
|
|
2167
2203
|
buildDbPath,
|
|
@@ -2293,6 +2329,8 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2293
2329
|
}
|
|
2294
2330
|
addNitroPlugin(resolve$1(getNitroVersion(nuxt) === 3 ? "./runtime/server/plugins/link-header-nitro3" : "./runtime/server/plugins/link-header"));
|
|
2295
2331
|
addNitroPlugin(resolve$1("./runtime/server/plugins/markdown-negotiation"));
|
|
2332
|
+
if (llmsTxtDirective.html)
|
|
2333
|
+
addNitroPlugin(resolve$1("./runtime/server/plugins/llms-txt-directive-html"));
|
|
2296
2334
|
addPlugin({
|
|
2297
2335
|
mode: "server",
|
|
2298
2336
|
src: resolve$1("./runtime/app/plugins/md-alternate.server")
|
|
@@ -2446,13 +2484,12 @@ export function trackDrizzleWork(event, work) { return work }
|
|
|
2446
2484
|
const assets = wrangler.assets ||= {};
|
|
2447
2485
|
const fileRouting = await readCloudflareAssetRouting(nitro.options.rootDir);
|
|
2448
2486
|
const routing = defu$1({ value: assets.run_worker_first }, { value: fileRouting.root }).value;
|
|
2449
|
-
|
|
2487
|
+
const additions = cloudflareWorkerFirstAdditions(routing, pages);
|
|
2450
2488
|
const environmentConfig = wrangler;
|
|
2451
2489
|
const configuredEnvironments = Object.values(environmentConfig.env || {}).flatMap((env) => env.assets?.run_worker_first === void 0 ? [] : [env.assets.run_worker_first]);
|
|
2452
2490
|
const fallbackPaths = cloudflareStaticFallbackPaths(routing, pages, [...fileRouting.environments, ...configuredEnvironments]);
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
assets.run_worker_first = [...Array.isArray(assets.run_worker_first) ? assets.run_worker_first : [], ...addedPaths];
|
|
2491
|
+
if (additions.length > 0)
|
|
2492
|
+
assets.run_worker_first = [...Array.isArray(assets.run_worker_first) ? assets.run_worker_first : [], ...additions];
|
|
2456
2493
|
nitro.options.runtimeConfig["nuxt-ai-ready"].cloudflarePrerenderedPages = fallbackPaths;
|
|
2457
2494
|
}
|
|
2458
2495
|
const plan = planStaticMarkdownHeaderRules(
|
package/package.json
CHANGED
|
@@ -95,6 +95,7 @@ Runtime indexing, database drivers, Cloudflare D1, and the server query helpers:
|
|
|
95
95
|
- **The same edit in both hooks applies twice.** During prerendering the Nitro `ai-ready:page:markdown` hook runs, then the Nuxt hook of the same name. Put an edit in one of them, not both.
|
|
96
96
|
- **Content source pages skip conversion hooks.** A Nuxt Content page ignores `mdreamOptions` and both Nitro hooks.
|
|
97
97
|
- **Dev has no page data.** Query helpers, MCP tools, and search return empty results in `nuxt dev`. Test with `nuxt build` and `node .output/server/index.mjs`.
|
|
98
|
+
- **Cloudflare static pages negotiate only with `contentNegotiation: true`.** The default leaves prerendered HTML on the asset service, so `Accept: text/markdown` gets HTML there. With `true`, prerendered pages go into `assets.run_worker_first`: exact paths up to Cloudflare's 100 entries, directory globs such as `/docs/*` past that. More than 100 top-level pages fail the build; set broader patterns yourself.
|
|
98
99
|
- **Response caching turns negotiation off.** An ISR, `swr`, or `cache` route rule without `varies: ['accept']` serves HTML to Markdown clients. With `botNegotiation: true`, the rule needs `varies: ['accept', 'sec-fetch-dest', 'user-agent']`. The `.md` URL still works. For a CDN that caches by URL only, set `contentNegotiation: false`.
|
|
99
100
|
- **Emptying `ctx.markdown` does not hide a page.** It drops the body from `llms-full.txt`. The URL stays in `llms.txt` and in the index. Use `sitemap.exclude` for discovery, and access control for private pages.
|
|
100
101
|
- **Root `skills/` folders require explicit publication.** Automatic discovery scans public directories only. Set `agentSkills.dir: 'skills'` to publish root skills intentionally.
|
|
@@ -125,7 +126,8 @@ Runtime indexing, database drivers, Cloudflare D1, and the server query helpers:
|
|
|
125
126
|
|
|
126
127
|
## Config
|
|
127
128
|
|
|
128
|
-
- `llmsTxt.
|
|
129
|
+
- `llmsTxt.directive` (`{ markdown: true, html: false }`): a `> Index of every page on this site: [llms.txt](...)` line after the frontmatter of each page's `.md`; `html: true` adds a visually hidden `/llms.txt` link at the top of each HTML body.
|
|
130
|
+
- `llmsTxt.markdownLinks` (`true`): link page entries in `llms.txt` to their `.md` twins. `false` links canonical URLs. With `site.url` set, page links in `llms.txt`, `sitemap.md`, and `.md` bodies are absolute.
|
|
129
131
|
- `describedby` (`true`): the `describedby` link tag and header.
|
|
130
132
|
- `tools.<listPages|searchPages|getPageMarkdown>.<mcp|webmcp>.enabled`: attach a built in tool to one transport only.
|
|
131
133
|
- `autoI18n` (`true`): with `@nuxtjs/i18n`, adds hreflang `Link` headers and a languages section in `llms.txt`.
|