nuxt-ai-ready 1.7.10 → 1.7.12
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 +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/db/drizzle/queries.js +10 -3
- package/dist/runtime/server/db/queries.js +6 -5
- package/dist/runtime/server/db/shared.d.ts +20 -0
- package/dist/runtime/server/db/shared.js +14 -3
- package/dist/runtime/server/middleware/markdown.js +21 -4
- package/dist/runtime/server/plugins/link-header.d.ts +4 -0
- package/dist/runtime/server/plugins/link-header.js +40 -0
- package/dist/shared/{nuxt-ai-ready.jeGqwuW1.mjs → nuxt-ai-ready.Cp2hOQih.mjs} +6 -0
- package/package.json +10 -10
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { realpath, readFile } from 'node:fs/promises';
|
|
3
3
|
import { 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.Cp2hOQih.mjs';
|
|
6
6
|
import 'node:module';
|
|
7
7
|
import '@nuxt/kit';
|
|
8
8
|
import 'defu';
|
|
@@ -5,7 +5,7 @@ import { colorize } from 'consola/utils';
|
|
|
5
5
|
import { resolveLocaleFromRoute } from 'nuxtseo-shared/i18n-runtime';
|
|
6
6
|
import { collectSitemap } from 'sitemapd/parse';
|
|
7
7
|
import { withLeadingSlash, withBase, joinURL } from 'ufo';
|
|
8
|
-
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.
|
|
8
|
+
import { l as logger, M as MARKDOWN_LINK_AVAILABILITY_FILE } from '../shared/nuxt-ai-ready.Cp2hOQih.mjs';
|
|
9
9
|
import { normalizePagePath, toMarkdownPath } from '../../dist/runtime/markdown-path.js';
|
|
10
10
|
import { toLogicalRoute, toDeployedRoute } from '../../dist/runtime/route-path.js';
|
|
11
11
|
import { initSchema, computeContentHash, insertPage, queryAllPages, exportDbDump } from '../../dist/runtime/server/db/shared.js';
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import 'defu';
|
|
|
7
7
|
import 'nuxt-site-config/kit';
|
|
8
8
|
import 'nuxtseo-shared/kit';
|
|
9
9
|
import 'pkg-types';
|
|
10
|
-
export { m as default } from './shared/nuxt-ai-ready.
|
|
10
|
+
export { m as default } from './shared/nuxt-ai-ready.Cp2hOQih.mjs';
|
|
11
11
|
import '../dist/runtime/server/utils/discovery-response.js';
|
|
12
12
|
import 'node:url';
|
|
13
13
|
import 'pathe';
|
|
@@ -11,8 +11,13 @@ function resolveFtsTokenizer(event) {
|
|
|
11
11
|
}
|
|
12
12
|
const RE_LEADING_SLASH = /^\//;
|
|
13
13
|
const RE_SLASH = /\//g;
|
|
14
|
+
function normalizeRoute(route) {
|
|
15
|
+
if (!route || route === "/")
|
|
16
|
+
return "/";
|
|
17
|
+
return route.startsWith("/") ? route : `/${route}`;
|
|
18
|
+
}
|
|
14
19
|
function normalizeRouteKey(route) {
|
|
15
|
-
return route.replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
|
|
20
|
+
return normalizeRoute(route).replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
|
|
16
21
|
}
|
|
17
22
|
function rowToPage(row) {
|
|
18
23
|
return {
|
|
@@ -42,7 +47,7 @@ export async function upsertPage(event, page) {
|
|
|
42
47
|
const client = await useDrizzle(event);
|
|
43
48
|
const now = Date.now();
|
|
44
49
|
const values = {
|
|
45
|
-
route: page.route,
|
|
50
|
+
route: normalizeRoute(page.route),
|
|
46
51
|
routeKey: normalizeRouteKey(page.route),
|
|
47
52
|
title: page.title,
|
|
48
53
|
description: page.description,
|
|
@@ -700,8 +705,10 @@ export async function seedRoutes(event, routes) {
|
|
|
700
705
|
if (routes.length === 0)
|
|
701
706
|
return 0;
|
|
702
707
|
const byRoute = /* @__PURE__ */ new Map();
|
|
703
|
-
for (const
|
|
708
|
+
for (const raw of routes) {
|
|
709
|
+
const route = normalizeRoute(raw);
|
|
704
710
|
byRoute.set(route, normalizeRouteKey(route));
|
|
711
|
+
}
|
|
705
712
|
const ROWS_PER_INSERT = 20;
|
|
706
713
|
const db = await useRawDb(event);
|
|
707
714
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4,7 +4,7 @@ import { resolveLocaleFromRoute } from "../utils/i18n.js";
|
|
|
4
4
|
import { parseSitemapCrawlState, serializeSitemapCrawlState } from "../utils/sitemap-crawl-state.js";
|
|
5
5
|
import { initSchema } from "./drizzle/queries.js";
|
|
6
6
|
import { useRawDb } from "./drizzle/raw.js";
|
|
7
|
-
import { normalizeRouteKey } from "./shared.js";
|
|
7
|
+
import { normalizeRoute, normalizeRouteKey } from "./shared.js";
|
|
8
8
|
function deriveLocale(event, route, explicit) {
|
|
9
9
|
if (explicit !== void 0)
|
|
10
10
|
return explicit;
|
|
@@ -261,12 +261,13 @@ export async function upsertPage(event, page) {
|
|
|
261
261
|
const db = await getDb(event);
|
|
262
262
|
if (!db)
|
|
263
263
|
return;
|
|
264
|
-
const
|
|
264
|
+
const route = normalizeRoute(page.route);
|
|
265
|
+
const routeKey = normalizeRouteKey(route);
|
|
265
266
|
const keywordsJson = JSON.stringify(page.keywords);
|
|
266
267
|
const indexedAt = Date.now();
|
|
267
268
|
const source = page.source || "runtime";
|
|
268
269
|
const lastSeenAt = source === "runtime" ? indexedAt : null;
|
|
269
|
-
const locale = deriveLocale(event,
|
|
270
|
+
const locale = deriveLocale(event, route, page.locale);
|
|
270
271
|
await db.exec(`
|
|
271
272
|
INSERT INTO ai_ready_pages (route, route_key, title, description, markdown, headings, keywords, content_hash, updated_at, indexed_at, is_error, indexed, source, last_seen_at, locale)
|
|
272
273
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)
|
|
@@ -284,7 +285,7 @@ export async function upsertPage(event, page) {
|
|
|
284
285
|
source = excluded.source,
|
|
285
286
|
last_seen_at = excluded.last_seen_at,
|
|
286
287
|
locale = excluded.locale
|
|
287
|
-
`, [
|
|
288
|
+
`, [route, routeKey, page.title, page.description, page.markdown, page.headings, keywordsJson, page.contentHash || null, page.updatedAt, indexedAt, page.isError ? 1 : 0, source, lastSeenAt, locale]);
|
|
288
289
|
}
|
|
289
290
|
export async function isPageFresh(event, route, ttlSeconds) {
|
|
290
291
|
if (ttlSeconds <= 0)
|
|
@@ -323,7 +324,7 @@ export async function seedRoutes(event, routes) {
|
|
|
323
324
|
const nowMs = Date.now();
|
|
324
325
|
const byRoute = /* @__PURE__ */ new Map();
|
|
325
326
|
for (const entry of routes) {
|
|
326
|
-
const route = typeof entry === "string" ? entry : entry.route;
|
|
327
|
+
const route = normalizeRoute(typeof entry === "string" ? entry : entry.route);
|
|
327
328
|
const explicitLocale = typeof entry === "string" ? void 0 : entry.locale;
|
|
328
329
|
byRoute.set(route, {
|
|
329
330
|
route,
|
|
@@ -27,9 +27,29 @@ export interface InitSchemaOptions {
|
|
|
27
27
|
* a CJK locale on a later deploy).
|
|
28
28
|
*/
|
|
29
29
|
export declare function initSchema(db: DatabaseAdapter, options?: InitSchemaOptions): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Canonical storage form of a route.
|
|
32
|
+
*
|
|
33
|
+
* `ai_ready_pages` puts a UNIQUE index on `route` and another on `route_key`,
|
|
34
|
+
* while every upsert targets `ON CONFLICT(route)`. Two spellings of one page
|
|
35
|
+
* therefore have to agree on `route`, or the conflict target misses, the insert
|
|
36
|
+
* reaches the `route_key` index instead, and the whole statement fails with
|
|
37
|
+
* `UNIQUE constraint failed: ai_ready_pages.route_key`.
|
|
38
|
+
*
|
|
39
|
+
* `''` and `'/'` are the pair that occurs in practice, since both key to
|
|
40
|
+
* `index`: one spelling takes the row, the other retries and fails on every
|
|
41
|
+
* subsequent write. A bare `'about'` against `'/about'` is the same defect.
|
|
42
|
+
*
|
|
43
|
+
* Normalising here rather than widening the conflict target keeps one page to
|
|
44
|
+
* one row. Widening it would let both spellings persist and race for the key.
|
|
45
|
+
*/
|
|
46
|
+
export declare function normalizeRoute(route: string): string;
|
|
30
47
|
/**
|
|
31
48
|
* Normalize route to storage key format
|
|
32
49
|
* e.g., '/about/team' -> 'about:team', '/' -> 'index'
|
|
50
|
+
*
|
|
51
|
+
* Derived from the canonical route so the two can never disagree: equal keys
|
|
52
|
+
* imply equal routes, which is what the pair of unique indexes requires.
|
|
33
53
|
*/
|
|
34
54
|
export declare function normalizeRouteKey(route: string): string;
|
|
35
55
|
/**
|
|
@@ -52,8 +52,13 @@ async function getStoredTokenizer(db) {
|
|
|
52
52
|
}
|
|
53
53
|
const RE_LEADING_SLASH = /^\//;
|
|
54
54
|
const RE_SLASH = /\//g;
|
|
55
|
+
export function normalizeRoute(route) {
|
|
56
|
+
if (!route || route === "/")
|
|
57
|
+
return "/";
|
|
58
|
+
return route.startsWith("/") ? route : `/${route}`;
|
|
59
|
+
}
|
|
55
60
|
export function normalizeRouteKey(route) {
|
|
56
|
-
return route.replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
|
|
61
|
+
return normalizeRoute(route).replace(RE_LEADING_SLASH, "").replace(RE_SLASH, ":") || "index";
|
|
57
62
|
}
|
|
58
63
|
export async function compressToBase64(data) {
|
|
59
64
|
const json = JSON.stringify(data);
|
|
@@ -172,9 +177,15 @@ const IMPORT_ROWS_PER_STATEMENT = 5;
|
|
|
172
177
|
export async function importDbDump(db, rows) {
|
|
173
178
|
if (rows.length === 0)
|
|
174
179
|
return;
|
|
180
|
+
const byRoute = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const row of rows) {
|
|
182
|
+
const route = normalizeRoute(row.route);
|
|
183
|
+
byRoute.set(route, { ...row, route, route_key: normalizeRouteKey(route) });
|
|
184
|
+
}
|
|
185
|
+
const canonical = [...byRoute.values()];
|
|
175
186
|
const stmts = [];
|
|
176
|
-
for (let i = 0; i <
|
|
177
|
-
const batch =
|
|
187
|
+
for (let i = 0; i < canonical.length; i += IMPORT_ROWS_PER_STATEMENT) {
|
|
188
|
+
const batch = canonical.slice(i, i + IMPORT_ROWS_PER_STATEMENT);
|
|
178
189
|
const valuesSql = batch.map(() => `(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`).join(", ");
|
|
179
190
|
const params = batch.flatMap((row) => {
|
|
180
191
|
const source = row.source || "prerender";
|
|
@@ -5,14 +5,31 @@ import { withSiteUrl } from "#site-config/server/composables/utils";
|
|
|
5
5
|
import { toMarkdownPath } from "../../markdown-path.js";
|
|
6
6
|
import { toDeployedRoute } from "../../route-path.js";
|
|
7
7
|
import { logger } from "../logger.js";
|
|
8
|
+
import { setStatusAwareLinkHeader } from "../plugins/link-header.js";
|
|
8
9
|
import { CONTENT_NEGOTIATION_VARY, resolveContentNegotiation } from "../utils/content-negotiation.js";
|
|
9
10
|
import { computeLocaleAlternates, resolveLocaleAlternateUrl, resolveLocaleFromRoute } from "../utils/i18n.js";
|
|
10
11
|
import { buildLinkHeader } from "../utils/link-header.js";
|
|
11
12
|
import { getMarkdownRenderInfo } from "../utils/markdown-request.js";
|
|
12
13
|
const INTERNAL_HEADER = "x-ai-ready-internal";
|
|
14
|
+
function setLinkHeader(event, path, variant, config, resolveUrl, routeContext) {
|
|
15
|
+
setHeader(event, "link", buildLinkHeader(path, variant, config, resolveUrl, routeContext));
|
|
16
|
+
}
|
|
17
|
+
function setStatusAwareHeader(event, path, variant, config, resolveUrl, routeContext) {
|
|
18
|
+
const successHeader = buildLinkHeader(path, variant, config, resolveUrl, routeContext);
|
|
19
|
+
if (!config.i18n) {
|
|
20
|
+
setHeader(event, "link", successHeader);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const safeHeader = buildLinkHeader(path, variant, { ...config, i18n: null }, resolveUrl, routeContext);
|
|
24
|
+
setStatusAwareLinkHeader(event, safeHeader, successHeader);
|
|
25
|
+
}
|
|
13
26
|
function setNegotiationHeaders(event, path, config, resolveUrl, routeContext) {
|
|
14
27
|
appendHeader(event, "vary", CONTENT_NEGOTIATION_VARY);
|
|
15
|
-
|
|
28
|
+
setLinkHeader(event, path, "html", config, resolveUrl, routeContext);
|
|
29
|
+
}
|
|
30
|
+
function setStatusAwareNegotiationHeaders(event, path, config, resolveUrl, routeContext) {
|
|
31
|
+
appendHeader(event, "vary", CONTENT_NEGOTIATION_VARY);
|
|
32
|
+
setStatusAwareHeader(event, path, "html", config, resolveUrl, routeContext);
|
|
16
33
|
}
|
|
17
34
|
function setUncacheableHeaders(event) {
|
|
18
35
|
setHeader(event, "cache-control", "private, no-store");
|
|
@@ -29,7 +46,7 @@ function setUncacheableHeaders(event) {
|
|
|
29
46
|
}
|
|
30
47
|
function setMarkdownHeaders(event, path, config, resolveUrl, routeContext) {
|
|
31
48
|
setHeader(event, "content-type", "text/markdown; charset=utf-8");
|
|
32
|
-
|
|
49
|
+
setLinkHeader(event, path, "markdown", config, resolveUrl, routeContext);
|
|
33
50
|
if (config.markdownCacheHeaders) {
|
|
34
51
|
const { maxAge, swr } = config.markdownCacheHeaders;
|
|
35
52
|
const cacheControl = swr ? `public, max-age=${maxAge}, stale-while-revalidate=${maxAge}` : `public, max-age=${maxAge}`;
|
|
@@ -100,9 +117,9 @@ export default defineEventHandler(async (event) => {
|
|
|
100
117
|
const canonicalUrl = resolveUrl(path);
|
|
101
118
|
if (negotiation === "html") {
|
|
102
119
|
if (contentNegotiation._tag === "enabled")
|
|
103
|
-
|
|
120
|
+
setStatusAwareNegotiationHeaders(event, path, config, resolveUrl, routeContext);
|
|
104
121
|
else
|
|
105
|
-
|
|
122
|
+
setStatusAwareHeader(event, path, "html", config, resolveUrl, routeContext);
|
|
106
123
|
return;
|
|
107
124
|
}
|
|
108
125
|
if (!isExplicit) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getHeader, getResponseStatus, setHeader } from "#nuxtseo/h3";
|
|
2
|
+
import { defineNitroPlugin } from "#nuxtseo/nitro";
|
|
3
|
+
const STATUS_AWARE_LINK_HEADER = "nuxt-ai-ready:status-aware-link-header";
|
|
4
|
+
const ERROR_LINK_HEADER = "x-nuxt-ai-ready-error-link";
|
|
5
|
+
export function setStatusAwareLinkHeader(event, safeHeader, successHeader) {
|
|
6
|
+
event.context[STATUS_AWARE_LINK_HEADER] = {
|
|
7
|
+
error: safeHeader,
|
|
8
|
+
success: successHeader ?? safeHeader
|
|
9
|
+
};
|
|
10
|
+
setHeader(event, "link", safeHeader);
|
|
11
|
+
}
|
|
12
|
+
export default defineNitroPlugin((nitro) => {
|
|
13
|
+
nitro.hooks.hook("error", (_error, { event }) => {
|
|
14
|
+
if (!event)
|
|
15
|
+
return;
|
|
16
|
+
const header = event.context[STATUS_AWARE_LINK_HEADER];
|
|
17
|
+
if (header)
|
|
18
|
+
event.node.req.headers[ERROR_LINK_HEADER] = header.error;
|
|
19
|
+
});
|
|
20
|
+
nitro.hooks.hook("beforeResponse", (event) => {
|
|
21
|
+
const header = event.context[STATUS_AWARE_LINK_HEADER];
|
|
22
|
+
if (!header)
|
|
23
|
+
return;
|
|
24
|
+
setHeader(event, "link", getResponseStatus(event) >= 400 ? header.error : header.success);
|
|
25
|
+
});
|
|
26
|
+
nitro.hooks.hook("render:response", (response, { event }) => {
|
|
27
|
+
const header = event.context[STATUS_AWARE_LINK_HEADER];
|
|
28
|
+
const errorHeader = event.path.startsWith("/__nuxt_error") && getHeader(event, "x-nuxt-error") === "true" ? getHeader(event, ERROR_LINK_HEADER) : void 0;
|
|
29
|
+
if (errorHeader) {
|
|
30
|
+
response.headers ||= {};
|
|
31
|
+
response.headers.link = errorHeader;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!header)
|
|
35
|
+
return;
|
|
36
|
+
response.headers ||= {};
|
|
37
|
+
const statusCode = response.statusCode ?? getResponseStatus(event);
|
|
38
|
+
response.headers.link = statusCode >= 400 ? header.error : header.success;
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -480,11 +480,16 @@ function collectPagePaths(routes, parentPath = "") {
|
|
|
480
480
|
return paths;
|
|
481
481
|
}, /* @__PURE__ */ new Map());
|
|
482
482
|
}
|
|
483
|
+
function isUnlocalizedLocalePage(page) {
|
|
484
|
+
return page?._tag === "unlocalized" && typeof page.path === "string";
|
|
485
|
+
}
|
|
483
486
|
function materializeI18nPages(i18n, routes) {
|
|
484
487
|
if (!i18n.pages)
|
|
485
488
|
return void 0;
|
|
486
489
|
const pagePaths = collectPagePaths(routes);
|
|
487
490
|
return Object.fromEntries(Object.entries(i18n.pages).map(([pageName, pageLocales]) => {
|
|
491
|
+
if (isUnlocalizedLocalePage(pageLocales))
|
|
492
|
+
return [pageName, pageLocales];
|
|
488
493
|
const defaultPath = pageLocales?.[i18n.defaultLocale];
|
|
489
494
|
const fallbackPath = typeof defaultPath === "string" ? defaultPath : pagePaths.get(pageName);
|
|
490
495
|
const locales = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
@@ -1471,6 +1476,7 @@ export async function lookupContentPage(event, path) {
|
|
|
1471
1476
|
middleware: true,
|
|
1472
1477
|
handler: resolve("./runtime/server/middleware/markdown")
|
|
1473
1478
|
});
|
|
1479
|
+
addServerPlugin(resolve("./runtime/server/plugins/link-header"));
|
|
1474
1480
|
addPlugin({
|
|
1475
1481
|
mode: "server",
|
|
1476
1482
|
src: resolve("./runtime/app/plugins/md-alternate.server")
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-ai-ready",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.12",
|
|
5
5
|
"description": "Best practice AI & LLM discoverability for Nuxt sites.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -62,18 +62,18 @@
|
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@mdream/js": "^1.
|
|
65
|
+
"@mdream/js": "^1.6.2",
|
|
66
66
|
"@nuxt/kit": "^4.5.2",
|
|
67
67
|
"citty": "^0.2.2",
|
|
68
68
|
"consola": "^3.4.2",
|
|
69
69
|
"defu": "^6.1.7",
|
|
70
70
|
"drizzle-orm": "^0.45.2",
|
|
71
|
-
"mdream": "^1.
|
|
72
|
-
"nuxt-site-config": "^4.2.
|
|
73
|
-
"nuxtseo-shared": "^5.3.
|
|
71
|
+
"mdream": "^1.6.2",
|
|
72
|
+
"nuxt-site-config": "^4.2.2",
|
|
73
|
+
"nuxtseo-shared": "^5.3.12",
|
|
74
74
|
"pathe": "^2.0.3",
|
|
75
75
|
"pkg-types": "^2.3.1",
|
|
76
|
-
"site-config-stack": "^4.2.
|
|
76
|
+
"site-config-stack": "^4.2.2",
|
|
77
77
|
"sitemapd": "^0.2.2",
|
|
78
78
|
"ufo": "^1.6.4",
|
|
79
79
|
"uncrypto": "^0.1.3",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
|
90
90
|
"@nuxtjs/mcp-toolkit": "^0.18.1",
|
|
91
91
|
"@nuxtjs/robots": "^6.1.4",
|
|
92
|
-
"@nuxtjs/sitemap": "^8.3.
|
|
92
|
+
"@nuxtjs/sitemap": "^8.3.4",
|
|
93
93
|
"@types/better-sqlite3": "^9.6.0",
|
|
94
94
|
"@vitest/coverage-v8": "^4.1.10",
|
|
95
95
|
"@vue/test-utils": "^2.4.11",
|
|
@@ -103,8 +103,8 @@
|
|
|
103
103
|
"happy-dom": "^20.11.2",
|
|
104
104
|
"nitropack": "^2.13.4",
|
|
105
105
|
"nuxt": "^4.5.2",
|
|
106
|
-
"nuxt-site-config": "^4.2.
|
|
107
|
-
"nuxtseo-layer-devtools": "^5.3.
|
|
106
|
+
"nuxt-site-config": "^4.2.2",
|
|
107
|
+
"nuxtseo-layer-devtools": "^5.3.12",
|
|
108
108
|
"playwright": "^1.62.1",
|
|
109
109
|
"playwright-core": "^1.62.1",
|
|
110
110
|
"tinyglobby": "^0.2.17",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"vue": "^3.5.41",
|
|
115
115
|
"vue-router": "^5.2.0",
|
|
116
116
|
"vue-tsc": "^3.3.9",
|
|
117
|
-
"wrangler": "^4.
|
|
117
|
+
"wrangler": "^4.121.0",
|
|
118
118
|
"zod": "^4.4.3"
|
|
119
119
|
},
|
|
120
120
|
"scripts": {
|