nuxt-ai-ready 1.5.7 → 1.5.8
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 +83 -6
- package/dist/runtime/llms-txt-utils.js +34 -2
- package/dist/runtime/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { mkdir, writeFile, appendFile, stat, access, readFile } from 'node:fs/promises';
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
4
|
-
import { useLogger, useNuxt, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, hasNuxtModule, addServerPlugin, addServerHandler, addPlugin } from '@nuxt/kit';
|
|
2
|
+
import { mkdir, writeFile, appendFile, stat, readdir, access, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, dirname, relative, resolve } from 'node:path';
|
|
4
|
+
import { useLogger, useNuxt, resolveFiles, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, hasNuxtModule, addServerPlugin, addServerHandler, addPlugin } from '@nuxt/kit';
|
|
5
5
|
import defu from 'defu';
|
|
6
6
|
import { installNuxtSiteConfig, useSiteConfig, withSiteUrl } from 'nuxt-site-config/kit';
|
|
7
7
|
import { setupDevToolsUI } from 'nuxtseo-shared/devtools';
|
|
@@ -9,7 +9,7 @@ import { resolveNuxtContentVersion } from 'nuxtseo-shared/kit';
|
|
|
9
9
|
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
10
10
|
import { parseSitemapXml } from '@nuxtjs/sitemap/utils';
|
|
11
11
|
import { colorize } from 'consola/utils';
|
|
12
|
-
import { withLeadingSlash, withBase } from 'ufo';
|
|
12
|
+
import { withLeadingSlash, withBase, joinURL } from 'ufo';
|
|
13
13
|
import { normalizePagePath, toMarkdownPath } from '../dist/runtime/markdown-path.js';
|
|
14
14
|
import { toLogicalRoute, toDeployedRoute } from '../dist/runtime/route-path.js';
|
|
15
15
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../dist/runtime/server/db/shared.js';
|
|
@@ -25,6 +25,41 @@ const PRERENDER_PAGE_TIMEOUT = 3e4;
|
|
|
25
25
|
const RE_HTML_MD_EXT = /\.(html|md)$/;
|
|
26
26
|
const RE_INDEX_SUFFIX = /\/index$/;
|
|
27
27
|
const RE_MD_EXT = /\.md$/;
|
|
28
|
+
const RE_NEGATED_GLOB = /^(!?)(.*)$/;
|
|
29
|
+
const RE_PARENT_DIR_GLOB = /!?\.\.\//;
|
|
30
|
+
const MARKDOWN_LINK_AVAILABILITY_FILE = "markdown-link-availability.json";
|
|
31
|
+
async function findOutputMarkdownPaths(publicDir, baseURL, srcDir, ignoredPaths, publicAssets) {
|
|
32
|
+
const paths = /* @__PURE__ */ new Set();
|
|
33
|
+
async function visit(directory, urlBase, relativeDirectory = "") {
|
|
34
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
35
|
+
await Promise.all(entries.map(async (entry) => {
|
|
36
|
+
const relativePath = join(relativeDirectory, entry.name);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
await visit(join(directory, entry.name), urlBase, relativePath);
|
|
39
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
40
|
+
const urlPath = joinURL(urlBase, relativePath.replaceAll("\\", "/"));
|
|
41
|
+
paths.add(joinURL(baseURL, urlPath));
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
await visit(publicDir, "/");
|
|
46
|
+
for (const asset of publicAssets) {
|
|
47
|
+
const patterns = [
|
|
48
|
+
"**/*.md",
|
|
49
|
+
...ignoredPaths.map((ignoredPath) => {
|
|
50
|
+
const [, negated = "", pattern = ""] = ignoredPath.match(RE_NEGATED_GLOB) || [];
|
|
51
|
+
const assetPattern = pattern.startsWith("*") ? pattern : relative(asset.dir, resolve(srcDir, pattern)).replaceAll("\\", "/");
|
|
52
|
+
return `${negated ? "" : "!"}${assetPattern}`;
|
|
53
|
+
}).filter((pattern) => !RE_PARENT_DIR_GLOB.test(pattern))
|
|
54
|
+
];
|
|
55
|
+
const files = await resolveFiles(asset.dir, patterns);
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const urlPath = joinURL(asset.baseURL || "/", relative(asset.dir, file).replaceAll("\\", "/"));
|
|
58
|
+
paths.add(joinURL(baseURL, urlPath));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [...paths].sort();
|
|
62
|
+
}
|
|
28
63
|
async function fetchPreviousMeta(siteUrl, indexNow) {
|
|
29
64
|
const metaUrl = withBase("/__ai-ready/pages.meta.json", siteUrl);
|
|
30
65
|
logger.info(`Fetching previous build meta from ${metaUrl}`);
|
|
@@ -399,6 +434,20 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
|
|
|
399
434
|
await submitIndexNow(changed, added, state.siteInfo.url, state.indexNow);
|
|
400
435
|
}
|
|
401
436
|
}
|
|
437
|
+
if (state.dbPath && state.llmsTxtConfig?.markdownLinks) {
|
|
438
|
+
const availabilityFile = join(dirname(state.dbPath), MARKDOWN_LINK_AVAILABILITY_FILE);
|
|
439
|
+
const paths = await findOutputMarkdownPaths(
|
|
440
|
+
nitro.options.output.publicDir,
|
|
441
|
+
nitro.options.baseURL,
|
|
442
|
+
nitro.options.srcDir,
|
|
443
|
+
nitro.options.ignore,
|
|
444
|
+
nitro.options.publicAssets
|
|
445
|
+
);
|
|
446
|
+
await writeFile(availabilityFile, JSON.stringify({
|
|
447
|
+
runtimeMarkdownAvailable: !nitro.options.static,
|
|
448
|
+
paths
|
|
449
|
+
}), "utf-8");
|
|
450
|
+
}
|
|
402
451
|
const llmsStats = await prerenderRoute(nitro, "/llms.txt");
|
|
403
452
|
const llmsFullStats = await stat(state.llmsFullTxtPath);
|
|
404
453
|
nitro._prerenderedRoutes.push({ route: "/llms-full.txt", fileName: "/llms-full.txt" });
|
|
@@ -464,6 +513,10 @@ export {}
|
|
|
464
513
|
addTemplate({
|
|
465
514
|
filename: "types/ai-ready-virtual.d.ts",
|
|
466
515
|
getContents: () => `declare module '#ai-ready-virtual/read-page-data.mjs' {
|
|
516
|
+
export function readMarkdownLinkAvailabilityFromFilesystem(): Promise<{
|
|
517
|
+
runtimeMarkdownAvailable: boolean
|
|
518
|
+
paths: string[]
|
|
519
|
+
}>
|
|
467
520
|
export function readPageDataFromFilesystem(): Promise<{
|
|
468
521
|
pages: Array<{
|
|
469
522
|
route: string
|
|
@@ -765,12 +818,13 @@ const module$1 = defineNuxtModule({
|
|
|
765
818
|
}
|
|
766
819
|
}
|
|
767
820
|
const mergedLlmsTxt = config.llmsTxt ? {
|
|
821
|
+
markdownLinks: config.llmsTxt.markdownLinks ?? false,
|
|
768
822
|
sections: [
|
|
769
823
|
...defaultLlmsTxtSections,
|
|
770
824
|
...config.llmsTxt.sections || []
|
|
771
825
|
],
|
|
772
826
|
notes: config.llmsTxt.notes
|
|
773
|
-
} : { sections: defaultLlmsTxtSections };
|
|
827
|
+
} : { markdownLinks: false, sections: defaultLlmsTxtSections };
|
|
774
828
|
const llmsTxtPayload = {
|
|
775
829
|
sections: mergedLlmsTxt.sections || [],
|
|
776
830
|
notes: typeof mergedLlmsTxt.notes === "string" ? [mergedLlmsTxt.notes] : mergedLlmsTxt.notes || []
|
|
@@ -859,7 +913,30 @@ const module$1 = defineNuxtModule({
|
|
|
859
913
|
}
|
|
860
914
|
}
|
|
861
915
|
nitroConfig.virtual = nitroConfig.virtual || {};
|
|
862
|
-
|
|
916
|
+
const markdownLinkAvailabilityPath = join(dirname(buildDbPath), MARKDOWN_LINK_AVAILABILITY_FILE);
|
|
917
|
+
nitroConfig.virtual["#ai-ready-virtual/read-page-data.mjs"] = nuxt.options.dev ? `
|
|
918
|
+
export async function readPageDataFromFilesystem() { return { pages: [], errorRoutes: [] } }
|
|
919
|
+
export async function readMarkdownLinkAvailabilityFromFilesystem() { return { runtimeMarkdownAvailable: false, paths: [] } }
|
|
920
|
+
` : `
|
|
921
|
+
export async function readMarkdownLinkAvailabilityFromFilesystem() {
|
|
922
|
+
if (!import.meta.prerender) {
|
|
923
|
+
return { runtimeMarkdownAvailable: false, paths: [] }
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const { readFile } = await import('node:fs/promises')
|
|
927
|
+
try {
|
|
928
|
+
const availability = JSON.parse(await readFile(${JSON.stringify(markdownLinkAvailabilityPath)}, 'utf8'))
|
|
929
|
+
return {
|
|
930
|
+
runtimeMarkdownAvailable: availability?.runtimeMarkdownAvailable === true,
|
|
931
|
+
paths: Array.isArray(availability?.paths) ? availability.paths.filter(path => typeof path === 'string') : [],
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
console.warn('[nuxt-ai-ready] Failed to read Markdown link availability; keeping canonical links.', error)
|
|
936
|
+
return { runtimeMarkdownAvailable: false, paths: [] }
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
863
940
|
export async function readPageDataFromFilesystem() {
|
|
864
941
|
if (!import.meta.prerender) {
|
|
865
942
|
return { pages: [], errorRoutes: [] }
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
|
+
import { decodePath } from "ufo";
|
|
2
3
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
3
4
|
import { withSiteTrailingSlash, withSiteUrl } from "#site-config/server/composables/utils";
|
|
4
5
|
import { formatAvailableLanguagesSection, formatLlmsTxtPageLink, normalizeLlmsTxtConfig } from "./llms-txt-format.js";
|
|
6
|
+
import { toMarkdownPath } from "./markdown-path.js";
|
|
5
7
|
import { normalizePersistedRoute, toDeployedRoute, toLogicalRoute } from "./route-path.js";
|
|
6
8
|
import { queryPages } from "./server/db/queries.js";
|
|
7
9
|
import { logger } from "./server/logger.js";
|
|
8
10
|
import { resolveLocaleFromRoute } from "./server/utils/i18n.js";
|
|
9
11
|
import { fetchSitemapUrls } from "./server/utils/sitemap.js";
|
|
10
12
|
export { normalizeLlmsTxtConfig };
|
|
13
|
+
function hasRuntimeMarkdownHandler(pathname) {
|
|
14
|
+
const lastSegment = pathname.split("/").pop() || "";
|
|
15
|
+
return !pathname.startsWith("/api") && !pathname.startsWith("/_") && !pathname.startsWith("/@") && !lastSegment.includes(".");
|
|
16
|
+
}
|
|
17
|
+
async function findMarkdownLinkAvailability() {
|
|
18
|
+
if (!import.meta.prerender) {
|
|
19
|
+
return {
|
|
20
|
+
runtimeMarkdownAvailable: true,
|
|
21
|
+
paths: /* @__PURE__ */ new Set()
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const { readMarkdownLinkAvailabilityFromFilesystem } = await import("#ai-ready-virtual/read-page-data.mjs");
|
|
25
|
+
const availability = await readMarkdownLinkAvailabilityFromFilesystem();
|
|
26
|
+
return {
|
|
27
|
+
runtimeMarkdownAvailable: availability.runtimeMarkdownAvailable,
|
|
28
|
+
paths: new Set(availability.paths.map(decodePath))
|
|
29
|
+
};
|
|
30
|
+
}
|
|
11
31
|
function getGroupPrefix(url, depth) {
|
|
12
32
|
const segments = url.split("/").filter(Boolean);
|
|
13
33
|
if (segments.length === 0)
|
|
@@ -177,8 +197,20 @@ Canonical Origin: ${canonicalSiteUrl}`);
|
|
|
177
197
|
seenPaths.add(pathname);
|
|
178
198
|
}
|
|
179
199
|
}
|
|
200
|
+
let resolvePageHref = resolvePath;
|
|
201
|
+
if (llmsTxtConfig.markdownLinks) {
|
|
202
|
+
const markdownLinkAvailability = await findMarkdownLinkAvailability();
|
|
203
|
+
resolvePageHref = (pathname) => {
|
|
204
|
+
const deployedPathname = resolvePath(pathname);
|
|
205
|
+
const markdownPath = resolvePath(toMarkdownPath(pathname));
|
|
206
|
+
if (markdownLinkAvailability.paths.has(decodePath(markdownPath)) || markdownLinkAvailability.runtimeMarkdownAvailable && hasRuntimeMarkdownHandler(pathname)) {
|
|
207
|
+
return markdownPath;
|
|
208
|
+
}
|
|
209
|
+
return deployedPathname;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
180
212
|
for (const page of [...prerendered, ...other])
|
|
181
|
-
page.href =
|
|
213
|
+
page.href = resolvePageHref(page.pathname);
|
|
182
214
|
if (i18n) {
|
|
183
215
|
const pageCounts = /* @__PURE__ */ new Map();
|
|
184
216
|
for (const locale of i18n.locales) pageCounts.set(locale.code, 0);
|
|
@@ -186,7 +218,7 @@ Canonical Origin: ${canonicalSiteUrl}`);
|
|
|
186
218
|
const code = p.locale || resolveLocaleFromRoute(p.pathname, i18n).locale;
|
|
187
219
|
pageCounts.set(code, (pageCounts.get(code) ?? 0) + 1);
|
|
188
220
|
}
|
|
189
|
-
parts.push(...formatAvailableLanguagesSection(i18n, pageCounts,
|
|
221
|
+
parts.push(...formatAvailableLanguagesSection(i18n, pageCounts, resolvePageHref));
|
|
190
222
|
parts.push("");
|
|
191
223
|
}
|
|
192
224
|
const isDefaultLocale = (item) => {
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -271,6 +271,13 @@ export interface LlmsTxtSection {
|
|
|
271
271
|
* Structured llms.txt configuration
|
|
272
272
|
*/
|
|
273
273
|
export interface LlmsTxtConfig {
|
|
274
|
+
/**
|
|
275
|
+
* Rewrite automatically generated page links to their Markdown representation
|
|
276
|
+
* when the final static output contains the file or the deployment retains
|
|
277
|
+
* the runtime Markdown handler.
|
|
278
|
+
* @default false
|
|
279
|
+
*/
|
|
280
|
+
markdownLinks?: boolean;
|
|
274
281
|
/** The sections of the documentation */
|
|
275
282
|
sections?: LlmsTxtSection[];
|
|
276
283
|
/** Additional heading-free preamble notes rendered before file-list sections */
|