nuxt-ai-ready 1.5.4 → 1.5.6
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 +72 -23
- package/dist/runtime/llms-txt-utils.js +42 -20
- package/dist/runtime/markdown-path.d.ts +1 -0
- package/dist/runtime/markdown-path.js +6 -2
- package/dist/runtime/route-path.d.ts +10 -0
- package/dist/runtime/route-path.js +16 -0
- package/dist/runtime/server/db/queries.js +9 -9
- package/dist/runtime/server/middleware/markdown.js +16 -12
- package/dist/runtime/server/middleware/markdown.prerender.js +29 -19
- package/dist/runtime/server/plugins/html-capture.prerender.d.ts +2 -0
- package/dist/runtime/server/plugins/html-capture.prerender.js +10 -0
- package/dist/runtime/server/routes/llms-full.txt.get.js +7 -2
- package/dist/runtime/server/utils/indexnow-shared.js +3 -2
- package/dist/runtime/server/utils/link-header.js +4 -2
- package/dist/runtime/server/utils/llms-full.js +2 -2
- package/dist/runtime/server/utils/prerender-html.d.ts +2 -0
- package/dist/runtime/server/utils/prerender-html.js +13 -0
- package/package.json +18 -18
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 } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, writeFile, appendFile, stat, access, readFile } from 'node:fs/promises';
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
|
-
import { useLogger, useNuxt, addTypeTemplate, addTemplate, defineNuxtModule, createResolver, hasNuxtModule, addServerHandler, addPlugin
|
|
4
|
+
import { useLogger, useNuxt, 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,8 +9,9 @@ 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 { withBase } from 'ufo';
|
|
13
|
-
import { toMarkdownPath } from '../dist/runtime/markdown-path.js';
|
|
12
|
+
import { withLeadingSlash, withBase } from 'ufo';
|
|
13
|
+
import { normalizePagePath, toMarkdownPath } from '../dist/runtime/markdown-path.js';
|
|
14
|
+
import { toLogicalRoute, toDeployedRoute } from '../dist/runtime/route-path.js';
|
|
14
15
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../dist/runtime/server/db/shared.js';
|
|
15
16
|
import { comparePageHashes, submitToIndexNowShared } from '../dist/runtime/server/utils/indexnow-shared.js';
|
|
16
17
|
import { buildLlmsFullTxtHeader, formatPageForLlmsFullTxt } from '../dist/runtime/server/utils/llms-full.js';
|
|
@@ -25,7 +26,7 @@ const RE_HTML_MD_EXT = /\.(html|md)$/;
|
|
|
25
26
|
const RE_INDEX_SUFFIX = /\/index$/;
|
|
26
27
|
const RE_MD_EXT = /\.md$/;
|
|
27
28
|
async function fetchPreviousMeta(siteUrl, indexNow) {
|
|
28
|
-
const metaUrl =
|
|
29
|
+
const metaUrl = withBase("/__ai-ready/pages.meta.json", siteUrl);
|
|
29
30
|
logger.info(`Fetching previous build meta from ${metaUrl}`);
|
|
30
31
|
const controller = new AbortController();
|
|
31
32
|
const timeoutId = setTimeout(() => controller.abort(), BUILD_FETCH_TIMEOUT);
|
|
@@ -41,7 +42,7 @@ async function fetchPreviousMeta(siteUrl, indexNow) {
|
|
|
41
42
|
}
|
|
42
43
|
logger.info(`Previous build: ${prevMeta.pageCount} pages (buildId: ${prevMeta.buildId})`);
|
|
43
44
|
if (indexNow) {
|
|
44
|
-
const keyUrl =
|
|
45
|
+
const keyUrl = withBase(`/${indexNow}.txt`, siteUrl);
|
|
45
46
|
const keyLive = await fetch(keyUrl, { signal: AbortSignal.timeout(5e3) }).then((r) => r.ok).catch(() => false);
|
|
46
47
|
if (!keyLive) {
|
|
47
48
|
logger.info("IndexNow key file not live yet - IndexNow submission will be skipped");
|
|
@@ -125,14 +126,18 @@ function resolveRouteLocale(route, i18n) {
|
|
|
125
126
|
return matched ? matched.code : i18n.defaultLocale;
|
|
126
127
|
}
|
|
127
128
|
async function processMarkdownRoute(state, nuxt, route, parsed, lastmod, options) {
|
|
128
|
-
|
|
129
|
+
route = normalizePagePath(route);
|
|
130
|
+
const { title, description, headings, keywords, updatedAt: metaUpdatedAt } = parsed;
|
|
129
131
|
let updatedAt = (lastmod instanceof Date ? lastmod.toISOString() : lastmod) || (/* @__PURE__ */ new Date()).toISOString();
|
|
130
132
|
if (metaUpdatedAt) {
|
|
131
133
|
const parsedDate = new Date(metaUpdatedAt);
|
|
132
134
|
if (!Number.isNaN(parsedDate.getTime()))
|
|
133
135
|
updatedAt = parsedDate.toISOString();
|
|
134
136
|
}
|
|
135
|
-
|
|
137
|
+
const hookContext = { ...parsed, route };
|
|
138
|
+
await nuxt.hooks.callHook("ai-ready:page:markdown", hookContext);
|
|
139
|
+
const { markdown } = hookContext;
|
|
140
|
+
parsed.markdown = markdown;
|
|
136
141
|
if (state.db) {
|
|
137
142
|
const contentHash = await computeContentHash(markdown);
|
|
138
143
|
await insertPage(state.db, {
|
|
@@ -147,7 +152,7 @@ async function processMarkdownRoute(state, nuxt, route, parsed, lastmod, options
|
|
|
147
152
|
locale: resolveRouteLocale(route, state.i18n)
|
|
148
153
|
});
|
|
149
154
|
}
|
|
150
|
-
if (state.llmsFullTxtPath && !options?.skipLlmsFullTxt) {
|
|
155
|
+
if (state.llmsFullTxtPath && !options?.skipLlmsFullTxt && markdown.trim()) {
|
|
151
156
|
const pageContent = formatPageForLlmsFullTxt(route, title, description, markdown, state.siteInfo?.url);
|
|
152
157
|
logger.debug(`Appending to llms-full.txt: ${route} (${(pageContent.length / 1024).toFixed(1)}kb)`);
|
|
153
158
|
await appendFile(state.llmsFullTxtPath, pageContent, "utf-8");
|
|
@@ -157,7 +162,7 @@ async function processMarkdownRoute(state, nuxt, route, parsed, lastmod, options
|
|
|
157
162
|
async function processSitemapEntry(state, nuxt, nitro, entry) {
|
|
158
163
|
const loc = typeof entry === "string" ? entry : entry.loc;
|
|
159
164
|
const lastmod = typeof entry === "string" ? void 0 : entry.lastmod;
|
|
160
|
-
const route = loc.
|
|
165
|
+
const route = toLogicalRoute(loc, nitro.options.baseURL);
|
|
161
166
|
if (route.split("/").some((segment) => segment.startsWith("_"))) {
|
|
162
167
|
return { crawled: false, skipped: true };
|
|
163
168
|
}
|
|
@@ -165,7 +170,7 @@ async function processSitemapEntry(state, nuxt, nitro, entry) {
|
|
|
165
170
|
return { crawled: false, skipped: true };
|
|
166
171
|
}
|
|
167
172
|
const mdRoute = toMarkdownPath(route);
|
|
168
|
-
const mdUrl =
|
|
173
|
+
const mdUrl = toDeployedRoute(mdRoute, nitro.options.baseURL);
|
|
169
174
|
logger.debug(`Fetching markdown for ${route} \u2192 ${mdUrl}`);
|
|
170
175
|
const res = await globalThis.$fetch(mdUrl, {
|
|
171
176
|
headers: { "x-nitro-prerender": mdRoute },
|
|
@@ -288,7 +293,8 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
|
|
|
288
293
|
let initPromise = null;
|
|
289
294
|
nitro.hooks.hook("prerender:generate", async (route) => {
|
|
290
295
|
if (route.error) {
|
|
291
|
-
const
|
|
296
|
+
const routePath = route.fileName || route.route;
|
|
297
|
+
const pageRoute2 = withLeadingSlash(routePath).replace(RE_HTML_MD_EXT, "").replace(RE_INDEX_SUFFIX, "").replace(RE_HTML_MD_EXT, "") || "/";
|
|
292
298
|
state.errorRoutes.add(pageRoute2);
|
|
293
299
|
logger.debug(`Detected error page: ${pageRoute2}`);
|
|
294
300
|
return;
|
|
@@ -566,6 +572,37 @@ async function detectI18n(opts = {}) {
|
|
|
566
572
|
return toRuntimeI18nConfig(auto);
|
|
567
573
|
}
|
|
568
574
|
|
|
575
|
+
function escapeRegExp(value) {
|
|
576
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
577
|
+
}
|
|
578
|
+
function ensureStaticHeader(contents, route, name, value) {
|
|
579
|
+
const eol = contents.includes("\r\n") ? "\r\n" : "\n";
|
|
580
|
+
const routePattern = new RegExp(`^${escapeRegExp(route)}[\\t ]*\\r?$`, "gm");
|
|
581
|
+
const routeMatches = [...contents.matchAll(routePattern)];
|
|
582
|
+
const routeMatch = routeMatches.at(-1);
|
|
583
|
+
if (!routeMatch || routeMatch.index === void 0) {
|
|
584
|
+
const separator = contents.length === 0 ? "" : contents.endsWith(`${eol}${eol}`) ? "" : contents.endsWith(eol) ? eol : `${eol}${eol}`;
|
|
585
|
+
return `${contents}${separator}${route}${eol} ${name}: ${value}${eol}`;
|
|
586
|
+
}
|
|
587
|
+
const routeLineEnd = routeMatch.index + routeMatch[0].length;
|
|
588
|
+
const blockStart = contents[routeLineEnd] === "\n" ? routeLineEnd + 1 : routeLineEnd;
|
|
589
|
+
const nextBlockPattern = /^(?![\t ]|\r?$).+/gm;
|
|
590
|
+
nextBlockPattern.lastIndex = blockStart;
|
|
591
|
+
const nextBlock = nextBlockPattern.exec(contents);
|
|
592
|
+
const blockEnd = nextBlock?.index ?? contents.length;
|
|
593
|
+
const block = contents.slice(blockStart, blockEnd);
|
|
594
|
+
const escapedName = escapeRegExp(name);
|
|
595
|
+
const existingHeaderPattern = new RegExp(
|
|
596
|
+
`^[\\t ]+(?:${escapedName}[\\t ]*:|![\\t ]*${escapedName}[\\t ]*\\r?$)`,
|
|
597
|
+
"im"
|
|
598
|
+
);
|
|
599
|
+
if (existingHeaderPattern.test(block)) {
|
|
600
|
+
return contents;
|
|
601
|
+
}
|
|
602
|
+
const prefix = blockStart === routeLineEnd ? eol : "";
|
|
603
|
+
return `${contents.slice(0, blockStart)}${prefix} ${name}: ${value}${eol}${contents.slice(blockStart)}`;
|
|
604
|
+
}
|
|
605
|
+
|
|
569
606
|
const module$1 = defineNuxtModule({
|
|
570
607
|
meta: {
|
|
571
608
|
name: "nuxt-ai-ready",
|
|
@@ -682,6 +719,8 @@ const module$1 = defineNuxtModule({
|
|
|
682
719
|
robotsOpts.groups = groups;
|
|
683
720
|
const group = {
|
|
684
721
|
userAgent: "*",
|
|
722
|
+
// Preserve nuxt-robots' default wildcard rule so the injected group remains valid.
|
|
723
|
+
disallow: [""],
|
|
685
724
|
contentSignal: [`ai-train=${config.contentSignal.aiTrain ? "yes" : "no"}`, `search=${config.contentSignal.search ? "yes" : "no"}`, `ai-input=${config.contentSignal.aiInput ? "yes" : "no"}`]
|
|
686
725
|
};
|
|
687
726
|
if (config.contentSignal.contentUsage !== false)
|
|
@@ -690,7 +729,7 @@ const module$1 = defineNuxtModule({
|
|
|
690
729
|
}
|
|
691
730
|
registerTypeTemplates();
|
|
692
731
|
const defaultLlmsTxtSections = [];
|
|
693
|
-
const llmsFullRoute = withSiteUrl("llms-full.txt");
|
|
732
|
+
const llmsFullRoute = withSiteUrl("llms-full.txt", { withBase: true });
|
|
694
733
|
defaultLlmsTxtSections.push({
|
|
695
734
|
title: "LLM Resources",
|
|
696
735
|
links: [
|
|
@@ -713,7 +752,7 @@ const module$1 = defineNuxtModule({
|
|
|
713
752
|
});
|
|
714
753
|
const mcpLink = {
|
|
715
754
|
title: "MCP",
|
|
716
|
-
href: withSiteUrl(nuxt.options.mcp !== false && nuxt.options.mcp?.route || "/mcp"),
|
|
755
|
+
href: withSiteUrl(nuxt.options.mcp !== false && nuxt.options.mcp?.route || "/mcp", { withBase: true }),
|
|
717
756
|
description: "Model Context Protocol server endpoint for AI agent integration."
|
|
718
757
|
};
|
|
719
758
|
if (defaultLlmsTxtSections[0]) {
|
|
@@ -944,6 +983,7 @@ export async function lookupContentPage(event, path) {
|
|
|
944
983
|
i18n: i18nConfig,
|
|
945
984
|
ftsTokenizer
|
|
946
985
|
};
|
|
986
|
+
addServerPlugin(resolve("./runtime/server/plugins/html-capture.prerender"));
|
|
947
987
|
addServerHandler({
|
|
948
988
|
middleware: true,
|
|
949
989
|
handler: resolve("./runtime/server/middleware/markdown.prerender")
|
|
@@ -1000,7 +1040,7 @@ export async function lookupContentPage(event, path) {
|
|
|
1000
1040
|
const siteConfig = useSiteConfig();
|
|
1001
1041
|
setupPrerenderHandler(config, buildDbPath, {
|
|
1002
1042
|
name: siteConfig.name,
|
|
1003
|
-
url: siteConfig.url,
|
|
1043
|
+
url: siteConfig.url ? withSiteUrl("/", { withBase: true }) : void 0,
|
|
1004
1044
|
description: siteConfig.description
|
|
1005
1045
|
}, mergedLlmsTxt, indexNow, { ftsTokenizer, i18n: i18nConfig });
|
|
1006
1046
|
}
|
|
@@ -1012,20 +1052,29 @@ export async function lookupContentPage(event, path) {
|
|
|
1012
1052
|
icon: "carbon:ai-label"
|
|
1013
1053
|
}, resolve, nuxt);
|
|
1014
1054
|
nuxt.options.nitro.routeRules = nuxt.options.nitro.routeRules || {};
|
|
1015
|
-
|
|
1016
|
-
|
|
1055
|
+
for (const route of ["/llms.txt", "/llms-full.txt"]) {
|
|
1056
|
+
nuxt.options.nitro.routeRules[route] = defu(
|
|
1057
|
+
nuxt.options.nitro.routeRules[route],
|
|
1058
|
+
{ headers: { "Content-Type": "text/plain; charset=utf-8" } }
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1017
1061
|
nuxt.hooks.hook("nitro:build:before", (nitro) => {
|
|
1018
1062
|
nitro.hooks.hook("compiled", async () => {
|
|
1019
1063
|
const headersPath = join(nitro.options.output.publicDir, "_headers");
|
|
1020
1064
|
logger.debug(`Checking for _headers file: ${headersPath}`);
|
|
1021
1065
|
const exists = await access(headersPath).then(() => true).catch(() => false);
|
|
1022
1066
|
if (exists) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1067
|
+
const headers = await readFile(headersPath, "utf8");
|
|
1068
|
+
const mergedHeaders = ensureStaticHeader(
|
|
1069
|
+
headers,
|
|
1070
|
+
"/*.md",
|
|
1071
|
+
"Content-Type",
|
|
1072
|
+
"text/markdown; charset=utf-8"
|
|
1073
|
+
);
|
|
1074
|
+
if (mergedHeaders !== headers) {
|
|
1075
|
+
await writeFile(headersPath, mergedHeaders);
|
|
1076
|
+
logger.debug("Merged .md charset header into _headers");
|
|
1077
|
+
}
|
|
1029
1078
|
}
|
|
1030
1079
|
});
|
|
1031
1080
|
});
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
2
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
3
|
+
import { withSiteTrailingSlash, withSiteUrl } from "#site-config/server/composables/utils";
|
|
3
4
|
import { normalizeLlmsTxtConfig } from "./llms-txt-format.js";
|
|
5
|
+
import { normalizePersistedRoute, toDeployedRoute, toLogicalRoute } from "./route-path.js";
|
|
4
6
|
import { queryPages } from "./server/db/queries.js";
|
|
5
7
|
import { logger } from "./server/logger.js";
|
|
6
8
|
import { localePath, resolveLocaleFromRoute } from "./server/utils/i18n.js";
|
|
7
9
|
import { fetchSitemapUrls } from "./server/utils/sitemap.js";
|
|
8
10
|
export { normalizeLlmsTxtConfig };
|
|
9
|
-
function formatAvailableLanguagesSection(i18n, pageCounts) {
|
|
11
|
+
function formatAvailableLanguagesSection(i18n, pageCounts, resolvePath) {
|
|
10
12
|
const lines = ["## Available Languages on Website", ""];
|
|
11
13
|
for (const locale of i18n.locales) {
|
|
12
14
|
const isDefault = locale.code === i18n.defaultLocale;
|
|
13
|
-
const prefix = localePath("/", locale.code, i18n);
|
|
15
|
+
const prefix = resolvePath(localePath("/", locale.code, i18n));
|
|
14
16
|
const count = pageCounts.get(locale.code) ?? 0;
|
|
15
17
|
const display = locale.nativeName ? `${locale.nativeName} (${locale.code})` : locale.name ? `${locale.name} (${locale.code})` : locale.code;
|
|
16
18
|
const suffix = isDefault ? "Content included below" : "Visit website for content";
|
|
@@ -102,10 +104,11 @@ function formatPagesWithGroups(pages) {
|
|
|
102
104
|
}
|
|
103
105
|
urlsInCurrentGroup++;
|
|
104
106
|
const descText = page.description ? `: ${page.description.substring(0, 160)}${page.description.length > 160 ? "..." : ""}` : "";
|
|
107
|
+
const href = page.href ?? page.pathname;
|
|
105
108
|
if (page.title && page.title !== page.pathname)
|
|
106
|
-
lines.push(`- [${page.title}](${
|
|
109
|
+
lines.push(`- [${page.title}](${href})${descText}`);
|
|
107
110
|
else
|
|
108
|
-
lines.push(`- ${
|
|
111
|
+
lines.push(`- ${href}${descText}`);
|
|
109
112
|
}
|
|
110
113
|
return lines;
|
|
111
114
|
}
|
|
@@ -116,26 +119,39 @@ export async function buildLlmsTxt(event) {
|
|
|
116
119
|
const siteConfig = getSiteConfig(event);
|
|
117
120
|
const llmsTxtConfig = aiReadyConfig.llmsTxt;
|
|
118
121
|
const i18n = aiReadyConfig.i18n;
|
|
122
|
+
const baseURL = runtimeConfig.app.baseURL;
|
|
123
|
+
const resolvePath = (path) => withSiteTrailingSlash(event, toDeployedRoute(path, baseURL));
|
|
124
|
+
const resolveUrl = (path) => withSiteUrl(event, toDeployedRoute(path, baseURL));
|
|
125
|
+
const canonicalSiteUrl = siteConfig.url ? resolveUrl("/") : void 0;
|
|
119
126
|
const parts = [];
|
|
120
|
-
parts.push(`# ${siteConfig.name ||
|
|
127
|
+
parts.push(`# ${siteConfig.name || canonicalSiteUrl}`);
|
|
121
128
|
if (siteConfig.description) {
|
|
122
129
|
parts.push(`
|
|
123
130
|
> ${siteConfig.description}`);
|
|
124
131
|
}
|
|
125
|
-
if (
|
|
132
|
+
if (canonicalSiteUrl) {
|
|
126
133
|
parts.push(`
|
|
127
|
-
Canonical Origin: ${
|
|
134
|
+
Canonical Origin: ${canonicalSiteUrl}`);
|
|
128
135
|
}
|
|
129
136
|
parts.push("");
|
|
130
|
-
const sections = llmsTxtConfig.sections
|
|
137
|
+
const sections = llmsTxtConfig.sections?.map((section) => ({
|
|
138
|
+
...section,
|
|
139
|
+
links: section.links ? [...section.links] : void 0
|
|
140
|
+
})) ?? [];
|
|
131
141
|
if (sections[0]?.links) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
const sitemapRoutes = sitemapConfig?.sitemaps ? Object.values(sitemapConfig.sitemaps).map((s) => s.sitemapName) : ["sitemap.xml"];
|
|
143
|
+
for (const name of sitemapRoutes) {
|
|
144
|
+
sections[0].links.push({
|
|
145
|
+
title: name,
|
|
146
|
+
href: resolveUrl(`/${name}`),
|
|
147
|
+
description: "XML sitemap for search engines and crawlers."
|
|
148
|
+
});
|
|
137
149
|
}
|
|
138
|
-
sections[0].links.push({
|
|
150
|
+
sections[0].links.push({
|
|
151
|
+
title: "robots.txt",
|
|
152
|
+
href: resolveUrl("/robots.txt"),
|
|
153
|
+
description: "Crawler rules and permissions."
|
|
154
|
+
});
|
|
139
155
|
}
|
|
140
156
|
const normalizedContent = normalizeLlmsTxtConfig({ ...llmsTxtConfig, sections });
|
|
141
157
|
if (normalizedContent) {
|
|
@@ -157,23 +173,29 @@ Canonical Origin: ${siteConfig.url}`);
|
|
|
157
173
|
);
|
|
158
174
|
}
|
|
159
175
|
const urls = await urlsPromise;
|
|
176
|
+
const sitemapPaths = urls.map((url) => toLogicalRoute(url.loc, baseURL));
|
|
177
|
+
const sitemapPathSet = new Set(sitemapPaths);
|
|
160
178
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
161
|
-
const errorSet = new Set(errorPages.map((p) => p.route));
|
|
179
|
+
const errorSet = new Set(errorPages.map((p) => normalizePersistedRoute(p.route, sitemapPathSet, baseURL)));
|
|
162
180
|
const prerendered = [];
|
|
163
181
|
for (const page of pages) {
|
|
164
|
-
|
|
165
|
-
|
|
182
|
+
const pathname = normalizePersistedRoute(page.route, sitemapPathSet, baseURL);
|
|
183
|
+
if (seenPaths.has(pathname))
|
|
184
|
+
continue;
|
|
185
|
+
seenPaths.add(pathname);
|
|
186
|
+
prerendered.push({ pathname, title: page.title, description: page.description, locale: page.locale });
|
|
166
187
|
}
|
|
167
188
|
const devModeHint = import.meta.dev && prerendered.length === 0 ? " (dev mode - run `nuxi generate` for page titles)" : "";
|
|
168
189
|
const other = [];
|
|
169
|
-
for (const
|
|
170
|
-
const pathname = url.loc.startsWith("/") ? url.loc : new URL(url.loc).pathname;
|
|
190
|
+
for (const pathname of sitemapPaths) {
|
|
171
191
|
if (!seenPaths.has(pathname) && !errorSet.has(pathname)) {
|
|
172
192
|
const locale = i18n ? resolveLocaleFromRoute(pathname, i18n).locale : void 0;
|
|
173
193
|
other.push({ pathname, locale });
|
|
174
194
|
seenPaths.add(pathname);
|
|
175
195
|
}
|
|
176
196
|
}
|
|
197
|
+
for (const page of [...prerendered, ...other])
|
|
198
|
+
page.href = resolvePath(page.pathname);
|
|
177
199
|
if (i18n) {
|
|
178
200
|
const pageCounts = /* @__PURE__ */ new Map();
|
|
179
201
|
for (const locale of i18n.locales) pageCounts.set(locale.code, 0);
|
|
@@ -181,7 +203,7 @@ Canonical Origin: ${siteConfig.url}`);
|
|
|
181
203
|
const code = p.locale || resolveLocaleFromRoute(p.pathname, i18n).locale;
|
|
182
204
|
pageCounts.set(code, (pageCounts.get(code) ?? 0) + 1);
|
|
183
205
|
}
|
|
184
|
-
parts.push(...formatAvailableLanguagesSection(i18n, pageCounts));
|
|
206
|
+
parts.push(...formatAvailableLanguagesSection(i18n, pageCounts, resolvePath));
|
|
185
207
|
parts.push("");
|
|
186
208
|
}
|
|
187
209
|
const isDefaultLocale = (item) => {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const RE_TRAILING_SLASHES = /\/+$/;
|
|
2
|
+
export function normalizePagePath(path) {
|
|
3
|
+
return path.replace(RE_TRAILING_SLASHES, "") || "/";
|
|
4
|
+
}
|
|
2
5
|
export function toMarkdownPath(path) {
|
|
3
|
-
|
|
6
|
+
const normalizedPath = normalizePagePath(path);
|
|
7
|
+
if (normalizedPath === "/")
|
|
4
8
|
return "/index.md";
|
|
5
|
-
return `${
|
|
9
|
+
return `${normalizedPath}.md`;
|
|
6
10
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Convert a sitemap URL (which includes app.baseURL) to a logical route. */
|
|
2
|
+
export declare function toLogicalRoute(pathOrUrl: string, baseURL: string): string;
|
|
3
|
+
/** Apply app.baseURL to a logical route exactly once. */
|
|
4
|
+
export declare function toDeployedRoute(route: string, baseURL: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Normalize rows written by older releases, which stored deployed paths.
|
|
7
|
+
* A sitemap match disambiguates those rows from real logical routes whose
|
|
8
|
+
* first segment happens to match app.baseURL.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizePersistedRoute(route: string, sitemapRoutes: ReadonlySet<string>, baseURL: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { joinURL, parseURL, withLeadingSlash, withoutBase } from "ufo";
|
|
2
|
+
import { normalizePagePath } from "./markdown-path.js";
|
|
3
|
+
export function toLogicalRoute(pathOrUrl, baseURL) {
|
|
4
|
+
const pathname = withLeadingSlash(parseURL(pathOrUrl).pathname);
|
|
5
|
+
return normalizePagePath(withoutBase(pathname, baseURL));
|
|
6
|
+
}
|
|
7
|
+
export function toDeployedRoute(route, baseURL) {
|
|
8
|
+
return joinURL(baseURL, withLeadingSlash(route));
|
|
9
|
+
}
|
|
10
|
+
export function normalizePersistedRoute(route, sitemapRoutes, baseURL) {
|
|
11
|
+
const normalizedRoute = normalizePagePath(withLeadingSlash(parseURL(route).pathname));
|
|
12
|
+
if (sitemapRoutes.has(normalizedRoute))
|
|
13
|
+
return normalizedRoute;
|
|
14
|
+
const logicalRoute = toLogicalRoute(normalizedRoute, baseURL);
|
|
15
|
+
return sitemapRoutes.has(logicalRoute) ? logicalRoute : normalizedRoute;
|
|
16
|
+
}
|
|
@@ -57,17 +57,17 @@ async function getPrerenderDb() {
|
|
|
57
57
|
const excludeErrors = sql.includes("is_error = 0");
|
|
58
58
|
const includeMarkdown = wantsMarkdown(sql);
|
|
59
59
|
if (isErrorQuery) {
|
|
60
|
-
return
|
|
61
|
-
route
|
|
62
|
-
title:
|
|
63
|
-
description:
|
|
64
|
-
...includeMarkdown ? { markdown:
|
|
65
|
-
headings:
|
|
66
|
-
keywords:
|
|
67
|
-
updated_at:
|
|
60
|
+
return [...errorRoutes].map((route) => ({
|
|
61
|
+
route,
|
|
62
|
+
title: "",
|
|
63
|
+
description: "",
|
|
64
|
+
...includeMarkdown ? { markdown: "" } : {},
|
|
65
|
+
headings: "[]",
|
|
66
|
+
keywords: "[]",
|
|
67
|
+
updated_at: "",
|
|
68
68
|
is_error: 1,
|
|
69
69
|
indexed: 0,
|
|
70
|
-
locale:
|
|
70
|
+
locale: ""
|
|
71
71
|
}));
|
|
72
72
|
}
|
|
73
73
|
const filtered = excludeErrors ? pages.filter((p) => !errorRoutes.has(p.route)) : pages;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getHeader, sendRedirect, setHeader } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
import { withSiteUrl } from "#site-config/server/composables/utils";
|
|
4
|
+
import { toDeployedRoute } from "../../route-path.js";
|
|
4
5
|
import { queryPages } from "../db/queries.js";
|
|
5
6
|
import { logger } from "../logger.js";
|
|
6
7
|
import { convertHtmlToMarkdown, extractLastUpdated, getMarkdownRenderInfo, toMarkdownPath } from "../utils.js";
|
|
@@ -23,7 +24,7 @@ function setMarkdownHeaders(event, path, config, resolveUrl) {
|
|
|
23
24
|
setHeader(event, "cache-control", cacheControl);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
function notFoundMarkdown(canonicalUrl, path, config) {
|
|
27
|
+
function notFoundMarkdown(canonicalUrl, path, config, resolveUrl) {
|
|
27
28
|
const body = [
|
|
28
29
|
`# Page not found`,
|
|
29
30
|
``,
|
|
@@ -31,14 +32,14 @@ function notFoundMarkdown(canonicalUrl, path, config) {
|
|
|
31
32
|
``,
|
|
32
33
|
`Try one of these resources:`,
|
|
33
34
|
``,
|
|
34
|
-
`- [Sitemap](/sitemap.xml)`,
|
|
35
|
-
`- [llms.txt](/llms.txt)`,
|
|
36
|
-
`- [llms-full.txt](/llms-full.txt)`,
|
|
35
|
+
`- [Sitemap](${resolveUrl("/sitemap.xml")})`,
|
|
36
|
+
`- [llms.txt](${resolveUrl("/llms.txt")})`,
|
|
37
|
+
`- [llms-full.txt](${resolveUrl("/llms-full.txt")})`,
|
|
37
38
|
``
|
|
38
39
|
].join("\n");
|
|
39
40
|
const i18n = config.i18n;
|
|
40
41
|
const locale = i18n ? resolveLocaleFromRoute(path, i18n).locale : void 0;
|
|
41
|
-
const alternates = i18n ? computeLocaleAlternates(path, i18n).map((a) => ({ hreflang: a.hreflang, href: a.path })) : void 0;
|
|
42
|
+
const alternates = i18n ? computeLocaleAlternates(path, i18n).map((a) => ({ hreflang: a.hreflang, href: resolveUrl(a.path) })) : void 0;
|
|
42
43
|
const frontmatter = buildFrontmatter({
|
|
43
44
|
title: "Page not found",
|
|
44
45
|
description: `No content is available at ${path}.`,
|
|
@@ -64,15 +65,18 @@ export default defineEventHandler(async (event) => {
|
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
const { path, isExplicit, negotiation } = renderInfo;
|
|
67
|
-
const
|
|
68
|
-
const
|
|
68
|
+
const runtimeConfig = useRuntimeConfig(event);
|
|
69
|
+
const config = runtimeConfig["nuxt-ai-ready"];
|
|
70
|
+
const baseURL = runtimeConfig.app.baseURL;
|
|
71
|
+
const resolvePath = (path2) => toDeployedRoute(path2, baseURL);
|
|
72
|
+
const resolveUrl = (path2) => withSiteUrl(event, resolvePath(path2));
|
|
69
73
|
const canonicalUrl = resolveUrl(path);
|
|
70
74
|
if (negotiation === "html") {
|
|
71
75
|
setNegotiationHeaders(event, path, config, resolveUrl);
|
|
72
76
|
return;
|
|
73
77
|
}
|
|
74
78
|
if (!isExplicit) {
|
|
75
|
-
return sendRedirect(event, toMarkdownPath(path), 307);
|
|
79
|
+
return sendRedirect(event, resolvePath(toMarkdownPath(path)), 307);
|
|
76
80
|
}
|
|
77
81
|
const contentPage = await tryGetContentMarkdown(event, path).catch((e) => {
|
|
78
82
|
logger.debug(`[markdown] Content lookup failed for ${path}`, e);
|
|
@@ -91,7 +95,7 @@ export default defineEventHandler(async (event) => {
|
|
|
91
95
|
${contentPage.markdown}`;
|
|
92
96
|
}
|
|
93
97
|
logger.debug(`[markdown] Fetching HTML for ${path}`);
|
|
94
|
-
const response = await event.fetch(path, {
|
|
98
|
+
const response = await event.fetch(resolvePath(path), {
|
|
95
99
|
headers: { [INTERNAL_HEADER]: "1" },
|
|
96
100
|
redirect: "manual"
|
|
97
101
|
}).catch((e) => {
|
|
@@ -100,7 +104,7 @@ ${contentPage.markdown}`;
|
|
|
100
104
|
});
|
|
101
105
|
if (!response) {
|
|
102
106
|
setMarkdownHeaders(event, path, config, resolveUrl);
|
|
103
|
-
return notFoundMarkdown(canonicalUrl, path, config);
|
|
107
|
+
return notFoundMarkdown(canonicalUrl, path, config, resolveUrl);
|
|
104
108
|
}
|
|
105
109
|
if (response.status >= 300 && response.status < 400) {
|
|
106
110
|
const location = response.headers.get("location");
|
|
@@ -115,12 +119,12 @@ ${contentPage.markdown}`;
|
|
|
115
119
|
}
|
|
116
120
|
if (!response.ok) {
|
|
117
121
|
setMarkdownHeaders(event, path, config, resolveUrl);
|
|
118
|
-
return notFoundMarkdown(canonicalUrl, path, config);
|
|
122
|
+
return notFoundMarkdown(canonicalUrl, path, config, resolveUrl);
|
|
119
123
|
}
|
|
120
124
|
const contentType = response.headers.get("content-type") || "";
|
|
121
125
|
if (!contentType.includes("text/html")) {
|
|
122
126
|
setMarkdownHeaders(event, path, config, resolveUrl);
|
|
123
|
-
return notFoundMarkdown(canonicalUrl, path, config);
|
|
127
|
+
return notFoundMarkdown(canonicalUrl, path, config, resolveUrl);
|
|
124
128
|
}
|
|
125
129
|
const html = await response.text();
|
|
126
130
|
logger.debug(`[markdown] Fetched HTML for ${path} (${html.length} bytes)`);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createError, defineEventHandler } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
import { withSiteUrl } from "#site-config/server/composables/utils";
|
|
4
|
+
import { toDeployedRoute } from "../../route-path.js";
|
|
4
5
|
import { logger } from "../logger.js";
|
|
5
6
|
import { convertHtmlToMarkdown, extractLastUpdated, getMarkdownRenderInfo } from "../utils.js";
|
|
6
7
|
import { tryGetContentMarkdown } from "../utils/content.js";
|
|
7
8
|
import { buildFrontmatter } from "../utils/frontmatter.js";
|
|
8
9
|
import { extractKeywords } from "../utils/keywords.js";
|
|
10
|
+
import { consumePrerenderedHtml } from "../utils/prerender-html.js";
|
|
9
11
|
function extractHeadingsFromMarkdown(markdown) {
|
|
10
12
|
const headings = [];
|
|
11
13
|
for (const m of markdown.matchAll(/^(#{1,6}) ([^\n]+)$/gm)) {
|
|
@@ -25,8 +27,10 @@ export default defineEventHandler(async (event) => {
|
|
|
25
27
|
if (!renderInfo || "notAcceptable" in renderInfo)
|
|
26
28
|
return;
|
|
27
29
|
const { path } = renderInfo;
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
+
const fullRuntimeConfig = useRuntimeConfig(event);
|
|
31
|
+
const runtimeConfig = fullRuntimeConfig["nuxt-ai-ready"];
|
|
32
|
+
const deployedPath = toDeployedRoute(path, fullRuntimeConfig.app.baseURL);
|
|
33
|
+
const canonicalUrl = withSiteUrl(event, deployedPath);
|
|
30
34
|
const contentPage = await tryGetContentMarkdown(event, path).catch(() => null);
|
|
31
35
|
if (contentPage) {
|
|
32
36
|
logger.debug(`[markdown.prerender] Using content source for ${path} (${contentPage.markdown.length} bytes)`);
|
|
@@ -50,26 +54,32 @@ ${contentPage.markdown}`;
|
|
|
50
54
|
updatedAt: lastUpdated2
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
let html = consumePrerenderedHtml(path);
|
|
58
|
+
if (html) {
|
|
59
|
+
logger.debug(`[markdown.prerender] Reusing prerendered HTML for ${path} (${html.length} bytes)`);
|
|
60
|
+
} else {
|
|
61
|
+
logger.debug(`[markdown.prerender] Fetching HTML for ${path}`);
|
|
62
|
+
const response = await event.fetch(deployedPath, { signal: AbortSignal.timeout(3e4) }).catch((err) => {
|
|
63
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
64
|
+
throw createError({
|
|
65
|
+
statusCode: 504,
|
|
66
|
+
statusMessage: "Gateway Timeout",
|
|
67
|
+
message: `Timed out fetching HTML for ${path}`
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
return createError({
|
|
74
|
+
statusCode: response.status,
|
|
75
|
+
statusMessage: response.statusText,
|
|
76
|
+
message: `Failed to fetch HTML for ${path}`
|
|
60
77
|
});
|
|
61
78
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return createError({
|
|
66
|
-
statusCode: response.status,
|
|
67
|
-
statusMessage: response.statusText,
|
|
68
|
-
message: `Failed to fetch HTML for ${path}`
|
|
69
|
-
});
|
|
79
|
+
html = await response.text();
|
|
80
|
+
logger.debug(`[markdown.prerender] Fetched HTML for ${path} (${html.length} bytes)`);
|
|
81
|
+
consumePrerenderedHtml(path);
|
|
70
82
|
}
|
|
71
|
-
const html = await response.text();
|
|
72
|
-
logger.debug(`[markdown.prerender] Fetched HTML for ${path} (${html.length} bytes)`);
|
|
73
83
|
if (html.includes("__NUXT_ERROR__") || html.includes("nuxt-error-page")) {
|
|
74
84
|
return createError({
|
|
75
85
|
statusCode: 404,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { storePrerenderedHtml } from "../utils/prerender-html.js";
|
|
2
|
+
export default function htmlCapturePlugin(nitroApp) {
|
|
3
|
+
if (!import.meta.prerender)
|
|
4
|
+
return;
|
|
5
|
+
nitroApp.hooks.hook("render:response", (response, { event }) => {
|
|
6
|
+
if (typeof response.body !== "string" || !String(response.headers?.["content-type"] || "").includes("text/html"))
|
|
7
|
+
return;
|
|
8
|
+
storePrerenderedHtml(event.path, response.body);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { eventHandler, sendIterable, setHeader, setResponseHeader } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
import { getSiteConfig } from "#site-config/server/composables";
|
|
4
|
+
import { withSiteUrl } from "#site-config/server/composables/utils";
|
|
5
|
+
import { toDeployedRoute } from "../../route-path.js";
|
|
4
6
|
import { countPages, streamPages } from "../db/queries.js";
|
|
5
7
|
import { buildLlmsFullTxtHeader, formatPageForLlmsFullTxt } from "../utils/llms-full.js";
|
|
6
8
|
export default eventHandler(async (event) => {
|
|
@@ -8,12 +10,15 @@ export default eventHandler(async (event) => {
|
|
|
8
10
|
setHeader(event, "Content-Type", "text/plain; charset=utf-8");
|
|
9
11
|
return "# llms-full.txt\n\nThis file is generated during prerender.";
|
|
10
12
|
}
|
|
11
|
-
const
|
|
13
|
+
const runtimeConfig = useRuntimeConfig(event);
|
|
14
|
+
const config = runtimeConfig["nuxt-ai-ready"];
|
|
12
15
|
const siteConfig = getSiteConfig(event);
|
|
16
|
+
const baseURL = runtimeConfig.app.baseURL;
|
|
17
|
+
const canonicalSiteUrl = siteConfig.url ? withSiteUrl(event, toDeployedRoute("/", baseURL)) : void 0;
|
|
13
18
|
const header = buildLlmsFullTxtHeader(
|
|
14
19
|
{
|
|
15
20
|
name: siteConfig.name,
|
|
16
|
-
url:
|
|
21
|
+
url: canonicalSiteUrl,
|
|
17
22
|
description: siteConfig.description
|
|
18
23
|
},
|
|
19
24
|
config.llmsTxt
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { joinURL } from "ufo";
|
|
1
2
|
export const INDEXNOW_HOSTS = ["api.indexnow.org", "www.bing.com"];
|
|
2
3
|
export function getIndexNowEndpoints() {
|
|
3
4
|
const testEndpoint = process.env.INDEXNOW_TEST_ENDPOINT;
|
|
@@ -8,12 +9,12 @@ export function getIndexNowEndpoints() {
|
|
|
8
9
|
}
|
|
9
10
|
export function buildIndexNowBody(routes, key, siteUrl) {
|
|
10
11
|
const urlList = routes.map(
|
|
11
|
-
(route) => route.startsWith("http") ? route :
|
|
12
|
+
(route) => route.startsWith("http") ? route : joinURL(siteUrl, route)
|
|
12
13
|
);
|
|
13
14
|
return {
|
|
14
15
|
host: new URL(siteUrl).host,
|
|
15
16
|
key,
|
|
16
|
-
keyLocation: `${
|
|
17
|
+
keyLocation: joinURL(siteUrl, `${key}.txt`),
|
|
17
18
|
urlList
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -15,9 +15,11 @@ function resolveHeaderUrl(path, resolveUrl) {
|
|
|
15
15
|
export function buildLinkHeader(path, variant, config, resolveUrl) {
|
|
16
16
|
const parts = [];
|
|
17
17
|
if (variant === "html") {
|
|
18
|
-
|
|
18
|
+
const href = resolveHeaderUrl(toMarkdownPath(path), resolveUrl);
|
|
19
|
+
parts.push(`<${encodePathForHeader(href)}>; rel="alternate"; type="text/markdown"`);
|
|
19
20
|
} else {
|
|
20
|
-
|
|
21
|
+
const href = resolveHeaderUrl(path, resolveUrl);
|
|
22
|
+
parts.push(`<${encodePathForHeader(href)}>; rel="alternate"; type="text/html"`);
|
|
21
23
|
}
|
|
22
24
|
if (config.i18n) {
|
|
23
25
|
const alternates = computeLocaleAlternates(path, config.i18n);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { joinURL } from "ufo";
|
|
1
2
|
import { normalizeLlmsTxtConfig } from "../../llms-txt-format.js";
|
|
2
|
-
const RE_TRAILING_SLASH = /\/$/;
|
|
3
3
|
const RE_FRONTMATTER = /^---\n[\s\S]*?\n---\n*/;
|
|
4
4
|
const RE_HEADING = /^(#{1,6}) ([^\n]+)$/gm;
|
|
5
5
|
export function formatPageForLlmsFullTxt(route, title, description, markdown, siteUrl) {
|
|
6
|
-
const canonicalUrl = siteUrl ?
|
|
6
|
+
const canonicalUrl = siteUrl ? joinURL(siteUrl, route) : route;
|
|
7
7
|
const heading = title && title !== route ? `### ${title}` : `### ${route}`;
|
|
8
8
|
const content = markdown.replace(RE_FRONTMATTER, "").replace(RE_HEADING, (_, hashes, text) => `h${hashes.length}. ${text}`);
|
|
9
9
|
const parts = [heading, ""];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const htmlByPath = /* @__PURE__ */ new Map();
|
|
2
|
+
function normalizeHtmlCachePath(path) {
|
|
3
|
+
return path.replace(/\/+$/, "") || "/";
|
|
4
|
+
}
|
|
5
|
+
export function storePrerenderedHtml(path, html) {
|
|
6
|
+
htmlByPath.set(normalizeHtmlCachePath(path), html);
|
|
7
|
+
}
|
|
8
|
+
export function consumePrerenderedHtml(path) {
|
|
9
|
+
const key = normalizeHtmlCachePath(path);
|
|
10
|
+
const html = htmlByPath.get(key);
|
|
11
|
+
htmlByPath.delete(key);
|
|
12
|
+
return html;
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.6",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@mdream/js": "^1.
|
|
61
|
-
"@nuxt/kit": "^4.
|
|
60
|
+
"@mdream/js": "^1.5.3",
|
|
61
|
+
"@nuxt/kit": "^4.5.0",
|
|
62
62
|
"citty": "^0.2.2",
|
|
63
63
|
"consola": "^3.4.2",
|
|
64
64
|
"defu": "^6.1.7",
|
|
65
65
|
"drizzle-orm": "^0.45.2",
|
|
66
|
-
"mdream": "^1.
|
|
66
|
+
"mdream": "^1.5.3",
|
|
67
67
|
"nuxt-site-config": "^4.1.1",
|
|
68
|
-
"nuxtseo-shared": "^5.3.
|
|
68
|
+
"nuxtseo-shared": "^5.3.3",
|
|
69
69
|
"pathe": "^2.0.3",
|
|
70
70
|
"pkg-types": "^2.3.1",
|
|
71
71
|
"site-config-stack": "^4.1.1",
|
|
@@ -74,14 +74,14 @@
|
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@antfu/eslint-config": "^9.1.0",
|
|
77
|
-
"@arethetypeswrong/cli": "^0.18.
|
|
77
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
78
78
|
"@libsql/client": "^0.17.4",
|
|
79
79
|
"@nuxt/content": "^3.15.0",
|
|
80
80
|
"@nuxt/module-builder": "^1.0.2",
|
|
81
81
|
"@nuxt/test-utils": "^4.0.3",
|
|
82
82
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
|
83
|
-
"@nuxtjs/mcp-toolkit": "^0.
|
|
84
|
-
"@nuxtjs/robots": "^6.1.
|
|
83
|
+
"@nuxtjs/mcp-toolkit": "^0.18.0",
|
|
84
|
+
"@nuxtjs/robots": "^6.1.3",
|
|
85
85
|
"@nuxtjs/sitemap": "^8.2.2",
|
|
86
86
|
"@types/better-sqlite3": "^7.6.13",
|
|
87
87
|
"@vitest/coverage-v8": "^4.1.10",
|
|
@@ -89,24 +89,24 @@
|
|
|
89
89
|
"@vueuse/nuxt": "^14.3.0",
|
|
90
90
|
"better-sqlite3": "^12.11.1",
|
|
91
91
|
"bumpp": "^11.1.0",
|
|
92
|
-
"eslint": "^10.
|
|
92
|
+
"eslint": "^10.7.0",
|
|
93
93
|
"eslint-plugin-harlanzw": "^0.17.0",
|
|
94
|
-
"execa": "^
|
|
95
|
-
"happy-dom": "^20.
|
|
94
|
+
"execa": "^10.0.0",
|
|
95
|
+
"happy-dom": "^20.11.0",
|
|
96
96
|
"nitropack": "^2.13.4",
|
|
97
|
-
"nuxt": "^4.
|
|
97
|
+
"nuxt": "^4.5.0",
|
|
98
98
|
"nuxt-site-config": "^4.1.1",
|
|
99
|
-
"nuxtseo-layer-devtools": "^5.3.
|
|
99
|
+
"nuxtseo-layer-devtools": "^5.3.3",
|
|
100
100
|
"playwright": "^1.61.1",
|
|
101
101
|
"playwright-core": "^1.61.1",
|
|
102
102
|
"tinyglobby": "^0.2.17",
|
|
103
|
-
"typescript": "
|
|
103
|
+
"typescript": "6.0.3",
|
|
104
104
|
"unbuild": "^3.6.1",
|
|
105
105
|
"vitest": "^4.1.10",
|
|
106
|
-
"vue": "^3.5.
|
|
107
|
-
"vue-router": "^5.
|
|
108
|
-
"vue-tsc": "^3.3.
|
|
109
|
-
"wrangler": "^4.
|
|
106
|
+
"vue": "^3.5.40",
|
|
107
|
+
"vue-router": "^5.2.0",
|
|
108
|
+
"vue-tsc": "^3.3.7",
|
|
109
|
+
"wrangler": "^4.112.0",
|
|
110
110
|
"zod": "^4.4.3"
|
|
111
111
|
},
|
|
112
112
|
"scripts": {
|