From 4d0e594f8b0fe4a3d31b578da3b8a51c90d98f3c Mon Sep 17 00:00:00 2001 From: Harlan GitHub Agent Date: Mon, 5 Oct 2026 08:47:50 +1100 Subject: [PATCH 1/3] fix(ai-ready): prune 410 routes and stop re-arming failed rows --- patches/nuxt-ai-ready@2.4.1.patch | 109 +++++ pnpm-lock.yaml | 408 +----------------- pnpm-workspace.yaml | 1 + test/fixtures/ai-ready-h3-shim.ts | 7 + test/fixtures/ai-ready-i18n-runtime-stub.ts | 15 + test/fixtures/ai-ready-nitro-shim.ts | 27 ++ test/fixtures/ai-ready-read-page-data-stub.ts | 10 + test/unit/ai-ready-gone-route.test.ts | 181 ++++++++ vitest.config.ts | 12 +- 9 files changed, 364 insertions(+), 406 deletions(-) create mode 100644 patches/nuxt-ai-ready@2.4.1.patch create mode 100644 test/fixtures/ai-ready-h3-shim.ts create mode 100644 test/fixtures/ai-ready-i18n-runtime-stub.ts create mode 100644 test/fixtures/ai-ready-nitro-shim.ts create mode 100644 test/fixtures/ai-ready-read-page-data-stub.ts create mode 100644 test/unit/ai-ready-gone-route.test.ts diff --git a/patches/nuxt-ai-ready@2.4.1.patch b/patches/nuxt-ai-ready@2.4.1.patch new file mode 100644 index 00000000..325677a5 --- /dev/null +++ b/patches/nuxt-ai-ready@2.4.1.patch @@ -0,0 +1,109 @@ +diff --git a/dist/runtime/server/db/drizzle/queries.js b/dist/runtime/server/db/drizzle/queries.js +index 8c6ca0b7321ca15684f3c663f4e51fc00a054e08..d26418deabec180f17c38e8aa519778f2aa44d0d 100644 +--- a/dist/runtime/server/db/drizzle/queries.js ++++ b/dist/runtime/server/db/drizzle/queries.js +@@ -706,7 +706,6 @@ export async function seedRoutes(event, routes, options = {}) { + indexed = CASE WHEN ai_ready_pages.is_error = 1 THEN 0 ELSE ai_ready_pages.indexed END + WHERE ai_ready_pages.last_seen_at IS NULL + OR ai_ready_pages.last_seen_at < excluded.last_seen_at - ${refreshWindowMs} +- OR ai_ready_pages.is_error = 1 + OR ai_ready_pages.locale IS DISTINCT FROM excluded.locale + `, + params +diff --git a/dist/runtime/server/db/queries.js b/dist/runtime/server/db/queries.js +index 75b402435d2c6f95c9debd71e703fa2d3dfd2206..eccf0b3c4aee4ff1f5ce17c9d4ac0cd3d17cf3df 100644 +--- a/dist/runtime/server/db/queries.js ++++ b/dist/runtime/server/db/queries.js +@@ -380,7 +380,6 @@ export async function seedRoutes(event, routes, options = {}) { + indexed = CASE WHEN ai_ready_pages.is_error = 1 THEN 0 ELSE ai_ready_pages.indexed END + WHERE ai_ready_pages.last_seen_at IS NULL + OR ai_ready_pages.last_seen_at < excluded.last_seen_at - ${refreshWindowMs} +- OR ai_ready_pages.is_error = 1 + OR ai_ready_pages.locale IS DISTINCT FROM excluded.locale + `, + params +@@ -389,6 +388,12 @@ export async function seedRoutes(event, routes, options = {}) { + await db.batch(stmts); + return byRoute.size; + } ++export async function prunePage(event, route) { ++ const db = await getDb(event); ++ if (!db) ++ return; ++ await db.exec("DELETE FROM ai_ready_pages WHERE route = ?", [normalizeRoute(route)]); ++} + export async function pruneStaleRoutes(event, staleThresholdSeconds, protectedSince, refreshWindowMs = SEED_REFRESH_WINDOW_MS) { + const db = await getDb(event); + if (!db) +diff --git a/dist/runtime/server/utils/batchIndex.js b/dist/runtime/server/utils/batchIndex.js +index c760b5acf0d48c5b2f6057bf24456d1b434a98ab..bc04ae4d25e16c7700453d7329b9331f05672428 100644 +--- a/dist/runtime/server/utils/batchIndex.js ++++ b/dist/runtime/server/utils/batchIndex.js +@@ -36,12 +36,14 @@ export async function batchIndexPages(event, options = {}) { + markFailedAsError: true, + force: true + }); +- return { route: page.route, success: result.success }; ++ return { route: page.route, success: result.success, gone: result.gone === true }; + }) + ); + for (const result of results) { + processed++; +- if (result.success) { ++ if (result.gone) { ++ logger.debug(`[batchIndex] Pruned gone route: ${result.route}`); ++ } else if (result.success) { + indexed++; + logger.debug(`[batchIndex] Indexed: ${result.route}`); + } else { +diff --git a/dist/runtime/server/utils/indexPage.d.ts b/dist/runtime/server/utils/indexPage.d.ts +index 8464b6fc49b0542c253124187bd04ea7be9d474e..9f2b0535d21b88966c57be4f0d1562a66b569476 100644 +--- a/dist/runtime/server/utils/indexPage.d.ts ++++ b/dist/runtime/server/utils/indexPage.d.ts +@@ -30,6 +30,8 @@ export interface IndexPageResult { + }; + /** Error message if failed */ + error?: string; ++ /** True when the route answered 410 Gone and its row was pruned */ ++ gone?: boolean; + } + /** + * Manually index a page's HTML content into database +diff --git a/dist/runtime/server/utils/indexPage.js b/dist/runtime/server/utils/indexPage.js +index 62441b349d1c96199207b0b3dad3f2f58423d354..002479c038c303c6ee94c67a6efd8f72a21ebe99 100644 +--- a/dist/runtime/server/utils/indexPage.js ++++ b/dist/runtime/server/utils/indexPage.js +@@ -1,5 +1,5 @@ + import { fetchWithEvent, useNitroApp, useRuntimeConfig } from "#nuxtseo/nitro"; +-import { getPageIndexState, upsertPage } from "../db/queries.js"; ++import { getPageIndexState, prunePage, upsertPage } from "../db/queries.js"; + import { computeContentHash } from "../db/shared.js"; + import { logger } from "../logger.js"; + import { convertHtmlToMarkdown } from "../utils.js"; +@@ -76,6 +76,7 @@ export async function indexPage(route, html, options = {}, event) { + } + export async function indexPageByRoute(route, event, options = {}) { + logger.debug(`[indexPageByRoute] Fetching HTML for ${route} (timeout: 10000ms)`); ++ let fetchStatus; + const html = await (event ? fetchWithEvent(event, route, { + headers: { accept: "text/html", [INTERNAL_HEADER]: "1" }, + timeout: 1e4 +@@ -84,6 +85,7 @@ export async function indexPageByRoute(route, event, options = {}) { + timeout: 1e4 + // 10s timeout per page (must fit within CF worker limit) + })).catch((err) => { ++ fetchStatus = err?.status ?? err?.statusCode ?? err?.response?.status; + logger.warn(`[indexPageByRoute] Failed to fetch ${route}:`, err.message); + return null; + }); +@@ -91,6 +93,10 @@ export async function indexPageByRoute(route, event, options = {}) { + logger.debug(`[indexPageByRoute] Fetched ${route} (${html.length} bytes)`); + } + if (!html || typeof html !== "string") { ++ if (fetchStatus === 410) { ++ await prunePage(event, route); ++ return { success: false, gone: true, error: `Route gone (410): ${route}` }; ++ } + if (options.markFailedAsError) { + await upsertPage(event, { + route, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72da48b4..46037f7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -385,6 +385,7 @@ catalogs: patchedDependencies: hookable@5.5.3: 3b44da13a743bfa2eab02aef7246065e4dfd56e9878c206f34b51322832cfb2c hookable@6.1.1: 7530364d493fa95257cb863d105ed55a1bb4b46d5c9a178a6d64cb73028ed344 + nuxt-ai-ready@2.4.1: c06f83cb214a4c2b4017b540ff07f2a358193c8a6800a10d385876428d54d1b9 importers: @@ -485,7 +486,7 @@ importers: version: 4.5.2(@babel/plugin-proposal-decorators@8.0.2)(@babel/plugin-syntax-jsx@7.29.7)(@babel/plugin-syntax-typescript@7.29.7)(@oxc-project/types@0.144.0)(@types/node@26.4.0)(@vue/compiler-sfc@3.5.43)(aws4fetch@1.0.20)(better-sqlite3@13.0.3)(commander@14.0.3)(db0@0.3.4(better-sqlite3@13.0.3))(esbuild@0.28.2)(eslint@10.9.1(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(optionator@0.9.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.4)(rollup@4.62.4))(rollup@4.62.4)(srvx@0.12.5)(supports-color@10.2.2)(terser@5.50.0)(tsx@4.23.13)(typescript@6.0.3)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))(vue-tsc@3.3.11(typescript@6.0.3))(yaml@2.9.1) nuxt-ai-ready: specifier: 'catalog:' - version: 2.4.1(d6a41079a964711e4f325b563afdaa67) + version: 2.4.1(patch_hash=c06f83cb214a4c2b4017b540ff07f2a358193c8a6800a10d385876428d54d1b9)(d6a41079a964711e4f325b563afdaa67) nuxt-auth-utils: specifier: 'catalog:' version: 0.5.30(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) @@ -3275,9 +3276,6 @@ packages: resolution: {integrity: sha512-da+MMyeXhBaKtxQiWPfy7+056wk3lVIhioJnXHXkJ2/OHDaZfFcyKHNl1R06sdYO8lIRXcXdoZ6LO2ARmkAREA==} engines: {node: '>=18.16.0'} - '@quansync/fs@1.1.0': - resolution: {integrity: sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==} - '@resvg/resvg-js-android-arm-eabi@2.6.2': resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==} engines: {node: '>= 10'} @@ -4834,141 +4832,6 @@ packages: peerDependencies: vue: ^3.5.0 - '@yuku-codegen/binding-android-arm64@0.8.7': - resolution: {integrity: sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==} - cpu: [arm64] - os: [android] - - '@yuku-codegen/binding-darwin-arm64@0.8.7': - resolution: {integrity: sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==} - cpu: [arm64] - os: [darwin] - - '@yuku-codegen/binding-darwin-x64@0.8.7': - resolution: {integrity: sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==} - cpu: [x64] - os: [darwin] - - '@yuku-codegen/binding-freebsd-x64@0.8.7': - resolution: {integrity: sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==} - cpu: [x64] - os: [freebsd] - - '@yuku-codegen/binding-linux-arm-gnu@0.8.7': - resolution: {integrity: sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@yuku-codegen/binding-linux-arm-musl@0.8.7': - resolution: {integrity: sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': - resolution: {integrity: sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@yuku-codegen/binding-linux-arm64-musl@0.8.7': - resolution: {integrity: sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@yuku-codegen/binding-linux-x64-gnu@0.8.7': - resolution: {integrity: sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@yuku-codegen/binding-linux-x64-musl@0.8.7': - resolution: {integrity: sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@yuku-codegen/binding-win32-arm64@0.8.7': - resolution: {integrity: sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==} - cpu: [arm64] - os: [win32] - - '@yuku-codegen/binding-win32-x64@0.8.7': - resolution: {integrity: sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==} - cpu: [x64] - os: [win32] - - '@yuku-parser/binding-android-arm64@0.8.7': - resolution: {integrity: sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==} - cpu: [arm64] - os: [android] - - '@yuku-parser/binding-darwin-arm64@0.8.7': - resolution: {integrity: sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==} - cpu: [arm64] - os: [darwin] - - '@yuku-parser/binding-darwin-x64@0.8.7': - resolution: {integrity: sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==} - cpu: [x64] - os: [darwin] - - '@yuku-parser/binding-freebsd-x64@0.8.7': - resolution: {integrity: sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==} - cpu: [x64] - os: [freebsd] - - '@yuku-parser/binding-linux-arm-gnu@0.8.7': - resolution: {integrity: sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm-musl@0.8.7': - resolution: {integrity: sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-arm64-gnu@0.8.7': - resolution: {integrity: sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm64-musl@0.8.7': - resolution: {integrity: sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-x64-gnu@0.8.7': - resolution: {integrity: sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-x64-musl@0.8.7': - resolution: {integrity: sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-win32-arm64@0.8.7': - resolution: {integrity: sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==} - cpu: [arm64] - os: [win32] - - '@yuku-parser/binding-win32-x64@0.8.7': - resolution: {integrity: sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==} - cpu: [x64] - os: [win32] - - '@yuku-toolchain/types@0.8.7': - resolution: {integrity: sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==} - abbrev@3.0.1: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -5865,15 +5728,6 @@ packages: zod: optional: true - dts-resolver@3.0.0: - resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} - engines: {node: ^22.18.0 || >=24.0.0} - peerDependencies: - oxc-resolver: '>=11.0.0' - peerDependenciesMeta: - oxc-resolver: - optional: true - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -6515,10 +6369,6 @@ packages: get-tsconfig@4.14.1: resolution: {integrity: sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==} - get-tsconfig@5.0.0-beta.5: - resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} - engines: {node: '>=20.20.0'} - giget@3.3.1: resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} hasBin: true @@ -6702,10 +6552,6 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - import-without-cache@0.4.1: - resolution: {integrity: sha512-vXoV9PjKHEednCUu01e98TkImxy67e3BJXbTIOmIq4Hyzw3IAwYRcuPkfeTzJzwyyts4kjuA73x+pWJLc4f86A==} - engines: {node: ^22.18.0 || >=24.0.0} - impound@1.1.6: resolution: {integrity: sha512-ugavDQkE74SGrBjagNIwNVHNBxX14mmfQnTm4jFGzOoWwYsgihqV698BNr5xwRY9quKK4rEg8bc6ECltllMr+w==} @@ -8253,9 +8099,6 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - quansync@1.0.0: - resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -8357,25 +8200,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown-plugin-dts@0.27.14: - resolution: {integrity: sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==} - engines: {node: ^22.18.0 || >=24.11.0} - peerDependencies: - '@typescript/native-preview': '*' - '@volar/typescript': ~2.4.0 - rolldown: ^1.0.0 - typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 - vue-tsc: ~3.2.0 || ~3.3.0 - peerDependenciesMeta: - '@typescript/native-preview': - optional: true - '@volar/typescript': - optional: true - typescript: - optional: true - vue-tsc: - optional: true - rolldown-string@0.3.1: resolution: {integrity: sha512-dv8GOXkYqUQdI0rsXB8hmzO95pKWSuX2eg5/JSJa9czfEVIFuKNR7ZJDfat8LlmD35RAhWY+evS7/wXXqFDfSg==} engines: {node: '>=20.19.0'} @@ -8825,50 +8649,12 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' - tsdown@0.22.14: - resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} - engines: {node: ^22.18.0 || >=24.11.0} - hasBin: true - peerDependencies: - '@arethetypeswrong/core': ^0.18.1 - '@tsdown/css': 0.22.14 - '@tsdown/exe': 0.22.14 - '@vitejs/devtools': '*' - publint: ^0.3.8 - tsx: '*' - typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 - unplugin-unused: ^0.5.0 - unrun: '*' - peerDependenciesMeta: - '@arethetypeswrong/core': - optional: true - '@tsdown/css': - optional: true - '@tsdown/exe': - optional: true - '@vitejs/devtools': - optional: true - publint: - optional: true - tsx: - optional: true - typescript: - optional: true - unplugin-unused: - optional: true - unrun: - optional: true - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -8915,9 +8701,6 @@ packages: ultrahtml@1.7.0: resolution: {integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==} - unconfig-core@7.5.0: - resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} @@ -9634,15 +9417,6 @@ packages: youch@4.1.1: resolution: {integrity: sha512-mxW3qiSnl+GRxXsaUMzv2Mbada1Y8CDltET9UxejDQe6DBYlSekghl5U5K0ReAikcHDi0G1vKZEmmo/NWAGKLA==} - yuku-ast@0.8.7: - resolution: {integrity: sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==} - - yuku-codegen@0.8.7: - resolution: {integrity: sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw==} - - yuku-parser@0.8.7: - resolution: {integrity: sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==} - zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -12871,10 +12645,6 @@ snapshots: safe-stable-stringify: 2.5.0 secure-json-parse: 4.1.0 - '@quansync/fs@1.1.0': - dependencies: - quansync: 1.0.0 - '@resvg/resvg-js-android-arm-eabi@2.6.2': optional: true @@ -14316,80 +14086,6 @@ snapshots: dependencies: vue: 3.5.43(typescript@6.0.3) - '@yuku-codegen/binding-android-arm64@0.8.7': - optional: true - - '@yuku-codegen/binding-darwin-arm64@0.8.7': - optional: true - - '@yuku-codegen/binding-darwin-x64@0.8.7': - optional: true - - '@yuku-codegen/binding-freebsd-x64@0.8.7': - optional: true - - '@yuku-codegen/binding-linux-arm-gnu@0.8.7': - optional: true - - '@yuku-codegen/binding-linux-arm-musl@0.8.7': - optional: true - - '@yuku-codegen/binding-linux-arm64-gnu@0.8.7': - optional: true - - '@yuku-codegen/binding-linux-arm64-musl@0.8.7': - optional: true - - '@yuku-codegen/binding-linux-x64-gnu@0.8.7': - optional: true - - '@yuku-codegen/binding-linux-x64-musl@0.8.7': - optional: true - - '@yuku-codegen/binding-win32-arm64@0.8.7': - optional: true - - '@yuku-codegen/binding-win32-x64@0.8.7': - optional: true - - '@yuku-parser/binding-android-arm64@0.8.7': - optional: true - - '@yuku-parser/binding-darwin-arm64@0.8.7': - optional: true - - '@yuku-parser/binding-darwin-x64@0.8.7': - optional: true - - '@yuku-parser/binding-freebsd-x64@0.8.7': - optional: true - - '@yuku-parser/binding-linux-arm-gnu@0.8.7': - optional: true - - '@yuku-parser/binding-linux-arm-musl@0.8.7': - optional: true - - '@yuku-parser/binding-linux-arm64-gnu@0.8.7': - optional: true - - '@yuku-parser/binding-linux-arm64-musl@0.8.7': - optional: true - - '@yuku-parser/binding-linux-x64-gnu@0.8.7': - optional: true - - '@yuku-parser/binding-linux-x64-musl@0.8.7': - optional: true - - '@yuku-parser/binding-win32-arm64@0.8.7': - optional: true - - '@yuku-parser/binding-win32-x64@0.8.7': - optional: true - - '@yuku-toolchain/types@0.8.7': {} - abbrev@3.0.1: {} abbrev@5.0.0: {} @@ -15075,8 +14771,6 @@ snapshots: valibot: 1.4.2(typescript@6.0.3) zod: 4.5.4 - dts-resolver@3.0.0: {} - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -15886,10 +15580,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-tsconfig@5.0.0-beta.5: - dependencies: - resolve-pkg-maps: 1.0.0 - giget@3.3.1: {} github-slugger@2.0.0: {} @@ -16068,8 +15758,6 @@ snapshots: import-meta-resolve@4.2.0: {} - import-without-cache@0.4.1: {} - impound@1.1.6(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -17190,7 +16878,7 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt-ai-ready@2.4.1(d6a41079a964711e4f325b563afdaa67): + nuxt-ai-ready@2.4.1(patch_hash=c06f83cb214a4c2b4017b540ff07f2a358193c8a6800a10d385876428d54d1b9)(d6a41079a964711e4f325b563afdaa67): dependencies: '@mdream/js': 1.7.2 '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) @@ -18357,8 +18045,6 @@ snapshots: quansync@0.2.11: {} - quansync@1.0.0: {} - queue-microtask@1.2.3: {} quote-js-string@0.1.0: {} @@ -18477,22 +18163,6 @@ snapshots: reusify@1.1.0: {} - rolldown-plugin-dts@0.27.14(@volar/typescript@2.4.28(typescript@6.0.3))(rolldown@1.2.4)(typescript@6.0.3)(vue-tsc@3.3.11(typescript@6.0.3)): - dependencies: - dts-resolver: 3.0.0 - get-tsconfig: 5.0.0-beta.5 - obug: 2.1.4 - rolldown: 1.2.4 - yuku-ast: 0.8.7 - yuku-codegen: 0.8.7 - yuku-parser: 0.8.7 - optionalDependencies: - '@volar/typescript': 2.4.28(typescript@6.0.3) - typescript: 6.0.3 - vue-tsc: 3.3.11(typescript@6.0.3) - transitivePeerDependencies: - - oxc-resolver - rolldown-string@0.3.1(rolldown@1.2.4): dependencies: magic-string: 1.2.3 @@ -19052,38 +18722,10 @@ snapshots: tr46@0.0.3: {} - tree-kill@1.2.2: {} - ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 - tsdown@0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.13)(typescript@6.0.3)(vue-tsc@3.3.11(typescript@6.0.3)): - dependencies: - ansis: 4.3.1 - cac: 7.0.0 - defu: 6.1.7 - empathic: 2.0.1 - hookable: 6.1.2 - import-without-cache: 0.4.1 - obug: 2.1.4 - picomatch: 4.0.7 - rolldown: 1.2.4 - rolldown-plugin-dts: 0.27.14(@volar/typescript@2.4.28(typescript@6.0.3))(rolldown@1.2.4)(typescript@6.0.3)(vue-tsc@3.3.11(typescript@6.0.3)) - tinyexec: 1.3.1 - tinyglobby: 0.2.17 - tree-kill: 1.2.2 - unconfig-core: 7.5.0 - verkit: 0.3.2 - optionalDependencies: - tsx: 4.23.13 - typescript: 6.0.3 - transitivePeerDependencies: - - '@typescript/native-preview' - - '@volar/typescript' - - oxc-resolver - - vue-tsc - tslib@2.8.1: {} tsx@4.23.13: @@ -19120,11 +18762,6 @@ snapshots: ultrahtml@1.7.0: {} - unconfig-core@7.5.0: - dependencies: - '@quansync/fs': 1.1.0 - quansync: 1.0.0 - uncrypto@0.1.3: {} unctx@2.5.0: @@ -19901,45 +19538,6 @@ snapshots: cookie-es: 3.1.1 youch-core: 0.3.3 - yuku-ast@0.8.7: - dependencies: - '@yuku-toolchain/types': 0.8.7 - - yuku-codegen@0.8.7: - dependencies: - '@yuku-toolchain/types': 0.8.7 - optionalDependencies: - '@yuku-codegen/binding-android-arm64': 0.8.7 - '@yuku-codegen/binding-darwin-arm64': 0.8.7 - '@yuku-codegen/binding-darwin-x64': 0.8.7 - '@yuku-codegen/binding-freebsd-x64': 0.8.7 - '@yuku-codegen/binding-linux-arm-gnu': 0.8.7 - '@yuku-codegen/binding-linux-arm-musl': 0.8.7 - '@yuku-codegen/binding-linux-arm64-gnu': 0.8.7 - '@yuku-codegen/binding-linux-arm64-musl': 0.8.7 - '@yuku-codegen/binding-linux-x64-gnu': 0.8.7 - '@yuku-codegen/binding-linux-x64-musl': 0.8.7 - '@yuku-codegen/binding-win32-arm64': 0.8.7 - '@yuku-codegen/binding-win32-x64': 0.8.7 - - yuku-parser@0.8.7: - dependencies: - '@yuku-toolchain/types': 0.8.7 - yuku-ast: 0.8.7 - optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.8.7 - '@yuku-parser/binding-darwin-arm64': 0.8.7 - '@yuku-parser/binding-darwin-x64': 0.8.7 - '@yuku-parser/binding-freebsd-x64': 0.8.7 - '@yuku-parser/binding-linux-arm-gnu': 0.8.7 - '@yuku-parser/binding-linux-arm-musl': 0.8.7 - '@yuku-parser/binding-linux-arm64-gnu': 0.8.7 - '@yuku-parser/binding-linux-arm64-musl': 0.8.7 - '@yuku-parser/binding-linux-x64-gnu': 0.8.7 - '@yuku-parser/binding-linux-x64-musl': 0.8.7 - '@yuku-parser/binding-win32-arm64': 0.8.7 - '@yuku-parser/binding-win32-x64': 0.8.7 - zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f58c6409..a5dfd892 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -178,6 +178,7 @@ pipelines: patchedDependencies: hookable@5.5.3: patches/hookable@5.5.3.patch hookable@6.1.1: patches/hookable@6.1.1.patch + nuxt-ai-ready@2.4.1: patches/nuxt-ai-ready@2.4.1.patch catalog: '@antfu/eslint-config': ^9.3.0 '@cloudflare/sandbox': 1.0.0 diff --git a/test/fixtures/ai-ready-h3-shim.ts b/test/fixtures/ai-ready-h3-shim.ts new file mode 100644 index 00000000..063022da --- /dev/null +++ b/test/fixtures/ai-ready-h3-shim.ts @@ -0,0 +1,7 @@ +/** + * Stand-in for the `#nuxtseo/h3` virtual the nuxt-ai-ready module injects at + * build time. Tests that load the module's runtime dist files directly alias + * this specifier here; the module's own runtime API is h3's, so re-export it. + */ + +export * from 'h3' diff --git a/test/fixtures/ai-ready-i18n-runtime-stub.ts b/test/fixtures/ai-ready-i18n-runtime-stub.ts new file mode 100644 index 00000000..0665a3e6 --- /dev/null +++ b/test/fixtures/ai-ready-i18n-runtime-stub.ts @@ -0,0 +1,15 @@ +/** + * Stand-in for the `#ai-ready-virtual/i18n-runtime.mjs` virtual the + * nuxt-ai-ready module generates from nuxtseo-shared at build time. Locale + * resolution only runs when the module's i18n config is set, which tests + * loading the runtime dist files don't do; the exports throw so that stays + * true loudly. + */ + +export function resolveLocaleFromRoute(): never { + throw new Error('#ai-ready-virtual/i18n-runtime.mjs stub: i18n is not available in tests') +} + +export function resolveI18nDomain(): never { + throw new Error('#ai-ready-virtual/i18n-runtime.mjs stub: i18n is not available in tests') +} diff --git a/test/fixtures/ai-ready-nitro-shim.ts b/test/fixtures/ai-ready-nitro-shim.ts new file mode 100644 index 00000000..5376a522 --- /dev/null +++ b/test/fixtures/ai-ready-nitro-shim.ts @@ -0,0 +1,27 @@ +/** + * Stand-in for the `#nuxtseo/nitro` virtual the nuxt-ai-ready module injects + * at build time. Tests that load the module's runtime dist files directly + * (node environment, no Nuxt build) alias this specifier here and mock it + * with `vi.mock`. Nothing in the suite imports the real functions, so every + * export throws to fail loud if that ever changes. + */ + +export function useRuntimeConfig(): never { + throw new Error('#nuxtseo/nitro shim: useRuntimeConfig must be mocked') +} + +export function useEvent(): never { + throw new Error('#nuxtseo/nitro shim: useEvent must be mocked') +} + +export function useNitroApp(): never { + throw new Error('#nuxtseo/nitro shim: useNitroApp must be mocked') +} + +export async function fetchWithEvent( + _event: unknown, + request: RequestInfo | URL, + init?: RequestInit, +): Promise { + return globalThis.fetch(request, init) +} diff --git a/test/fixtures/ai-ready-read-page-data-stub.ts b/test/fixtures/ai-ready-read-page-data-stub.ts new file mode 100644 index 00000000..bde6280c --- /dev/null +++ b/test/fixtures/ai-ready-read-page-data-stub.ts @@ -0,0 +1,10 @@ +/** + * Stand-in for the `#ai-ready-virtual/read-page-data.mjs` virtual the + * nuxt-ai-ready module generates at build time. It only serves the prerender + * database path, which tests never execute; it exists so the dynamic import + * inside the module's dist files resolves in a plain node environment. + */ + +export async function readPageDataFromFilesystem(): Promise { + throw new Error('#ai-ready-virtual/read-page-data.mjs stub: the prerender path is not available in tests') +} diff --git a/test/unit/ai-ready-gone-route.test.ts b/test/unit/ai-ready-gone-route.test.ts new file mode 100644 index 00000000..c0f92378 --- /dev/null +++ b/test/unit/ai-ready-gone-route.test.ts @@ -0,0 +1,181 @@ +// @vitest-environment node +import type { H3Event } from 'h3' +import type { SQLInputValue } from 'node:sqlite' +import { DatabaseSync } from 'node:sqlite' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +/** + * The pinned nuxt-ai-ready (2.4.1, patched in patches/) must treat a 410 + * route as terminal: the page row is pruned and the cron index result stays + * error-free, instead of the seed re-arming the failed row every crawl. + * + * Regression net for the patch: a catalog pin bump without a matching patch + * would silently restore the re-arm and fail these tests. + */ + +const mocks = vi.hoisted(() => ({ + initSchema: vi.fn(), + runtimeConfig: {} as Record, + useRawDb: vi.fn(), + fetchWithEvent: vi.fn(), +})) + +vi.mock('#nuxtseo/nitro', () => ({ + useEvent: () => { + throw new Error('No active event') + }, + useRuntimeConfig: () => mocks.runtimeConfig, + fetchWithEvent: mocks.fetchWithEvent, + useNitroApp: () => ({ hooks: { callHook: vi.fn() } }), +})) + +// The package exports map only exposes the module entry, so the test reaches +// the runtime dist files by path. `vi.mock` matches the dist files' own +// relative imports of the same real paths. +vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/drizzle/queries.js', () => ({ + initSchema: mocks.initSchema, +})) + +vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/drizzle/raw.js', () => ({ + useRawDb: mocks.useRawDb, +})) + +vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/logger.js', () => ({ + logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }, +})) + +// indexPage.js pulls in the module's utils.js for the success path, whose +// mdream import loads a wasm asset no test environment serves. The 410 paths +// under test never reach it. +vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils.js', () => ({ + convertHtmlToMarkdown: () => { + throw new Error('convertHtmlToMarkdown is not available in tests') + }, +})) + +const { seedRoutes } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/queries.js') +const { batchIndexPages } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/batchIndex.js') + +const HOUR = 60 * 60 * 1000 +const T0 = Date.UTC(2026, 9, 1) +// Truthy so the dist code routes fetches through `fetchWithEvent`, which the +// test controls. +const event = { + fetch: () => new Response(null), + __is_event__: true, + node: {}, + context: {}, +} as unknown as H3Event + +const SCHEMA = ` + CREATE TABLE IF NOT EXISTS ai_ready_pages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + route TEXT UNIQUE NOT NULL, + route_key TEXT UNIQUE NOT NULL, + title TEXT NOT NULL DEFAULT '', + description TEXT NOT NULL DEFAULT '', + markdown TEXT NOT NULL DEFAULT '', + headings TEXT NOT NULL DEFAULT '[]', + keywords TEXT NOT NULL DEFAULT '[]', + content_hash TEXT, + updated_at TEXT NOT NULL, + indexed_at INTEGER NOT NULL, + is_error INTEGER NOT NULL DEFAULT 0, + indexed INTEGER NOT NULL DEFAULT 0, + source TEXT NOT NULL DEFAULT 'prerender', + last_seen_at INTEGER, + locale TEXT NOT NULL DEFAULT '' + ) +` + +function goneFetchError(status: number): Error { + return Object.assign(new Error(`Request failed with status code ${status} Gone`), { + status, + statusCode: status, + }) +} + +describe('ai-ready gone route (patched nuxt-ai-ready)', () => { + let sqlite: DatabaseSync + + beforeEach(() => { + vi.useFakeTimers({ toFake: ['Date'] }) + vi.setSystemTime(T0) + sqlite = new DatabaseSync(':memory:') + sqlite.exec(SCHEMA) + mocks.initSchema.mockReset().mockResolvedValue(undefined) + mocks.useRawDb.mockReset().mockResolvedValue({ + dialect: 'sqlite', + all: async (sql: string, params: SQLInputValue[] = []) => sqlite.prepare(sql).all(...params), + first: async (sql: string, params: SQLInputValue[] = []) => sqlite.prepare(sql).get(...params), + exec: async (sql: string, params: SQLInputValue[] = []) => { + sqlite.prepare(sql).run(...params) + }, + batch: async (stmts: { sql: string, params?: SQLInputValue[] }[]) => { + for (const stmt of stmts) + sqlite.prepare(stmt.sql).run(...(stmt.params || [])) + }, + }) + mocks.fetchWithEvent.mockReset() + mocks.runtimeConfig['nuxt-ai-ready'] = { + debug: false, + database: { type: 'd1' }, + runtimeSync: { enabled: true, ttl: 3600, batchSize: 10 }, + } + }) + + afterEach(() => { + vi.useRealTimers() + sqlite.close() + }) + + function rowCount(): number { + return Number(sqlite.prepare('SELECT COUNT(*) as count FROM ai_ready_pages').get()?.count ?? 0) + } + + function row(route: string): { is_error: number, indexed: number } | undefined { + return sqlite.prepare('SELECT is_error, indexed FROM ai_ready_pages WHERE route = ?').get(route) as { is_error: number, indexed: number } | undefined + } + + it('does not re-arm a failed row inside the seed refresh window', async () => { + await seedRoutes(event, ['/failed']) + mocks.fetchWithEvent.mockRejectedValue(goneFetchError(500)) + await batchIndexPages(event, { limit: 10 }) + expect(row('/failed')).toMatchObject({ is_error: 1 }) + + await seedRoutes(event, ['/failed']) + expect(row('/failed')).toMatchObject({ is_error: 1 }) + }) + + it('re-arms a failed row after the seed refresh window', async () => { + await seedRoutes(event, ['/failed']) + mocks.fetchWithEvent.mockRejectedValue(goneFetchError(500)) + await batchIndexPages(event, { limit: 10 }) + expect(row('/failed')).toMatchObject({ is_error: 1 }) + + vi.setSystemTime(T0 + 25 * HOUR) + await seedRoutes(event, ['/failed']) + expect(row('/failed')).toEqual({ is_error: 0, indexed: 0 }) + }) + + it('prunes a 410 route and keeps indexResult.errors empty across two cron passes', async () => { + mocks.fetchWithEvent.mockRejectedValue(goneFetchError(410)) + + await seedRoutes(event, ['/gone']) + const first = await batchIndexPages(event, { limit: 10 }) + expect(first.errors).toEqual([]) + expect(first.indexed).toBe(0) + expect(first.remaining).toBe(0) + expect(first.complete).toBe(true) + expect(rowCount()).toBe(0) + + // The sitemap still lists the route, so the next pass re-seeds it; the + // fetch is gone again, the row is pruned again, and nothing is reported. + await seedRoutes(event, ['/gone']) + expect(rowCount()).toBe(1) + const second = await batchIndexPages(event, { limit: 10 }) + expect(second.errors).toEqual([]) + expect(second.remaining).toBe(0) + expect(rowCount()).toBe(0) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts index 5dd06bf8..9e3094da 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,7 +16,17 @@ import { configDefaults } from 'vitest/config' const MAX_WORKERS = Math.max(2, Math.min(6, availableParallelism() - 2)) export default defineVitestConfig({ - resolve: { alias: { '#checkin/checks': fileURLToPath(new URL('./test/fixtures/checkin-registry.ts', import.meta.url)) } }, + resolve: { + alias: { + '#checkin/checks': fileURLToPath(new URL('./test/fixtures/checkin-registry.ts', import.meta.url)), + // The nuxt-ai-ready module injects these virtuals at build time; tests + // that load its runtime dist files directly need them to resolve. + '#nuxtseo/nitro': fileURLToPath(new URL('./test/fixtures/ai-ready-nitro-shim.ts', import.meta.url)), + '#nuxtseo/h3': fileURLToPath(new URL('./test/fixtures/ai-ready-h3-shim.ts', import.meta.url)), + '#ai-ready-virtual/read-page-data.mjs': fileURLToPath(new URL('./test/fixtures/ai-ready-read-page-data-stub.ts', import.meta.url)), + '#ai-ready-virtual/i18n-runtime.mjs': fileURLToPath(new URL('./test/fixtures/ai-ready-i18n-runtime-stub.ts', import.meta.url)), + }, + }, test: { globals: true, environment: 'nuxt', From 2d486c6ef14b47b1bc9f36781bc8e57fe9584418 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 5 Oct 2026 15:03:02 +1100 Subject: [PATCH 2/3] fix(indexing): use released nuxt-ai-ready 2.5.2 --- patches/nuxt-ai-ready@2.4.1.patch | 109 ----- pnpm-lock.yaml | 605 ++++++++++++++++++-------- pnpm-workspace.yaml | 5 +- test/unit/ai-ready-gone-route.test.ts | 30 +- 4 files changed, 462 insertions(+), 287 deletions(-) delete mode 100644 patches/nuxt-ai-ready@2.4.1.patch diff --git a/patches/nuxt-ai-ready@2.4.1.patch b/patches/nuxt-ai-ready@2.4.1.patch deleted file mode 100644 index 325677a5..00000000 --- a/patches/nuxt-ai-ready@2.4.1.patch +++ /dev/null @@ -1,109 +0,0 @@ -diff --git a/dist/runtime/server/db/drizzle/queries.js b/dist/runtime/server/db/drizzle/queries.js -index 8c6ca0b7321ca15684f3c663f4e51fc00a054e08..d26418deabec180f17c38e8aa519778f2aa44d0d 100644 ---- a/dist/runtime/server/db/drizzle/queries.js -+++ b/dist/runtime/server/db/drizzle/queries.js -@@ -706,7 +706,6 @@ export async function seedRoutes(event, routes, options = {}) { - indexed = CASE WHEN ai_ready_pages.is_error = 1 THEN 0 ELSE ai_ready_pages.indexed END - WHERE ai_ready_pages.last_seen_at IS NULL - OR ai_ready_pages.last_seen_at < excluded.last_seen_at - ${refreshWindowMs} -- OR ai_ready_pages.is_error = 1 - OR ai_ready_pages.locale IS DISTINCT FROM excluded.locale - `, - params -diff --git a/dist/runtime/server/db/queries.js b/dist/runtime/server/db/queries.js -index 75b402435d2c6f95c9debd71e703fa2d3dfd2206..eccf0b3c4aee4ff1f5ce17c9d4ac0cd3d17cf3df 100644 ---- a/dist/runtime/server/db/queries.js -+++ b/dist/runtime/server/db/queries.js -@@ -380,7 +380,6 @@ export async function seedRoutes(event, routes, options = {}) { - indexed = CASE WHEN ai_ready_pages.is_error = 1 THEN 0 ELSE ai_ready_pages.indexed END - WHERE ai_ready_pages.last_seen_at IS NULL - OR ai_ready_pages.last_seen_at < excluded.last_seen_at - ${refreshWindowMs} -- OR ai_ready_pages.is_error = 1 - OR ai_ready_pages.locale IS DISTINCT FROM excluded.locale - `, - params -@@ -389,6 +388,12 @@ export async function seedRoutes(event, routes, options = {}) { - await db.batch(stmts); - return byRoute.size; - } -+export async function prunePage(event, route) { -+ const db = await getDb(event); -+ if (!db) -+ return; -+ await db.exec("DELETE FROM ai_ready_pages WHERE route = ?", [normalizeRoute(route)]); -+} - export async function pruneStaleRoutes(event, staleThresholdSeconds, protectedSince, refreshWindowMs = SEED_REFRESH_WINDOW_MS) { - const db = await getDb(event); - if (!db) -diff --git a/dist/runtime/server/utils/batchIndex.js b/dist/runtime/server/utils/batchIndex.js -index c760b5acf0d48c5b2f6057bf24456d1b434a98ab..bc04ae4d25e16c7700453d7329b9331f05672428 100644 ---- a/dist/runtime/server/utils/batchIndex.js -+++ b/dist/runtime/server/utils/batchIndex.js -@@ -36,12 +36,14 @@ export async function batchIndexPages(event, options = {}) { - markFailedAsError: true, - force: true - }); -- return { route: page.route, success: result.success }; -+ return { route: page.route, success: result.success, gone: result.gone === true }; - }) - ); - for (const result of results) { - processed++; -- if (result.success) { -+ if (result.gone) { -+ logger.debug(`[batchIndex] Pruned gone route: ${result.route}`); -+ } else if (result.success) { - indexed++; - logger.debug(`[batchIndex] Indexed: ${result.route}`); - } else { -diff --git a/dist/runtime/server/utils/indexPage.d.ts b/dist/runtime/server/utils/indexPage.d.ts -index 8464b6fc49b0542c253124187bd04ea7be9d474e..9f2b0535d21b88966c57be4f0d1562a66b569476 100644 ---- a/dist/runtime/server/utils/indexPage.d.ts -+++ b/dist/runtime/server/utils/indexPage.d.ts -@@ -30,6 +30,8 @@ export interface IndexPageResult { - }; - /** Error message if failed */ - error?: string; -+ /** True when the route answered 410 Gone and its row was pruned */ -+ gone?: boolean; - } - /** - * Manually index a page's HTML content into database -diff --git a/dist/runtime/server/utils/indexPage.js b/dist/runtime/server/utils/indexPage.js -index 62441b349d1c96199207b0b3dad3f2f58423d354..002479c038c303c6ee94c67a6efd8f72a21ebe99 100644 ---- a/dist/runtime/server/utils/indexPage.js -+++ b/dist/runtime/server/utils/indexPage.js -@@ -1,5 +1,5 @@ - import { fetchWithEvent, useNitroApp, useRuntimeConfig } from "#nuxtseo/nitro"; --import { getPageIndexState, upsertPage } from "../db/queries.js"; -+import { getPageIndexState, prunePage, upsertPage } from "../db/queries.js"; - import { computeContentHash } from "../db/shared.js"; - import { logger } from "../logger.js"; - import { convertHtmlToMarkdown } from "../utils.js"; -@@ -76,6 +76,7 @@ export async function indexPage(route, html, options = {}, event) { - } - export async function indexPageByRoute(route, event, options = {}) { - logger.debug(`[indexPageByRoute] Fetching HTML for ${route} (timeout: 10000ms)`); -+ let fetchStatus; - const html = await (event ? fetchWithEvent(event, route, { - headers: { accept: "text/html", [INTERNAL_HEADER]: "1" }, - timeout: 1e4 -@@ -84,6 +85,7 @@ export async function indexPageByRoute(route, event, options = {}) { - timeout: 1e4 - // 10s timeout per page (must fit within CF worker limit) - })).catch((err) => { -+ fetchStatus = err?.status ?? err?.statusCode ?? err?.response?.status; - logger.warn(`[indexPageByRoute] Failed to fetch ${route}:`, err.message); - return null; - }); -@@ -91,6 +93,10 @@ export async function indexPageByRoute(route, event, options = {}) { - logger.debug(`[indexPageByRoute] Fetched ${route} (${html.length} bytes)`); - } - if (!html || typeof html !== "string") { -+ if (fetchStatus === 410) { -+ await prunePage(event, route); -+ return { success: false, gone: true, error: `Route gone (410): ${route}` }; -+ } - if (options.markFailedAsError) { - await upsertPage(event, { - route, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46037f7b..fd51685e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -324,8 +324,8 @@ catalogs: specifier: 4.5.2 version: 4.5.2 nuxt-ai-ready: - specifier: ^2.4.1 - version: 2.4.1 + specifier: 2.5.2 + version: 2.5.2 nuxt-auth-utils: specifier: ^0.5.30 version: 0.5.30 @@ -385,7 +385,6 @@ catalogs: patchedDependencies: hookable@5.5.3: 3b44da13a743bfa2eab02aef7246065e4dfd56e9878c206f34b51322832cfb2c hookable@6.1.1: 7530364d493fa95257cb863d105ed55a1bb4b46d5c9a178a6d64cb73028ed344 - nuxt-ai-ready@2.4.1: c06f83cb214a4c2b4017b540ff07f2a358193c8a6800a10d385876428d54d1b9 importers: @@ -486,7 +485,7 @@ importers: version: 4.5.2(@babel/plugin-proposal-decorators@8.0.2)(@babel/plugin-syntax-jsx@7.29.7)(@babel/plugin-syntax-typescript@7.29.7)(@oxc-project/types@0.144.0)(@types/node@26.4.0)(@vue/compiler-sfc@3.5.43)(aws4fetch@1.0.20)(better-sqlite3@13.0.3)(commander@14.0.3)(db0@0.3.4(better-sqlite3@13.0.3))(esbuild@0.28.2)(eslint@10.9.1(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(optionator@0.9.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.4)(rollup@4.62.4))(rollup@4.62.4)(srvx@0.12.5)(supports-color@10.2.2)(terser@5.50.0)(tsx@4.23.13)(typescript@6.0.3)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))(vue-tsc@3.3.11(typescript@6.0.3))(yaml@2.9.1) nuxt-ai-ready: specifier: 'catalog:' - version: 2.4.1(patch_hash=c06f83cb214a4c2b4017b540ff07f2a358193c8a6800a10d385876428d54d1b9)(d6a41079a964711e4f325b563afdaa67) + version: 2.5.2(edd296e53195b3482bf40119e39b367d) nuxt-auth-utils: specifier: 'catalog:' version: 0.5.30(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) @@ -583,7 +582,7 @@ importers: version: 17.4.1 nuxt-skew-protection: specifier: 'catalog:' - version: 1.5.4(0eac537ebc754eb94db577118ac95f1d) + version: 1.5.4(73c74dfcf4ef4d62389bf3f7c2a3ab8c) tsx: specifier: 'catalog:' version: 4.23.13 @@ -2392,98 +2391,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@mdream/js@1.7.2': - resolution: {integrity: sha512-NiihQ8D6btMFN03ZRHdev/zXAQvyjf9joQsZRjwu3Z2nS9Dm03sofPWhHB5CiwKw2Z6bHdx5I9ShAgyUU6yElA==} - hasBin: true - - '@mdream/rust-android-arm-eabi@1.7.2': - resolution: {integrity: sha512-tMBfSk8EqrDaJ+pN9mmiQfEUTOFYe6zQ2f9LWlLaKULmStoYS+gFzet0kteoU61M8s34IvYTKiWxSMpIpKQQNg==} - engines: {node: '>= 22.0.0'} - cpu: [arm] - os: [android] - - '@mdream/rust-android-arm64@1.7.2': - resolution: {integrity: sha512-0d5qkx1NiQG1oD3Oq2Gx/8k3rQj4x6QF+LXAMyARVpCzyON5JgWwKxeqViky6GfXG42XhsYslZbSJ4cu+1H6KA==} - engines: {node: '>= 22.0.0'} - cpu: [arm64] - os: [android] - - '@mdream/rust-darwin-arm64@1.7.2': - resolution: {integrity: sha512-BdkFikEblaoJnghm8C525UR7b8TsjXDNOp1Cns9xWJS8VeDruxDdkCm/o86bLT86S6iELiMmY14cshRgui8yyA==} - engines: {node: '>= 22.0.0'} - cpu: [arm64] - os: [darwin] - - '@mdream/rust-darwin-x64@1.7.2': - resolution: {integrity: sha512-juV8Sl/+Q4/3divH9+PBEHBX/BWjl4nmeYB54OXPfp8tA//1zEG4PFIQLN1c6HG0wr0u3rCzvLboLBbD0QR/Pw==} - engines: {node: '>= 22.0.0'} - cpu: [x64] - os: [darwin] - - '@mdream/rust-freebsd-x64@1.7.2': - resolution: {integrity: sha512-WWFNP0kQB634SCchzTOhmyVPRXFQP6UjAMQGVDZ86+L6yAQ21fjOI2PpCfdc8UqcDxhH0t1FNfcA4yY3v2hd2g==} - engines: {node: '>= 22.0.0'} - cpu: [x64] - os: [freebsd] - - '@mdream/rust-linux-arm-gnueabihf@1.7.2': - resolution: {integrity: sha512-8cuTU4D9buahI8npETi51jBTJ3OYoVD8/PmZQRWWWaEBXhUpSdNLW8pVRoRiY+kg4/RGvwgXQC0e0QRG7T7HOw==} - engines: {node: '>= 22.0.0'} - cpu: [arm] - os: [linux] - - '@mdream/rust-linux-arm64-gnu@1.7.2': - resolution: {integrity: sha512-b4nrAmyeXvVdW0iw0wkuSzSho0EtNvtIrJ9neKD1h+CDm/qflOWRVl5P05fe2RbI/evWMmApM2pkhfBhRGuXjA==} - engines: {node: '>= 22.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@mdream/rust-linux-arm64-musl@1.7.2': - resolution: {integrity: sha512-Ui+2S8P//Dv2fYTXrwySPLxvvthyU8SFSwarSN0KH6hqv7PTiKmUeg1Q3AX3r5m3MU/1wdkccoVlgCZXBgPiUQ==} - engines: {node: '>= 22.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@mdream/rust-linux-x64-gnu@1.7.2': - resolution: {integrity: sha512-eUu1P5dJLAba8P3pjfGLWk/fzFfyQZjimSurKHHu5XuSrpCGwIov38ure4dZYEANy2LFSeG1FGCQQlAfz4mi/w==} - engines: {node: '>= 22.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@mdream/rust-linux-x64-musl@1.7.2': - resolution: {integrity: sha512-pTAlHifHWWsT3xaPCcLF+hcbx+vnCo0CagzWUg0QmCqG7VppcIccJx0NArwHXuXYhJ4m7P9/5FgyrVlvEv7Zfg==} - engines: {node: '>= 22.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@mdream/rust-wasm32-wasi@1.7.2': - resolution: {integrity: sha512-a4o7ne98w43UiH5shc8lu382kVrjzds3Mk6p5PDn8zKEmrnewGpOGWhoxQ8iC2ifszyLPMxcrCdX0KolTOiFaA==} - engines: {node: '>= 22.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@mdream/rust-win32-arm64-msvc@1.7.2': - resolution: {integrity: sha512-NrcmXQlWApsJNaeh+cXWceEgShD4xDU0hLHvQmnHFn1oWPG8mVMPq5ZpjkL2bnPmXP35g9QaiSst/ruM4zW04g==} - engines: {node: '>= 22.0.0'} - cpu: [arm64] - os: [win32] - - '@mdream/rust-win32-x64-msvc@1.7.2': - resolution: {integrity: sha512-EImOvcWaJljn1BqJr8FiXNoqWftlyEmy7JBDIdUMdcoZ51q2CrRgQfNJrviVehcaVsylBvBjTlpRLhRrhAMlCQ==} - engines: {node: '>= 22.0.0'} - cpu: [x64] - os: [win32] - '@modelcontextprotocol/client@2.0.0': resolution: {integrity: sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==} engines: {node: '>=20'} @@ -2819,6 +2726,14 @@ packages: zod: optional: true + '@nuxtjs/robots@6.2.4': + resolution: {integrity: sha512-+MyHUT6fAqJHnGPxzSrPgXCp+giH04A9aS4RwSvjgHDzYQ+xOqpaSy8jb7U7sPlXmPZE2Et3ened9ccfmwGPKA==} + peerDependencies: + zod: '>=3' + peerDependenciesMeta: + zod: + optional: true + '@nuxtjs/seo@5.3.16': resolution: {integrity: sha512-qXFN2xQieTLJLISYcZ1znPZcXVfJrsJyv6WSKKZjHCGGfEv5Q/TTOVmrrKxjhsBiKAXRHoX/YOewnzR2mUI5rw==} peerDependencies: @@ -2833,6 +2748,15 @@ packages: zod: optional: true + '@nuxtjs/sitemap@8.6.1': + resolution: {integrity: sha512-8tjWb0poZZSRUkJG9AICPn5TskHyxVRQDH9T8PXD1LCcDYuTqPbsJ7tB0qwbS7OFrTHQHcBWcnHX7MNdgLO77Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: '>=3' + peerDependenciesMeta: + zod: + optional: true + '@one-ini/wasm@0.2.1': resolution: {integrity: sha512-TUqERXGNTifZ9y2g3wPxQrw3HpHv/02DsW3D90T9x0hhonrL1ZqpSmNrU2XkoIq0fP1N6gZfVQzy2Fw1ZvGBNg==} @@ -2850,6 +2774,12 @@ packages: cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm-eabi@0.141.0': + resolution: {integrity: sha512-jk7086MFvR/T4DG9IY7MKBVt1PMxvSZoz/TvnifodvS0pjghVwJHRttnAExhlwdMOgHv1TmLdENnbNpYk2zjvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm-eabi@0.143.0': resolution: {integrity: sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2868,6 +2798,12 @@ packages: cpu: [arm64] os: [android] + '@oxc-parser/binding-android-arm64@0.141.0': + resolution: {integrity: sha512-a4XDQ27ZT7e7zwAlxJDTiCA7IBGWDuy2+MhFq85Of7XlBSmpkfcBFml11q0Zx6f7RMuI0B4xCtt2ytBS4yOptg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-android-arm64@0.143.0': resolution: {integrity: sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2886,6 +2822,12 @@ packages: cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.141.0': + resolution: {integrity: sha512-m/kVk6rzYmBeHYnz+1Y5fod00AVTTxMbC71azFfm/zjx1j9XxwKtA0+VfkKuVMC8rbghb9TtfevnuWZa9OuPEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.143.0': resolution: {integrity: sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2904,6 +2846,12 @@ packages: cpu: [x64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.141.0': + resolution: {integrity: sha512-o0X+6KZlfucWU/v5oKRQPwdFXsXAjW8jmpo/Gpw/qyKsbKtlfkHoeH9Bjp/m13TwjewvJnCkwF0DWzgpC4HjTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.143.0': resolution: {integrity: sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2922,6 +2870,12 @@ packages: cpu: [x64] os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.141.0': + resolution: {integrity: sha512-W5KbTnNkTMMMylqj6dYqnsXvkmESVPodPKYLJ5zdzIPdl9fUJtolkpUeSzYEbGGYB4a4A4avl3EePnZ/wLIdJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.143.0': resolution: {integrity: sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2940,6 +2894,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.141.0': + resolution: {integrity: sha512-g3dtbJa8zeOGK36Sr9cQavsdi5H/ie2hVjrSjIxsNAR1qZA40ZYVXnfdfoMAlq8CmB9qFL1yhsSCUHeNmdmt8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': resolution: {integrity: sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2958,6 +2918,12 @@ packages: cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.141.0': + resolution: {integrity: sha512-e6hwQqd+3lvP13G2jxvFpoA7dzHcFLN+Mq47JCVMtdNHbbyBRo756JCtbbJH6ca8inTfyqZoqBmS3vhQlzAK2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': resolution: {integrity: sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2977,6 +2943,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.141.0': + resolution: {integrity: sha512-vXz2BLAuypA+4MLyBg94pzEo6THVnzYnCtAjXoihIIQo0t2pnp/AmW+SH1EI+4VbuJnC//KplIJ5yyaCGua4jA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.143.0': resolution: {integrity: sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2998,6 +2971,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.141.0': + resolution: {integrity: sha512-jMkS/EztNW34HKsXIaT/SoHcmtocq/vWhwFOVduF9kduuuRIVwfwQ6uxzIO+qPKSXdd2TXt54of0BJ2zFMXnmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.143.0': resolution: {integrity: sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3019,6 +2999,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.141.0': + resolution: {integrity: sha512-vo+MR+n3zQJ6Mq92hiP084NZcgDv5iJlVR02gMf28neMvVT1tKVm7VeiW/DxhdqOi3QLeaXIk9cUcLL1qrkngw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': resolution: {integrity: sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3040,6 +3027,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.141.0': + resolution: {integrity: sha512-oh80w+7RuiO5gBp9Jnoa/H8Qlt3JsHL2MkW+0dwEdlDMdslVZX/YsekSK6EeyEenY66/mhCfypsNATQ7Ph3qlQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': resolution: {integrity: sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3061,6 +3055,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.141.0': + resolution: {integrity: sha512-LOyEmFA8sCnYbEXP1+iQvCC/P1YXHMA/t6x1Ksp0Y9VwhLFsiBJFzV1zIxrOIE2LKaGGhDjQ29xq9cbq6omDXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.143.0': resolution: {integrity: sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3082,6 +3083,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.141.0': + resolution: {integrity: sha512-3wnwk/l1CvszVE5TJR1wSl/zSEfydRqrNhn6s7Vr9IzSJpUQIroqVsIoPARHRFA+FQwkxAFDAHDAasa7v8OobQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.143.0': resolution: {integrity: sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3103,6 +3111,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.141.0': + resolution: {integrity: sha512-qtyQVAAebFq57B2tifTlel3TgGqUtsYNI/e+p6aya9rN9lOZVTDvr215fGYSA9XWooxzMxDiVxkBLk2jQHbsOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.143.0': resolution: {integrity: sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3124,6 +3139,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.141.0': + resolution: {integrity: sha512-SkGV1nKw40roEc94pv5EaaeH2ay14G6+roe8Q0wIUC1LcEKxzKW921h7+ZuZX0D3q2Mb/7aSFmxEVqnko3lPRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.143.0': resolution: {integrity: sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3144,6 +3166,12 @@ packages: cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.141.0': + resolution: {integrity: sha512-cVgDM7n8QziQqOaP5hNgUYfMG7S/ZeuPxFWXnnHRv7rh025COk0rfQ6eEdKG3j/GaUuyvNZN4ifF1J8KmuXLLA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.143.0': resolution: {integrity: sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3161,12 +3189,23 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.141.0': + resolution: {integrity: sha512-HggH++Fkn3OilBn+bs3jpgIFQa34oMAyUUHy0vpGum+gt1Eb5nyLc8dNU/RAPSw6lsLrx7ncKtHSZE+3Sp0l2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-parser/binding-win32-arm64-msvc@0.140.0': resolution: {integrity: sha512-fGSQldwEYKhM+H8uLt76Op8hh5+FYaR6lvvQ1Txw3Mhn86DyQXLcI0fi1EkFlTK7F+46OCk/j0AJMzZQm6g5Xg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.141.0': + resolution: {integrity: sha512-KLSEH9GwgbrqbJOjtGHt9STw96s+78yDzp7IDN8Lno+7Ut9sNBfZ4jYZIz4mD50qmWUjoOI7i9I6UENbhNbMZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.143.0': resolution: {integrity: sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3185,6 +3224,12 @@ packages: cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.141.0': + resolution: {integrity: sha512-9UVWUOOCI/1YkiSSNjg2zyBJYM9E/t1A/8GNobd48JDn/fQ6mzxcVO3H08jb3rAaW/B1VBf8eCORTvSsO9T08g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.143.0': resolution: {integrity: sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3203,6 +3248,12 @@ packages: cpu: [x64] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.141.0': + resolution: {integrity: sha512-HI/wsvbWT5RHHw5c37D0fEgeTd8/1Q4OJs5jUmEBc17VZFG6SsCIe4barq7NsAPPks/JW+3ayi3Rp+PQI5h4Kg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.143.0': resolution: {integrity: sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3218,6 +3269,9 @@ packages: '@oxc-project/types@0.140.0': resolution: {integrity: sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==} + '@oxc-project/types@0.141.0': + resolution: {integrity: sha512-S4as7z0j0xQkXcJlyY5ehntwK8/wRkQb9Cyqw+J/N2rkWGQGK0SxD6X6DhQTc7qsxVTBxXbxZtBJh3mr3PtIzQ==} + '@oxc-project/types@0.143.0': resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} @@ -7097,10 +7151,6 @@ packages: mdn-data@2.29.0: resolution: {integrity: sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==} - mdream@1.7.2: - resolution: {integrity: sha512-0cXfvkNSZVF0sd4emNC30kxQarb7/0Ib8xm0iL9Y5RGXuBBDCDPJDpWpSnUO6HBin7GMGOvrXP5IWdf/BOkLyQ==} - hasBin: true - mdurl@2.1.0: resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} @@ -7404,13 +7454,12 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nuxt-ai-ready@2.4.1: - resolution: {integrity: sha512-RHNm/+aj56eSw8fGzuwbZoDyTRA4qLFtx0RiRc4g5ME25GTKo6YNN4b1YuuwWs99Z8W6TyYdshHDclvinJAnSA==} + nuxt-ai-ready@2.5.2: + resolution: {integrity: sha512-dabw3E2Yi4/nNsMGNGlgUGg5PAqUPXsScTxjUx+uOMLzDD5DhcbJxxmb0WUoMXv4PumjkSgJ2fB0cOzwe0PAhA==} hasBin: true peerDependencies: '@libsql/client': ^0.18.0 '@neondatabase/serverless': ^1.0.0 - '@nuxtjs/sitemap': '>=8.3.1' better-sqlite3: ^11.0.0 || ^12.0.0 || ^13.0.0 postgres: ^3.4.9 peerDependenciesMeta: @@ -7422,6 +7471,9 @@ packages: optional: true postgres: optional: true + bundledDependencies: + - mdream + - '@mdream/js' nuxt-auth-utils@0.5.30: resolution: {integrity: sha512-KtHMHy/OQAy/RYAmORBDvtNRs0BYbeLQOOrMv0FpBpWUYqMBA2lhumqnTACljFHGLGNpMy25Ck7cWVhVUjc6vA==} @@ -7654,6 +7706,10 @@ packages: resolution: {integrity: sha512-h6QFWd6lBMfjESqgQ27GjzrSDb0qbznp7VDQqp2zvgsrWut4vcchyMIzOVXvGQ2GMZgKw9RWrFNWv9WqGL0p7Q==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.141.0: + resolution: {integrity: sha512-uFkGGr1KMWd6aWv9UAqooYrN78trw8MWWmoPvgWokfBEUq1+eiIQ+qfj3wokhy0fxtZWZk+0dHoS7/yRTJtd6w==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.143.0: resolution: {integrity: sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -11066,50 +11122,6 @@ snapshots: - encoding - supports-color - '@mdream/js@1.7.2': - dependencies: - cac: 7.0.0 - pathe: 2.0.3 - - '@mdream/rust-android-arm-eabi@1.7.2': - optional: true - - '@mdream/rust-android-arm64@1.7.2': - optional: true - - '@mdream/rust-darwin-arm64@1.7.2': - optional: true - - '@mdream/rust-darwin-x64@1.7.2': - optional: true - - '@mdream/rust-freebsd-x64@1.7.2': - optional: true - - '@mdream/rust-linux-arm-gnueabihf@1.7.2': - optional: true - - '@mdream/rust-linux-arm64-gnu@1.7.2': - optional: true - - '@mdream/rust-linux-arm64-musl@1.7.2': - optional: true - - '@mdream/rust-linux-x64-gnu@1.7.2': - optional: true - - '@mdream/rust-linux-x64-musl@1.7.2': - optional: true - - '@mdream/rust-wasm32-wasi@1.7.2': - optional: true - - '@mdream/rust-win32-arm64-msvc@1.7.2': - optional: true - - '@mdream/rust-win32-x64-msvc@1.7.2': - optional: true - '@modelcontextprotocol/client@2.0.0': dependencies: '@modelcontextprotocol/core': 2.0.0 @@ -11307,6 +11319,18 @@ snapshots: - rolldown - unplugin + '@nuxt/devtools-kit@4.0.0-alpha.7(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))': + dependencies: + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + tinyexec: 1.3.0 + vite: 8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1) + transitivePeerDependencies: + - magic-string + - magicast + - oxc-parser + - rolldown + - unplugin + '@nuxt/devtools-kit@4.0.0-alpha.7(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))': dependencies: '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) @@ -11693,6 +11717,36 @@ snapshots: - rolldown - unplugin + '@nuxt/kit@4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))': + dependencies: + c12: 3.3.4(magicast@0.5.4) + consola: 3.4.2 + defu: 6.1.7 + destr: 2.0.5 + errx: 0.1.2 + exsolve: 1.1.1 + ignore: 7.0.6 + jiti: 2.7.0 + klona: 2.0.6 + mlly: 1.8.2 + nostics: 1.2.0 + ohash: 2.0.12 + pathe: 2.0.3 + pkg-types: 2.3.1 + rc9: 3.0.1 + scule: 1.3.0 + tinyglobby: 0.2.17 + ufo: 1.6.4 + unctx: 3.0.0(magic-string@1.2.3)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + untyped: 2.0.0 + verkit: 0.3.2 + transitivePeerDependencies: + - magic-string + - magicast + - oxc-parser + - rolldown + - unplugin + '@nuxt/kit@4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))': dependencies: c12: 3.3.4(magicast@0.5.4) @@ -12302,6 +12356,31 @@ snapshots: - vite - vue + '@nuxtjs/robots@6.2.4(cf966cf814271844d225a11e31c3c432)': + dependencies: + '@fingerprintjs/botd': 2.0.0 + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + consola: 3.4.2 + defu: 6.1.7 + h3: 1.15.11 + nuxt-site-config: 4.2.3(cf966cf814271844d225a11e31c3c432) + nuxtseo-shared: 5.3.16(c7438970c0cb6fde15f5a71b3137c18e) + pathe: 2.0.3 + pkg-types: 2.3.3 + ufo: 1.6.4 + optionalDependencies: + zod: 4.5.4 + transitivePeerDependencies: + - '@nuxt/schema' + - magic-string + - magicast + - nuxt + - oxc-parser + - rolldown + - unplugin + - vite + - vue + '@nuxtjs/seo@5.3.16(00327d07b55d03f13ed8bf1111d9fed9)': dependencies: '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) @@ -12399,6 +12478,35 @@ snapshots: - vite - vue + '@nuxtjs/sitemap@8.6.1(6ac0989e72bc69304d32b666ee169f24)': + dependencies: + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + consola: 3.4.2 + defu: 6.1.7 + nuxt-site-config: 4.2.3(1d22eddfda677e8c568f08cb58c75d5f) + nuxtseo-shared: 5.3.16(2b0dfa7bc788f3b7a134f416fa102aba) + ofetch: 1.5.1 + oxc-parser: 0.141.0 + oxc-walker: 1.1.1(@oxc-project/types@0.144.0)(oxc-parser@0.141.0)(rolldown@1.2.4) + pathe: 2.0.3 + pkg-types: 2.3.3 + radix3: 1.1.2 + sitemapd: 0.2.2 + ufo: 1.6.4 + ultrahtml: 1.7.0 + optionalDependencies: + zod: 4.5.4 + transitivePeerDependencies: + - '@nuxt/schema' + - '@oxc-project/types' + - magic-string + - magicast + - nuxt + - rolldown + - unplugin + - vite + - vue + '@one-ini/wasm@0.2.1': {} '@opentelemetry/api@1.9.1': {} @@ -12408,6 +12516,9 @@ snapshots: '@oxc-parser/binding-android-arm-eabi@0.140.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.141.0': + optional: true + '@oxc-parser/binding-android-arm-eabi@0.143.0': optional: true @@ -12417,6 +12528,9 @@ snapshots: '@oxc-parser/binding-android-arm64@0.140.0': optional: true + '@oxc-parser/binding-android-arm64@0.141.0': + optional: true + '@oxc-parser/binding-android-arm64@0.143.0': optional: true @@ -12426,6 +12540,9 @@ snapshots: '@oxc-parser/binding-darwin-arm64@0.140.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.141.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.143.0': optional: true @@ -12435,6 +12552,9 @@ snapshots: '@oxc-parser/binding-darwin-x64@0.140.0': optional: true + '@oxc-parser/binding-darwin-x64@0.141.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.143.0': optional: true @@ -12444,6 +12564,9 @@ snapshots: '@oxc-parser/binding-freebsd-x64@0.140.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.141.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.143.0': optional: true @@ -12453,6 +12576,9 @@ snapshots: '@oxc-parser/binding-linux-arm-gnueabihf@0.140.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.141.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.143.0': optional: true @@ -12462,6 +12588,9 @@ snapshots: '@oxc-parser/binding-linux-arm-musleabihf@0.140.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.141.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.143.0': optional: true @@ -12471,6 +12600,9 @@ snapshots: '@oxc-parser/binding-linux-arm64-gnu@0.140.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.141.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.143.0': optional: true @@ -12480,6 +12612,9 @@ snapshots: '@oxc-parser/binding-linux-arm64-musl@0.140.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.141.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.143.0': optional: true @@ -12489,6 +12624,9 @@ snapshots: '@oxc-parser/binding-linux-ppc64-gnu@0.140.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.141.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.143.0': optional: true @@ -12498,6 +12636,9 @@ snapshots: '@oxc-parser/binding-linux-riscv64-gnu@0.140.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.141.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.143.0': optional: true @@ -12507,6 +12648,9 @@ snapshots: '@oxc-parser/binding-linux-riscv64-musl@0.140.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.141.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.143.0': optional: true @@ -12516,6 +12660,9 @@ snapshots: '@oxc-parser/binding-linux-s390x-gnu@0.140.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.141.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.143.0': optional: true @@ -12525,6 +12672,9 @@ snapshots: '@oxc-parser/binding-linux-x64-gnu@0.140.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.141.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.143.0': optional: true @@ -12534,6 +12684,9 @@ snapshots: '@oxc-parser/binding-linux-x64-musl@0.140.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.141.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.143.0': optional: true @@ -12543,6 +12696,9 @@ snapshots: '@oxc-parser/binding-openharmony-arm64@0.140.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.141.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.143.0': optional: true @@ -12556,9 +12712,19 @@ snapshots: '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) optional: true + '@oxc-parser/binding-wasm32-wasi@0.141.0': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.140.0': optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.141.0': + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.143.0': optional: true @@ -12568,6 +12734,9 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc@0.140.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.141.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.143.0': optional: true @@ -12577,6 +12746,9 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.140.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.141.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.143.0': optional: true @@ -12585,6 +12757,8 @@ snapshots: '@oxc-project/types@0.140.0': {} + '@oxc-project/types@0.141.0': {} + '@oxc-project/types@0.143.0': {} '@oxc-project/types@0.144.0': {} @@ -16355,22 +16529,6 @@ snapshots: mdn-data@2.29.0: {} - mdream@1.7.2: - optionalDependencies: - '@mdream/rust-android-arm-eabi': 1.7.2 - '@mdream/rust-android-arm64': 1.7.2 - '@mdream/rust-darwin-arm64': 1.7.2 - '@mdream/rust-darwin-x64': 1.7.2 - '@mdream/rust-freebsd-x64': 1.7.2 - '@mdream/rust-linux-arm-gnueabihf': 1.7.2 - '@mdream/rust-linux-arm64-gnu': 1.7.2 - '@mdream/rust-linux-arm64-musl': 1.7.2 - '@mdream/rust-linux-x64-gnu': 1.7.2 - '@mdream/rust-linux-x64-musl': 1.7.2 - '@mdream/rust-wasm32-wasi': 1.7.2 - '@mdream/rust-win32-arm64-msvc': 1.7.2 - '@mdream/rust-win32-x64-msvc': 1.7.2 - mdurl@2.1.0: {} media-typer@1.1.1: {} @@ -16878,16 +17036,15 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt-ai-ready@2.4.1(patch_hash=c06f83cb214a4c2b4017b540ff07f2a358193c8a6800a10d385876428d54d1b9)(d6a41079a964711e4f325b563afdaa67): + nuxt-ai-ready@2.5.2(edd296e53195b3482bf40119e39b367d): dependencies: - '@mdream/js': 1.7.2 '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) - '@nuxtjs/sitemap': 8.5.0(cf966cf814271844d225a11e31c3c432) + '@nuxtjs/robots': 6.2.4(cf966cf814271844d225a11e31c3c432) + '@nuxtjs/sitemap': 8.6.1(6ac0989e72bc69304d32b666ee169f24) citty: 0.2.2 consola: 3.4.2 defu: 6.1.7 drizzle-orm: 1.0.0-rc.4(@cloudflare/workers-types@5.20260901.1)(@opentelemetry/api@1.9.1)(better-sqlite3@13.0.3)(valibot@1.4.2(typescript@6.0.3))(zod@4.5.4) - mdream: 1.7.2 nuxt-site-config: 4.2.3(cf966cf814271844d225a11e31c3c432) nuxtseo-shared: 5.3.16(c7438970c0cb6fde15f5a71b3137c18e) pathe: 2.0.3 @@ -16916,6 +17073,7 @@ snapshots: - '@nuxt/schema' - '@op-engineering/op-sqlite' - '@opentelemetry/api' + - '@oxc-project/types' - '@planetscale/database' - '@sinclair/typebox' - '@sqlitecloud/drivers' @@ -17144,6 +17302,20 @@ snapshots: - vue - zod + nuxt-site-config-kit@4.2.3(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))(vue@3.5.43(typescript@6.0.3)): + dependencies: + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + site-config-stack: 4.2.3(vue@3.5.43(typescript@6.0.3)) + std-env: 4.2.0 + ufo: 1.6.4 + transitivePeerDependencies: + - magic-string + - magicast + - oxc-parser + - rolldown + - unplugin + - vue + nuxt-site-config-kit@4.2.3(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))(vue@3.5.43(typescript@6.0.3)): dependencies: '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) @@ -17172,6 +17344,31 @@ snapshots: - unplugin - vue + nuxt-site-config@4.2.3(1d22eddfda677e8c568f08cb58c75d5f): + dependencies: + '@nuxt/devalue': 2.0.2 + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + consola: 3.4.2 + defu: 6.1.7 + h3: 1.15.11 + nuxt-site-config-kit: 4.2.3(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))(vue@3.5.43(typescript@6.0.3)) + nuxtseo-shared: 5.3.16(2b0dfa7bc788f3b7a134f416fa102aba) + pathe: 2.0.3 + pkg-types: 2.3.3 + site-config-stack: 4.2.3(vue@3.5.43(typescript@6.0.3)) + ufo: 1.6.4 + vue: 3.5.43(typescript@6.0.3) + transitivePeerDependencies: + - '@nuxt/schema' + - magic-string + - magicast + - nuxt + - oxc-parser + - rolldown + - unplugin + - vite + - zod + nuxt-site-config@4.2.3(ad58215c2b7b9c07fe45270ca880bc74): dependencies: '@nuxt/devalue': 2.0.2 @@ -17222,10 +17419,10 @@ snapshots: - vite - zod - nuxt-skew-protection@1.5.4(0eac537ebc754eb94db577118ac95f1d): + nuxt-skew-protection@1.5.4(73c74dfcf4ef4d62389bf3f7c2a3ab8c): dependencies: '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) - '@nuxtjs/robots': 6.2.0(cf966cf814271844d225a11e31c3c432) + '@nuxtjs/robots': 6.2.4(cf966cf814271844d225a11e31c3c432) '@vueuse/core': 14.4.0(vue@3.5.43(typescript@6.0.3)) consola: 3.4.2 cookie-es: 3.1.1 @@ -17408,6 +17605,36 @@ snapshots: - xml2js - yaml + nuxtseo-shared@5.3.16(2b0dfa7bc788f3b7a134f416fa102aba): + dependencies: + '@clack/prompts': 1.7.0 + '@nuxt/devtools-kit': 4.0.0-alpha.7(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)))(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)) + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))) + '@nuxt/schema': 4.5.2 + birpc: 4.1.0 + consola: 3.4.2 + defu: 6.1.7 + nuxt: 4.5.2(@babel/plugin-proposal-decorators@8.0.2)(@babel/plugin-syntax-jsx@7.29.7)(@babel/plugin-syntax-typescript@7.29.7)(@oxc-project/types@0.144.0)(@types/node@26.4.0)(@vue/compiler-sfc@3.5.43)(aws4fetch@1.0.20)(better-sqlite3@13.0.3)(commander@14.0.3)(db0@0.3.4(better-sqlite3@13.0.3))(esbuild@0.28.2)(eslint@10.9.1(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(optionator@0.9.4)(oxc-parser@0.143.0)(rolldown@1.2.4)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.4)(rollup@4.62.4))(rollup@4.62.4)(srvx@0.12.5)(supports-color@10.2.2)(terser@5.50.0)(tsx@4.23.13)(typescript@6.0.3)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))(vue-tsc@3.3.11(typescript@6.0.3))(yaml@2.9.1) + nypm: 0.6.9 + ofetch: 1.5.1 + pathe: 2.0.3 + pkg-types: 2.3.3 + radix3: 1.1.2 + sirv: 3.0.2 + std-env: 4.2.0 + ufo: 1.6.4 + vue: 3.5.43(typescript@6.0.3) + optionalDependencies: + nuxt-site-config: 4.2.3(cf966cf814271844d225a11e31c3c432) + zod: 4.5.4 + transitivePeerDependencies: + - magic-string + - magicast + - oxc-parser + - rolldown + - unplugin + - vite + nuxtseo-shared@5.3.16(aa6b2278834f668f00ca4bca18a50fd1): dependencies: '@clack/prompts': 1.7.0 @@ -17566,6 +17793,31 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.140.0 '@oxc-parser/binding-win32-x64-msvc': 0.140.0 + oxc-parser@0.141.0: + dependencies: + '@oxc-project/types': 0.141.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.141.0 + '@oxc-parser/binding-android-arm64': 0.141.0 + '@oxc-parser/binding-darwin-arm64': 0.141.0 + '@oxc-parser/binding-darwin-x64': 0.141.0 + '@oxc-parser/binding-freebsd-x64': 0.141.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.141.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.141.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.141.0 + '@oxc-parser/binding-linux-arm64-musl': 0.141.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.141.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.141.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.141.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.141.0 + '@oxc-parser/binding-linux-x64-gnu': 0.141.0 + '@oxc-parser/binding-linux-x64-musl': 0.141.0 + '@oxc-parser/binding-openharmony-arm64': 0.141.0 + '@oxc-parser/binding-wasm32-wasi': 0.141.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.141.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.141.0 + '@oxc-parser/binding-win32-x64-msvc': 0.141.0 + oxc-parser@0.143.0: dependencies: '@oxc-project/types': 0.143.0 @@ -17620,6 +17872,12 @@ snapshots: oxc-parser: 0.140.0 rolldown: 1.2.4 + oxc-walker@1.1.1(@oxc-project/types@0.144.0)(oxc-parser@0.141.0)(rolldown@1.2.4): + optionalDependencies: + '@oxc-project/types': 0.144.0 + oxc-parser: 0.141.0 + rolldown: 1.2.4 + oxc-walker@1.1.1(@oxc-project/types@0.144.0)(oxc-parser@0.143.0)(rolldown@1.2.4): optionalDependencies: '@oxc-project/types': 0.144.0 @@ -18778,6 +19036,13 @@ snapshots: rolldown: 1.2.4 unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)) + unctx@3.0.0(magic-string@1.2.3)(oxc-parser@0.141.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))): + optionalDependencies: + magic-string: 1.2.3 + oxc-parser: 0.141.0 + rolldown: 1.2.4 + unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1)) + unctx@3.0.0(magic-string@1.2.3)(oxc-parser@0.143.0)(rolldown@1.2.4)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.4)(rollup@4.62.4)(vite@8.2.1(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.13)(yaml@2.9.1))): optionalDependencies: magic-string: 1.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a5dfd892..08f8a0d0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -86,7 +86,7 @@ minimumReleaseAgeExclude: - '@sentry/server-utils@11.1.0' - '@sentry/vue@11.1.0' - motion-v@2.5.1 - - nuxt-ai-ready@2.4.1 + - nuxt-ai-ready@2.5.2 shellEmulator: true autoInstallPeers: false @@ -178,7 +178,6 @@ pipelines: patchedDependencies: hookable@5.5.3: patches/hookable@5.5.3.patch hookable@6.1.1: patches/hookable@6.1.1.patch - nuxt-ai-ready@2.4.1: patches/nuxt-ai-ready@2.4.1.patch catalog: '@antfu/eslint-config': ^9.3.0 '@cloudflare/sandbox': 1.0.0 @@ -233,7 +232,7 @@ catalog: marked: ^18.0.11 motion-v: ^2.5.1 nuxt: 4.5.2 - nuxt-ai-ready: ^2.4.1 + nuxt-ai-ready: 2.5.2 nuxt-auth-utils: ^0.5.30 nuxt-og-image: ^6.9.1 nuxt-skew-protection: ^1.5.4 diff --git a/test/unit/ai-ready-gone-route.test.ts b/test/unit/ai-ready-gone-route.test.ts index c0f92378..14765333 100644 --- a/test/unit/ai-ready-gone-route.test.ts +++ b/test/unit/ai-ready-gone-route.test.ts @@ -5,12 +5,11 @@ import { DatabaseSync } from 'node:sqlite' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' /** - * The pinned nuxt-ai-ready (2.4.1, patched in patches/) must treat a 410 + * The released nuxt-ai-ready runtime must treat a 410 * route as terminal: the page row is pruned and the cron index result stays * error-free, instead of the seed re-arming the failed row every crawl. * - * Regression net for the patch: a catalog pin bump without a matching patch - * would silently restore the re-arm and fail these tests. + * Keep this regression coverage when updating the package catalog pin. */ const mocks = vi.hoisted(() => ({ @@ -40,6 +39,11 @@ vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/drizzle/raw.js' useRawDb: mocks.useRawDb, })) +// Build dump restoration is a separate boundary from route indexing. +vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/checkStale.js', () => ({ + checkAndHandleStale: async () => ({ action: 'none' }), +})) + vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/logger.js', () => ({ logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }, })) @@ -55,6 +59,7 @@ vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils.js', () => ( const { seedRoutes } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/queries.js') const { batchIndexPages } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/batchIndex.js') +const { indexPageByRoute } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/indexPage.js') const HOUR = 60 * 60 * 1000 const T0 = Date.UTC(2026, 9, 1) @@ -95,7 +100,7 @@ function goneFetchError(status: number): Error { }) } -describe('ai-ready gone route (patched nuxt-ai-ready)', () => { +describe('ai-ready gone route', () => { let sqlite: DatabaseSync beforeEach(() => { @@ -119,7 +124,7 @@ describe('ai-ready gone route (patched nuxt-ai-ready)', () => { mocks.fetchWithEvent.mockReset() mocks.runtimeConfig['nuxt-ai-ready'] = { debug: false, - database: { type: 'd1' }, + database: { _tag: 'Enabled', type: 'd1' }, runtimeSync: { enabled: true, ttl: 3600, batchSize: 10 }, } }) @@ -137,6 +142,21 @@ describe('ai-ready gone route (patched nuxt-ai-ready)', () => { return sqlite.prepare('SELECT is_error, indexed FROM ai_ready_pages WHERE route = ?').get(route) as { is_error: number, indexed: number } | undefined } + it.each([ + { status: 410 }, + { statusCode: 410 }, + { response: { status: 410 } }, + ])('reports a gone route without an indexing error for %j', async (status) => { + await seedRoutes(event, ['/gone']) + mocks.fetchWithEvent.mockRejectedValue(Object.assign(new Error('Gone'), status)) + + expect(await indexPageByRoute('/gone', event, { markFailedAsError: true })).toEqual({ + success: false, + gone: true, + }) + expect(rowCount()).toBe(0) + }) + it('does not re-arm a failed row inside the seed refresh window', async () => { await seedRoutes(event, ['/failed']) mocks.fetchWithEvent.mockRejectedValue(goneFetchError(500)) From e35549ee154efebe2cd4897524094990a4cad0b1 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 5 Oct 2026 15:05:53 +1100 Subject: [PATCH 3/3] test(indexing): drop consumer runtime regression fixtures --- test/fixtures/ai-ready-h3-shim.ts | 7 - test/fixtures/ai-ready-i18n-runtime-stub.ts | 15 -- test/fixtures/ai-ready-nitro-shim.ts | 27 --- test/fixtures/ai-ready-read-page-data-stub.ts | 10 - test/unit/ai-ready-gone-route.test.ts | 201 ------------------ vitest.config.ts | 12 +- 6 files changed, 1 insertion(+), 271 deletions(-) delete mode 100644 test/fixtures/ai-ready-h3-shim.ts delete mode 100644 test/fixtures/ai-ready-i18n-runtime-stub.ts delete mode 100644 test/fixtures/ai-ready-nitro-shim.ts delete mode 100644 test/fixtures/ai-ready-read-page-data-stub.ts delete mode 100644 test/unit/ai-ready-gone-route.test.ts diff --git a/test/fixtures/ai-ready-h3-shim.ts b/test/fixtures/ai-ready-h3-shim.ts deleted file mode 100644 index 063022da..00000000 --- a/test/fixtures/ai-ready-h3-shim.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Stand-in for the `#nuxtseo/h3` virtual the nuxt-ai-ready module injects at - * build time. Tests that load the module's runtime dist files directly alias - * this specifier here; the module's own runtime API is h3's, so re-export it. - */ - -export * from 'h3' diff --git a/test/fixtures/ai-ready-i18n-runtime-stub.ts b/test/fixtures/ai-ready-i18n-runtime-stub.ts deleted file mode 100644 index 0665a3e6..00000000 --- a/test/fixtures/ai-ready-i18n-runtime-stub.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Stand-in for the `#ai-ready-virtual/i18n-runtime.mjs` virtual the - * nuxt-ai-ready module generates from nuxtseo-shared at build time. Locale - * resolution only runs when the module's i18n config is set, which tests - * loading the runtime dist files don't do; the exports throw so that stays - * true loudly. - */ - -export function resolveLocaleFromRoute(): never { - throw new Error('#ai-ready-virtual/i18n-runtime.mjs stub: i18n is not available in tests') -} - -export function resolveI18nDomain(): never { - throw new Error('#ai-ready-virtual/i18n-runtime.mjs stub: i18n is not available in tests') -} diff --git a/test/fixtures/ai-ready-nitro-shim.ts b/test/fixtures/ai-ready-nitro-shim.ts deleted file mode 100644 index 5376a522..00000000 --- a/test/fixtures/ai-ready-nitro-shim.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Stand-in for the `#nuxtseo/nitro` virtual the nuxt-ai-ready module injects - * at build time. Tests that load the module's runtime dist files directly - * (node environment, no Nuxt build) alias this specifier here and mock it - * with `vi.mock`. Nothing in the suite imports the real functions, so every - * export throws to fail loud if that ever changes. - */ - -export function useRuntimeConfig(): never { - throw new Error('#nuxtseo/nitro shim: useRuntimeConfig must be mocked') -} - -export function useEvent(): never { - throw new Error('#nuxtseo/nitro shim: useEvent must be mocked') -} - -export function useNitroApp(): never { - throw new Error('#nuxtseo/nitro shim: useNitroApp must be mocked') -} - -export async function fetchWithEvent( - _event: unknown, - request: RequestInfo | URL, - init?: RequestInit, -): Promise { - return globalThis.fetch(request, init) -} diff --git a/test/fixtures/ai-ready-read-page-data-stub.ts b/test/fixtures/ai-ready-read-page-data-stub.ts deleted file mode 100644 index bde6280c..00000000 --- a/test/fixtures/ai-ready-read-page-data-stub.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Stand-in for the `#ai-ready-virtual/read-page-data.mjs` virtual the - * nuxt-ai-ready module generates at build time. It only serves the prerender - * database path, which tests never execute; it exists so the dynamic import - * inside the module's dist files resolves in a plain node environment. - */ - -export async function readPageDataFromFilesystem(): Promise { - throw new Error('#ai-ready-virtual/read-page-data.mjs stub: the prerender path is not available in tests') -} diff --git a/test/unit/ai-ready-gone-route.test.ts b/test/unit/ai-ready-gone-route.test.ts deleted file mode 100644 index 14765333..00000000 --- a/test/unit/ai-ready-gone-route.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -// @vitest-environment node -import type { H3Event } from 'h3' -import type { SQLInputValue } from 'node:sqlite' -import { DatabaseSync } from 'node:sqlite' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -/** - * The released nuxt-ai-ready runtime must treat a 410 - * route as terminal: the page row is pruned and the cron index result stays - * error-free, instead of the seed re-arming the failed row every crawl. - * - * Keep this regression coverage when updating the package catalog pin. - */ - -const mocks = vi.hoisted(() => ({ - initSchema: vi.fn(), - runtimeConfig: {} as Record, - useRawDb: vi.fn(), - fetchWithEvent: vi.fn(), -})) - -vi.mock('#nuxtseo/nitro', () => ({ - useEvent: () => { - throw new Error('No active event') - }, - useRuntimeConfig: () => mocks.runtimeConfig, - fetchWithEvent: mocks.fetchWithEvent, - useNitroApp: () => ({ hooks: { callHook: vi.fn() } }), -})) - -// The package exports map only exposes the module entry, so the test reaches -// the runtime dist files by path. `vi.mock` matches the dist files' own -// relative imports of the same real paths. -vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/drizzle/queries.js', () => ({ - initSchema: mocks.initSchema, -})) - -vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/drizzle/raw.js', () => ({ - useRawDb: mocks.useRawDb, -})) - -// Build dump restoration is a separate boundary from route indexing. -vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/checkStale.js', () => ({ - checkAndHandleStale: async () => ({ action: 'none' }), -})) - -vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/logger.js', () => ({ - logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})) - -// indexPage.js pulls in the module's utils.js for the success path, whose -// mdream import loads a wasm asset no test environment serves. The 410 paths -// under test never reach it. -vi.mock('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils.js', () => ({ - convertHtmlToMarkdown: () => { - throw new Error('convertHtmlToMarkdown is not available in tests') - }, -})) - -const { seedRoutes } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/db/queries.js') -const { batchIndexPages } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/batchIndex.js') -const { indexPageByRoute } = await import('../../node_modules/nuxt-ai-ready/dist/runtime/server/utils/indexPage.js') - -const HOUR = 60 * 60 * 1000 -const T0 = Date.UTC(2026, 9, 1) -// Truthy so the dist code routes fetches through `fetchWithEvent`, which the -// test controls. -const event = { - fetch: () => new Response(null), - __is_event__: true, - node: {}, - context: {}, -} as unknown as H3Event - -const SCHEMA = ` - CREATE TABLE IF NOT EXISTS ai_ready_pages ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - route TEXT UNIQUE NOT NULL, - route_key TEXT UNIQUE NOT NULL, - title TEXT NOT NULL DEFAULT '', - description TEXT NOT NULL DEFAULT '', - markdown TEXT NOT NULL DEFAULT '', - headings TEXT NOT NULL DEFAULT '[]', - keywords TEXT NOT NULL DEFAULT '[]', - content_hash TEXT, - updated_at TEXT NOT NULL, - indexed_at INTEGER NOT NULL, - is_error INTEGER NOT NULL DEFAULT 0, - indexed INTEGER NOT NULL DEFAULT 0, - source TEXT NOT NULL DEFAULT 'prerender', - last_seen_at INTEGER, - locale TEXT NOT NULL DEFAULT '' - ) -` - -function goneFetchError(status: number): Error { - return Object.assign(new Error(`Request failed with status code ${status} Gone`), { - status, - statusCode: status, - }) -} - -describe('ai-ready gone route', () => { - let sqlite: DatabaseSync - - beforeEach(() => { - vi.useFakeTimers({ toFake: ['Date'] }) - vi.setSystemTime(T0) - sqlite = new DatabaseSync(':memory:') - sqlite.exec(SCHEMA) - mocks.initSchema.mockReset().mockResolvedValue(undefined) - mocks.useRawDb.mockReset().mockResolvedValue({ - dialect: 'sqlite', - all: async (sql: string, params: SQLInputValue[] = []) => sqlite.prepare(sql).all(...params), - first: async (sql: string, params: SQLInputValue[] = []) => sqlite.prepare(sql).get(...params), - exec: async (sql: string, params: SQLInputValue[] = []) => { - sqlite.prepare(sql).run(...params) - }, - batch: async (stmts: { sql: string, params?: SQLInputValue[] }[]) => { - for (const stmt of stmts) - sqlite.prepare(stmt.sql).run(...(stmt.params || [])) - }, - }) - mocks.fetchWithEvent.mockReset() - mocks.runtimeConfig['nuxt-ai-ready'] = { - debug: false, - database: { _tag: 'Enabled', type: 'd1' }, - runtimeSync: { enabled: true, ttl: 3600, batchSize: 10 }, - } - }) - - afterEach(() => { - vi.useRealTimers() - sqlite.close() - }) - - function rowCount(): number { - return Number(sqlite.prepare('SELECT COUNT(*) as count FROM ai_ready_pages').get()?.count ?? 0) - } - - function row(route: string): { is_error: number, indexed: number } | undefined { - return sqlite.prepare('SELECT is_error, indexed FROM ai_ready_pages WHERE route = ?').get(route) as { is_error: number, indexed: number } | undefined - } - - it.each([ - { status: 410 }, - { statusCode: 410 }, - { response: { status: 410 } }, - ])('reports a gone route without an indexing error for %j', async (status) => { - await seedRoutes(event, ['/gone']) - mocks.fetchWithEvent.mockRejectedValue(Object.assign(new Error('Gone'), status)) - - expect(await indexPageByRoute('/gone', event, { markFailedAsError: true })).toEqual({ - success: false, - gone: true, - }) - expect(rowCount()).toBe(0) - }) - - it('does not re-arm a failed row inside the seed refresh window', async () => { - await seedRoutes(event, ['/failed']) - mocks.fetchWithEvent.mockRejectedValue(goneFetchError(500)) - await batchIndexPages(event, { limit: 10 }) - expect(row('/failed')).toMatchObject({ is_error: 1 }) - - await seedRoutes(event, ['/failed']) - expect(row('/failed')).toMatchObject({ is_error: 1 }) - }) - - it('re-arms a failed row after the seed refresh window', async () => { - await seedRoutes(event, ['/failed']) - mocks.fetchWithEvent.mockRejectedValue(goneFetchError(500)) - await batchIndexPages(event, { limit: 10 }) - expect(row('/failed')).toMatchObject({ is_error: 1 }) - - vi.setSystemTime(T0 + 25 * HOUR) - await seedRoutes(event, ['/failed']) - expect(row('/failed')).toEqual({ is_error: 0, indexed: 0 }) - }) - - it('prunes a 410 route and keeps indexResult.errors empty across two cron passes', async () => { - mocks.fetchWithEvent.mockRejectedValue(goneFetchError(410)) - - await seedRoutes(event, ['/gone']) - const first = await batchIndexPages(event, { limit: 10 }) - expect(first.errors).toEqual([]) - expect(first.indexed).toBe(0) - expect(first.remaining).toBe(0) - expect(first.complete).toBe(true) - expect(rowCount()).toBe(0) - - // The sitemap still lists the route, so the next pass re-seeds it; the - // fetch is gone again, the row is pruned again, and nothing is reported. - await seedRoutes(event, ['/gone']) - expect(rowCount()).toBe(1) - const second = await batchIndexPages(event, { limit: 10 }) - expect(second.errors).toEqual([]) - expect(second.remaining).toBe(0) - expect(rowCount()).toBe(0) - }) -}) diff --git a/vitest.config.ts b/vitest.config.ts index 9e3094da..5dd06bf8 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,17 +16,7 @@ import { configDefaults } from 'vitest/config' const MAX_WORKERS = Math.max(2, Math.min(6, availableParallelism() - 2)) export default defineVitestConfig({ - resolve: { - alias: { - '#checkin/checks': fileURLToPath(new URL('./test/fixtures/checkin-registry.ts', import.meta.url)), - // The nuxt-ai-ready module injects these virtuals at build time; tests - // that load its runtime dist files directly need them to resolve. - '#nuxtseo/nitro': fileURLToPath(new URL('./test/fixtures/ai-ready-nitro-shim.ts', import.meta.url)), - '#nuxtseo/h3': fileURLToPath(new URL('./test/fixtures/ai-ready-h3-shim.ts', import.meta.url)), - '#ai-ready-virtual/read-page-data.mjs': fileURLToPath(new URL('./test/fixtures/ai-ready-read-page-data-stub.ts', import.meta.url)), - '#ai-ready-virtual/i18n-runtime.mjs': fileURLToPath(new URL('./test/fixtures/ai-ready-i18n-runtime-stub.ts', import.meta.url)), - }, - }, + resolve: { alias: { '#checkin/checks': fileURLToPath(new URL('./test/fixtures/checkin-registry.ts', import.meta.url)) } }, test: { globals: true, environment: 'nuxt',