nuxt-ai-ready 1.5.3 → 1.5.5
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 +67 -16
- package/dist/runtime/markdown-path.d.ts +1 -0
- package/dist/runtime/markdown-path.js +6 -2
- package/dist/runtime/server/db/drizzle/providers/dbPath.js +5 -5
- package/dist/runtime/server/middleware/markdown.prerender.js +24 -17
- 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/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';
|
|
@@ -10,7 +10,7 @@ import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
|
10
10
|
import { parseSitemapXml } from '@nuxtjs/sitemap/utils';
|
|
11
11
|
import { colorize } from 'consola/utils';
|
|
12
12
|
import { withBase } from 'ufo';
|
|
13
|
-
import { toMarkdownPath } from '../dist/runtime/markdown-path.js';
|
|
13
|
+
import { normalizePagePath, toMarkdownPath } from '../dist/runtime/markdown-path.js';
|
|
14
14
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../dist/runtime/server/db/shared.js';
|
|
15
15
|
import { comparePageHashes, submitToIndexNowShared } from '../dist/runtime/server/utils/indexnow-shared.js';
|
|
16
16
|
import { buildLlmsFullTxtHeader, formatPageForLlmsFullTxt } from '../dist/runtime/server/utils/llms-full.js';
|
|
@@ -125,14 +125,18 @@ function resolveRouteLocale(route, i18n) {
|
|
|
125
125
|
return matched ? matched.code : i18n.defaultLocale;
|
|
126
126
|
}
|
|
127
127
|
async function processMarkdownRoute(state, nuxt, route, parsed, lastmod, options) {
|
|
128
|
-
|
|
128
|
+
route = normalizePagePath(route);
|
|
129
|
+
const { title, description, headings, keywords, updatedAt: metaUpdatedAt } = parsed;
|
|
129
130
|
let updatedAt = (lastmod instanceof Date ? lastmod.toISOString() : lastmod) || (/* @__PURE__ */ new Date()).toISOString();
|
|
130
131
|
if (metaUpdatedAt) {
|
|
131
132
|
const parsedDate = new Date(metaUpdatedAt);
|
|
132
133
|
if (!Number.isNaN(parsedDate.getTime()))
|
|
133
134
|
updatedAt = parsedDate.toISOString();
|
|
134
135
|
}
|
|
135
|
-
|
|
136
|
+
const hookContext = { ...parsed, route };
|
|
137
|
+
await nuxt.hooks.callHook("ai-ready:page:markdown", hookContext);
|
|
138
|
+
const { markdown } = hookContext;
|
|
139
|
+
parsed.markdown = markdown;
|
|
136
140
|
if (state.db) {
|
|
137
141
|
const contentHash = await computeContentHash(markdown);
|
|
138
142
|
await insertPage(state.db, {
|
|
@@ -147,7 +151,7 @@ async function processMarkdownRoute(state, nuxt, route, parsed, lastmod, options
|
|
|
147
151
|
locale: resolveRouteLocale(route, state.i18n)
|
|
148
152
|
});
|
|
149
153
|
}
|
|
150
|
-
if (state.llmsFullTxtPath && !options?.skipLlmsFullTxt) {
|
|
154
|
+
if (state.llmsFullTxtPath && !options?.skipLlmsFullTxt && markdown.trim()) {
|
|
151
155
|
const pageContent = formatPageForLlmsFullTxt(route, title, description, markdown, state.siteInfo?.url);
|
|
152
156
|
logger.debug(`Appending to llms-full.txt: ${route} (${(pageContent.length / 1024).toFixed(1)}kb)`);
|
|
153
157
|
await appendFile(state.llmsFullTxtPath, pageContent, "utf-8");
|
|
@@ -157,7 +161,7 @@ async function processMarkdownRoute(state, nuxt, route, parsed, lastmod, options
|
|
|
157
161
|
async function processSitemapEntry(state, nuxt, nitro, entry) {
|
|
158
162
|
const loc = typeof entry === "string" ? entry : entry.loc;
|
|
159
163
|
const lastmod = typeof entry === "string" ? void 0 : entry.lastmod;
|
|
160
|
-
const route = loc.startsWith("http") ? new URL(loc).pathname : loc;
|
|
164
|
+
const route = normalizePagePath(loc.startsWith("http") ? new URL(loc).pathname : loc);
|
|
161
165
|
if (route.split("/").some((segment) => segment.startsWith("_"))) {
|
|
162
166
|
return { crawled: false, skipped: true };
|
|
163
167
|
}
|
|
@@ -262,7 +266,10 @@ async function prerenderRoute(nitro, route) {
|
|
|
262
266
|
await writeFile(filePath, data, "utf8");
|
|
263
267
|
const _route = {
|
|
264
268
|
route,
|
|
265
|
-
fileName
|
|
269
|
+
// fileName is relative to the output public dir (nitro core convention);
|
|
270
|
+
// presets key overrides/route exclusions off it, so an absolute path here
|
|
271
|
+
// breaks e.g. the Vercel config.json overrides map.
|
|
272
|
+
fileName: route,
|
|
266
273
|
generateTimeMS: Date.now() - start
|
|
267
274
|
};
|
|
268
275
|
nitro._prerenderedRoutes.push(_route);
|
|
@@ -392,6 +399,7 @@ function setupPrerenderHandler(options, dbPath, siteInfo, llmsTxtConfig, indexNo
|
|
|
392
399
|
}
|
|
393
400
|
const llmsStats = await prerenderRoute(nitro, "/llms.txt");
|
|
394
401
|
const llmsFullStats = await stat(state.llmsFullTxtPath);
|
|
402
|
+
nitro._prerenderedRoutes.push({ route: "/llms-full.txt", fileName: "/llms-full.txt" });
|
|
395
403
|
const kb = (b) => (b / 1024).toFixed(1);
|
|
396
404
|
const totalKb = kb(llmsStats.size + llmsFullStats.size);
|
|
397
405
|
const dim = (s) => colorize("dim", s);
|
|
@@ -562,6 +570,37 @@ async function detectI18n(opts = {}) {
|
|
|
562
570
|
return toRuntimeI18nConfig(auto);
|
|
563
571
|
}
|
|
564
572
|
|
|
573
|
+
function escapeRegExp(value) {
|
|
574
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
575
|
+
}
|
|
576
|
+
function ensureStaticHeader(contents, route, name, value) {
|
|
577
|
+
const eol = contents.includes("\r\n") ? "\r\n" : "\n";
|
|
578
|
+
const routePattern = new RegExp(`^${escapeRegExp(route)}[\\t ]*\\r?$`, "gm");
|
|
579
|
+
const routeMatches = [...contents.matchAll(routePattern)];
|
|
580
|
+
const routeMatch = routeMatches.at(-1);
|
|
581
|
+
if (!routeMatch || routeMatch.index === void 0) {
|
|
582
|
+
const separator = contents.length === 0 ? "" : contents.endsWith(`${eol}${eol}`) ? "" : contents.endsWith(eol) ? eol : `${eol}${eol}`;
|
|
583
|
+
return `${contents}${separator}${route}${eol} ${name}: ${value}${eol}`;
|
|
584
|
+
}
|
|
585
|
+
const routeLineEnd = routeMatch.index + routeMatch[0].length;
|
|
586
|
+
const blockStart = contents[routeLineEnd] === "\n" ? routeLineEnd + 1 : routeLineEnd;
|
|
587
|
+
const nextBlockPattern = /^(?![\t ]|\r?$).+/gm;
|
|
588
|
+
nextBlockPattern.lastIndex = blockStart;
|
|
589
|
+
const nextBlock = nextBlockPattern.exec(contents);
|
|
590
|
+
const blockEnd = nextBlock?.index ?? contents.length;
|
|
591
|
+
const block = contents.slice(blockStart, blockEnd);
|
|
592
|
+
const escapedName = escapeRegExp(name);
|
|
593
|
+
const existingHeaderPattern = new RegExp(
|
|
594
|
+
`^[\\t ]+(?:${escapedName}[\\t ]*:|![\\t ]*${escapedName}[\\t ]*\\r?$)`,
|
|
595
|
+
"im"
|
|
596
|
+
);
|
|
597
|
+
if (existingHeaderPattern.test(block)) {
|
|
598
|
+
return contents;
|
|
599
|
+
}
|
|
600
|
+
const prefix = blockStart === routeLineEnd ? eol : "";
|
|
601
|
+
return `${contents.slice(0, blockStart)}${prefix} ${name}: ${value}${eol}${contents.slice(blockStart)}`;
|
|
602
|
+
}
|
|
603
|
+
|
|
565
604
|
const module$1 = defineNuxtModule({
|
|
566
605
|
meta: {
|
|
567
606
|
name: "nuxt-ai-ready",
|
|
@@ -678,6 +717,8 @@ const module$1 = defineNuxtModule({
|
|
|
678
717
|
robotsOpts.groups = groups;
|
|
679
718
|
const group = {
|
|
680
719
|
userAgent: "*",
|
|
720
|
+
// Preserve nuxt-robots' default wildcard rule so the injected group remains valid.
|
|
721
|
+
disallow: [""],
|
|
681
722
|
contentSignal: [`ai-train=${config.contentSignal.aiTrain ? "yes" : "no"}`, `search=${config.contentSignal.search ? "yes" : "no"}`, `ai-input=${config.contentSignal.aiInput ? "yes" : "no"}`]
|
|
682
723
|
};
|
|
683
724
|
if (config.contentSignal.contentUsage !== false)
|
|
@@ -940,6 +981,7 @@ export async function lookupContentPage(event, path) {
|
|
|
940
981
|
i18n: i18nConfig,
|
|
941
982
|
ftsTokenizer
|
|
942
983
|
};
|
|
984
|
+
addServerPlugin(resolve("./runtime/server/plugins/html-capture.prerender"));
|
|
943
985
|
addServerHandler({
|
|
944
986
|
middleware: true,
|
|
945
987
|
handler: resolve("./runtime/server/middleware/markdown.prerender")
|
|
@@ -1008,20 +1050,29 @@ export async function lookupContentPage(event, path) {
|
|
|
1008
1050
|
icon: "carbon:ai-label"
|
|
1009
1051
|
}, resolve, nuxt);
|
|
1010
1052
|
nuxt.options.nitro.routeRules = nuxt.options.nitro.routeRules || {};
|
|
1011
|
-
|
|
1012
|
-
|
|
1053
|
+
for (const route of ["/llms.txt", "/llms-full.txt"]) {
|
|
1054
|
+
nuxt.options.nitro.routeRules[route] = defu(
|
|
1055
|
+
nuxt.options.nitro.routeRules[route],
|
|
1056
|
+
{ headers: { "Content-Type": "text/plain; charset=utf-8" } }
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1013
1059
|
nuxt.hooks.hook("nitro:build:before", (nitro) => {
|
|
1014
1060
|
nitro.hooks.hook("compiled", async () => {
|
|
1015
1061
|
const headersPath = join(nitro.options.output.publicDir, "_headers");
|
|
1016
1062
|
logger.debug(`Checking for _headers file: ${headersPath}`);
|
|
1017
1063
|
const exists = await access(headersPath).then(() => true).catch(() => false);
|
|
1018
1064
|
if (exists) {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1065
|
+
const headers = await readFile(headersPath, "utf8");
|
|
1066
|
+
const mergedHeaders = ensureStaticHeader(
|
|
1067
|
+
headers,
|
|
1068
|
+
"/*.md",
|
|
1069
|
+
"Content-Type",
|
|
1070
|
+
"text/markdown; charset=utf-8"
|
|
1071
|
+
);
|
|
1072
|
+
if (mergedHeaders !== headers) {
|
|
1073
|
+
await writeFile(headersPath, mergedHeaders);
|
|
1074
|
+
logger.debug("Merged .md charset header into _headers");
|
|
1075
|
+
}
|
|
1025
1076
|
}
|
|
1026
1077
|
});
|
|
1027
1078
|
});
|
|
@@ -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
|
}
|
|
@@ -15,8 +15,8 @@ async function ensureWritableDir(dir) {
|
|
|
15
15
|
await rm(probe, { force: true }).catch(() => null);
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
function
|
|
19
|
-
return err.code === "EROFS" || err.code === "EACCES";
|
|
18
|
+
function isRecoverable(err) {
|
|
19
|
+
return err.code === "EROFS" || err.code === "EACCES" || err.code === "ENOENT" || err.code === "ENOTDIR";
|
|
20
20
|
}
|
|
21
21
|
export async function resolveWritableDbPath(dbPath) {
|
|
22
22
|
const cached = resolved.get(dbPath);
|
|
@@ -28,19 +28,19 @@ export async function resolveWritableDbPath(dbPath) {
|
|
|
28
28
|
resolved.set(dbPath, dbPath);
|
|
29
29
|
return dbPath;
|
|
30
30
|
}
|
|
31
|
-
if (!
|
|
31
|
+
if (!isRecoverable(err))
|
|
32
32
|
throw err;
|
|
33
33
|
const key = createHash("sha256").update(resolve(dbPath)).digest("hex").slice(0, 16);
|
|
34
34
|
const fallbackDir = join(tmpdir(), `ai-ready-${key}`);
|
|
35
35
|
const fallbackErr = await ensureWritableDir(fallbackDir);
|
|
36
36
|
if (fallbackErr) {
|
|
37
37
|
throw new Error(
|
|
38
|
-
`[ai-ready] Database directory '${dir}' is
|
|
38
|
+
`[ai-ready] Database directory '${dir}' is not writable (${err.code}) and the temp dir fallback ('${fallbackDir}') also failed: ${fallbackErr.message}. Set database.filename to a writable path, or use a serverless driver: database.type 'd1' (Cloudflare), 'neon' (Vercel/Postgres), or 'libsql' (Turso).`
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
const fallback = join(fallbackDir, "pages.db");
|
|
42
42
|
logger.warn(
|
|
43
|
-
`[ai-ready] Database directory '${dir}' is
|
|
43
|
+
`[ai-ready] Database directory '${dir}' is not writable (${err.code}); falling back to '${fallback}'. This database is ephemeral and reseeded on cold start. Set database.filename to a writable persistent path (or a volume) to silence this warning.`
|
|
44
44
|
);
|
|
45
45
|
resolved.set(dbPath, fallback);
|
|
46
46
|
return fallback;
|
|
@@ -6,6 +6,7 @@ import { convertHtmlToMarkdown, extractLastUpdated, getMarkdownRenderInfo } from
|
|
|
6
6
|
import { tryGetContentMarkdown } from "../utils/content.js";
|
|
7
7
|
import { buildFrontmatter } from "../utils/frontmatter.js";
|
|
8
8
|
import { extractKeywords } from "../utils/keywords.js";
|
|
9
|
+
import { consumePrerenderedHtml } from "../utils/prerender-html.js";
|
|
9
10
|
function extractHeadingsFromMarkdown(markdown) {
|
|
10
11
|
const headings = [];
|
|
11
12
|
for (const m of markdown.matchAll(/^(#{1,6}) ([^\n]+)$/gm)) {
|
|
@@ -50,26 +51,32 @@ ${contentPage.markdown}`;
|
|
|
50
51
|
updatedAt: lastUpdated2
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
let html = consumePrerenderedHtml(path);
|
|
55
|
+
if (html) {
|
|
56
|
+
logger.debug(`[markdown.prerender] Reusing prerendered HTML for ${path} (${html.length} bytes)`);
|
|
57
|
+
} else {
|
|
58
|
+
logger.debug(`[markdown.prerender] Fetching HTML for ${path}`);
|
|
59
|
+
const response = await event.fetch(path, { signal: AbortSignal.timeout(3e4) }).catch((err) => {
|
|
60
|
+
if (err?.name === "TimeoutError" || err?.name === "AbortError") {
|
|
61
|
+
throw createError({
|
|
62
|
+
statusCode: 504,
|
|
63
|
+
statusMessage: "Gateway Timeout",
|
|
64
|
+
message: `Timed out fetching HTML for ${path}`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
return createError({
|
|
71
|
+
statusCode: response.status,
|
|
72
|
+
statusMessage: response.statusText,
|
|
73
|
+
message: `Failed to fetch HTML for ${path}`
|
|
60
74
|
});
|
|
61
75
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return createError({
|
|
66
|
-
statusCode: response.status,
|
|
67
|
-
statusMessage: response.statusText,
|
|
68
|
-
message: `Failed to fetch HTML for ${path}`
|
|
69
|
-
});
|
|
76
|
+
html = await response.text();
|
|
77
|
+
logger.debug(`[markdown.prerender] Fetched HTML for ${path} (${html.length} bytes)`);
|
|
78
|
+
consumePrerenderedHtml(path);
|
|
70
79
|
}
|
|
71
|
-
const html = await response.text();
|
|
72
|
-
logger.debug(`[markdown.prerender] Fetched HTML for ${path} (${html.length} bytes)`);
|
|
73
80
|
if (html.includes("__NUXT_ERROR__") || html.includes("nuxt-error-page")) {
|
|
74
81
|
return createError({
|
|
75
82
|
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
|
+
}
|
|
@@ -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.5",
|
|
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.2",
|
|
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.2",
|
|
67
67
|
"nuxt-site-config": "^4.1.1",
|
|
68
|
-
"nuxtseo-shared": "^5.3.
|
|
68
|
+
"nuxtseo-shared": "^5.3.2",
|
|
69
69
|
"pathe": "^2.0.3",
|
|
70
70
|
"pkg-types": "^2.3.1",
|
|
71
71
|
"site-config-stack": "^4.1.1",
|
|
@@ -74,39 +74,39 @@
|
|
|
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
|
-
"@nuxt/content": "^3.
|
|
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.
|
|
83
|
+
"@nuxtjs/mcp-toolkit": "^0.18.0",
|
|
84
84
|
"@nuxtjs/robots": "^6.1.2",
|
|
85
85
|
"@nuxtjs/sitemap": "^8.2.2",
|
|
86
86
|
"@types/better-sqlite3": "^7.6.13",
|
|
87
|
-
"@vitest/coverage-v8": "^4.1.
|
|
87
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
88
88
|
"@vue/test-utils": "^2.4.11",
|
|
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": "^
|
|
94
|
+
"execa": "^10.0.0",
|
|
95
95
|
"happy-dom": "^20.10.6",
|
|
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.2",
|
|
100
100
|
"playwright": "^1.61.1",
|
|
101
101
|
"playwright-core": "^1.61.1",
|
|
102
102
|
"tinyglobby": "^0.2.17",
|
|
103
103
|
"typescript": "^6.0.3",
|
|
104
104
|
"unbuild": "^3.6.1",
|
|
105
|
-
"vitest": "^4.1.
|
|
106
|
-
"vue": "^3.5.
|
|
107
|
-
"vue-router": "^5.
|
|
108
|
-
"vue-tsc": "^3.3.
|
|
109
|
-
"wrangler": "^4.
|
|
105
|
+
"vitest": "^4.1.10",
|
|
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": {
|