diff --git a/.gitignore b/.gitignore index a215f73ae..3a7944dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ result-* .corepack/ .vinext + +tests/fixtures-repos/*/clone/ diff --git a/packages/vinext/package.json b/packages/vinext/package.json index 192ab8a01..33fe79246 100644 --- a/packages/vinext/package.json +++ b/packages/vinext/package.json @@ -33,6 +33,10 @@ "types": "./dist/server/prod-server.d.ts", "import": "./dist/server/prod-server.js" }, + "./build/run-prerender": { + "types": "./dist/build/run-prerender.d.ts", + "import": "./dist/build/run-prerender.js" + }, "./cloudflare": { "types": "./dist/cloudflare/index.d.ts", "import": "./dist/cloudflare/index.js" diff --git a/packages/vinext/src/build/prerender.ts b/packages/vinext/src/build/prerender.ts index f2491d209..e84188837 100644 --- a/packages/vinext/src/build/prerender.ts +++ b/packages/vinext/src/build/prerender.ts @@ -25,10 +25,20 @@ import type { AppRoute } from "../routing/app-router.js"; import type { ResolvedNextConfig } from "../config/next-config.js"; import { classifyPagesRoute, classifyAppRoute } from "./report.js"; import { createValidFileMatcher, type ValidFileMatcher } from "../routing/file-matcher.js"; -import { NoOpCacheHandler, setCacheHandler, getCacheHandler } from "../shims/cache.js"; +import { MemoryCacheHandler, setCacheHandler, getCacheHandler } from "../shims/cache.js"; import { runWithHeadersContext, headersContextFromRequest } from "../shims/headers.js"; import { startProdServer } from "../server/prod-server.js"; import { readPrerenderSecret } from "./server-manifest.js"; +import { + consumePrerenderPageTags, + enablePrerenderTagCollection, +} from "../server/app-page-cache.js"; +import { + consumePrerenderFetchMetrics, + disablePrerenderMetricsCollection, + enablePrerenderMetricsCollection, + type PrerenderFetchMetric, +} from "../shims/fetch-cache.js"; export { readPrerenderSecret } from "./server-manifest.js"; // ─── Public Types ───────────────────────────────────────────────────────────── @@ -53,6 +63,23 @@ export type PrerenderRouteResult = path?: string; /** Which router produced this route. Used by cache seeding. */ router: "app" | "pages"; + /** + * ISR cache tags for this route (path tags + unstable_cache tags). + * Written to vinext-prerender.json and used by seedMemoryCacheFromPrerender + * to ensure updateTag/revalidateTag also invalidates prerendered ISR entries. + */ + tags?: string[]; + /** + * HTTP status code of the prerendered response. + * Omitted when 200. Present for routes that rendered a not-found (404) page. + */ + httpStatus?: number; + /** + * Fetch metrics collected during prerender for diagnostics. + * Written to vinext-prerender.json and used to generate + * .next/diagnostics/fetch-metrics.json for test compatibility. + */ + fetchMetrics?: PrerenderFetchMetric[]; } | { route: string; @@ -372,7 +399,7 @@ export async function prerenderPages({ } const previousHandler = getCacheHandler(); - setCacheHandler(new NoOpCacheHandler()); + setCacheHandler(new MemoryCacheHandler()); process.env.VINEXT_PRERENDER = "1"; // ownedProdServerHandle: a prod server we started ourselves and must close in finally. // When the caller passes options._prodServer we use that and do NOT close it. @@ -686,11 +713,19 @@ export async function prerenderApp({ fs.mkdirSync(outDir, { recursive: true }); const previousHandler = getCacheHandler(); - setCacheHandler(new NoOpCacheHandler()); + // Use a fresh MemoryCacheHandler (not NoOpCacheHandler) for the prerender phase. + // This ensures ISR entries start empty (no stale carry-over from previous + // builds), while still allowing fetch deduplication within individual renders. + // NoOpCacheHandler disables dedup too, causing identical fetches on the same + // page to return different values (breaking W3C trace context dedup tests). + setCacheHandler(new MemoryCacheHandler()); // VINEXT_PRERENDER=1 tells the prod server to skip instrumentation.register() // and enable prerender-only endpoints (/__vinext/prerender/*). - // The set/delete is wrapped in try/finally so it is always restored. + // NEXT_PHASE=phase-production-build matches Next.js behavior during static generation: + // pages can check process.env.NEXT_PHASE to call notFound() at build time. + // Both are set/deleted here and restored in the finally block. process.env.VINEXT_PRERENDER = "1"; + process.env.NEXT_PHASE = "phase-production-build"; const serverDir = path.dirname(rscBundlePath); @@ -969,6 +1004,14 @@ export async function prerenderApp({ // ── Render each URL via direct RSC handler invocation ───────────────────── + // Enable tag collection so that renderAppPageLifecycle() records per-page + // unstable_cache tags into the global sidecar. consumePrerenderPageTags() + // reads and clears these after each render so they end up in the manifest. + enablePrerenderTagCollection(); + // Enable fetch metrics collection so that every cacheable fetch during + // prerender records a diagnostic entry keyed by navPathname. + enablePrerenderMetricsCollection(); + /** * Render a single URL and return its result. * `onProgress` is intentionally not called here; the outer loop calls it @@ -995,40 +1038,77 @@ export async function prerenderApp({ const htmlRes = await runWithHeadersContext(headersContextFromRequest(htmlRequest), () => rscHandler(htmlRequest), ); - if (!htmlRes.ok) { + const httpStatus = htmlRes.status; + + // Non-ok responses that are not 404 are errors (or skips for speculative routes). + if (!htmlRes.ok && httpStatus !== 404) { + consumePrerenderPageTags(urlPath); if (isSpeculative) { return { route: routePattern, status: "skipped", reason: "dynamic" }; } return { route: routePattern, status: "error", - error: `RSC handler returned ${htmlRes.status}`, + error: `RSC handler returned ${httpStatus}`, }; } - // Detect dynamic usage for speculative routes via Cache-Control header. - // When headers(), cookies(), connection(), or noStore() are called during - // render, the server sets Cache-Control: no-store. We treat this as a - // signal that the route is dynamic and should be skipped. - if (isSpeculative) { + // Speculative route that returned 404: not a static page. + if (isSpeculative && !htmlRes.ok) { + consumePrerenderPageTags(urlPath); + return { route: routePattern, status: "skipped", reason: "dynamic" }; + } + + // Detect dynamic usage via Cache-Control: no-store in the render response. + // When headers(), cookies(), noStore(), or a no-store/revalidate:0 fetch + // is called during render, the server sets Cache-Control: no-store. + // We treat this as a signal that the route is dynamic and should not be + // seeded into the ISR cache — even for non-speculative routes (those with + // an explicit generateStaticParams) whose fetches indicate dynamic data. + { const cacheControl = htmlRes.headers.get("cache-control") ?? ""; if (cacheControl.includes("no-store")) { + const dynamicReason = htmlRes.headers.get("x-vinext-dynamic-reason"); await htmlRes.body?.cancel(); + consumePrerenderPageTags(urlPath); + if (dynamicReason) { + console.log( + `Static generation failed due to dynamic usage on ${urlPath}, reason: ${dynamicReason}`, + ); + } return { route: routePattern, status: "skipped", reason: "dynamic" }; } } const html = await htmlRes.text(); + // Capture ISR cache tags registered during this render. + // Rewrite the hierarchy tags (_N_T_/.../layout and .../page) to use the + // route pattern instead of the concrete URL. This matches Next.js which + // uses revalidatePath('/blog/[slug]', 'page') to target the pattern tag. + const rawPageTags = consumePrerenderPageTags(urlPath); + const pageTags = rewriteTagsToRoutePattern(rawPageTags, urlPath, routePattern); + const fetchMetrics = consumePrerenderFetchMetrics(urlPath); + // Fetch RSC payload via a second invocation with RSC headers // TODO: Extract RSC payload from the first response instead of invoking the handler twice. - const rscRequest = new Request(`http://localhost${urlPath}`, { - headers: { Accept: "text/x-component", RSC: "1" }, - }); - const rscRes = await runWithHeadersContext(headersContextFromRequest(rscRequest), () => - rscHandler(rscRequest), - ); - const rscData = rscRes.ok ? await rscRes.text() : null; + // Skip RSC fetch for 404 pages - they don't have a meaningful RSC payload. + let rscData: string | null = null; + if (httpStatus === 200) { + const rscRequest = new Request(`http://localhost${urlPath}`, { + headers: { Accept: "text/x-component", RSC: "1" }, + }); + const rscRes = await runWithHeadersContext(headersContextFromRequest(rscRequest), () => + rscHandler(rscRequest), + ); + rscData = rscRes.ok ? await rscRes.text() : null; + } + // Clear any sidecar entry left by the RSC-only request. + consumePrerenderPageTags(urlPath); + // Discard any fetch metrics recorded during the RSC-only request — we + // only want metrics from the HTML render (first pass) which mirrors what + // Next.js logs in its diagnostics output (one entry per unique fetch). + consumePrerenderFetchMetrics(urlPath); const outputFiles: string[] = []; @@ -1055,6 +1135,9 @@ export async function prerenderApp({ revalidate, router: "app", ...(urlPath !== routePattern ? { path: urlPath } : {}), + ...(pageTags.length > 0 ? { tags: pageTags } : {}), + ...(httpStatus !== 200 ? { httpStatus } : {}), + ...(fetchMetrics.length > 0 ? { fetchMetrics } : {}), }; } catch (e) { if (isSpeculative) { @@ -1117,6 +1200,8 @@ export async function prerenderApp({ } finally { setCacheHandler(previousHandler); delete process.env.VINEXT_PRERENDER; + delete process.env.NEXT_PHASE; + disablePrerenderMetricsCollection(); if (ownedProdServerHandle) { await new Promise((resolve) => ownedProdServerHandle!.server.close(() => resolve())); } @@ -1133,6 +1218,90 @@ export function getRscOutputPath(urlPath: string): string { return urlPath.replace(/^\//, "") + ".rsc"; } +// ─── Tag helpers ───────────────────────────────────────────────────────────── + +/** + * Convert an Express-style route pattern segment to Next.js bracket style. + * + * - `:slug` → `[slug]` + * - `:slug+` → `[...slug]` + * - `:slug*` → `[[...slug]]` + */ +function expressSegToNextJs(seg: string): string { + const catchAll = seg.match(/^:(.+)\+$/); + if (catchAll) return `[...${catchAll[1]}]`; + const optionalCatchAll = seg.match(/^:(.+)\*$/); + if (optionalCatchAll) return `[[...${optionalCatchAll[1]}]]`; + const dynamic = seg.match(/^:(.+)$/); + if (dynamic) return `[${dynamic[1]}]`; + return seg; +} + +/** + * Convert an Express-style route pattern (e.g. `/blog/:slug`) to Next.js + * file-system bracket notation (e.g. `/blog/[slug]`). + * + * vinext stores route patterns in Express style internally; Next.js tag names + * (`_N_T_`) use the bracket notation so revalidatePath('/blog/[slug]', 'page') + * targets the right cache entries. + */ +function expressPatternToNextJs(pattern: string): string { + return "/" + pattern.split("/").filter(Boolean).map(expressSegToNextJs).join("/"); +} + +/** + * Rewrite the _N_T_ hierarchy tags in `tags` to use the route pattern instead + * of the concrete URL path for the layout/page segment identifiers. + * + * Next.js uses the route pattern (e.g. `/blog/[slug]`) for revalidatePath() + * target matching, so `_N_T_/blog/[slug]/page` is the canonical tag rather + * than `_N_T_/blog/hello-world/page`. We post-process the tags emitted by + * `__pageCacheTags()` (which uses the concrete path) to match this behaviour. + * + * Result order: + * [bare_pathname, ...hierarchy_tags_using_pattern, instance_tag, ...fetch_tags] + * + * @param tags Tags from getPageTags() / consumePrerenderPageTags() + * @param urlPath Concrete prerendered URL, e.g. `/blog/hello-world` + * @param routePattern Route file-system pattern in Express style, e.g. `/blog/:slug` + */ +export function rewriteTagsToRoutePattern( + tags: string[], + urlPath: string, + routePattern: string, +): string[] { + if (urlPath === routePattern) return tags; + + // Convert Express-style pattern to Next.js bracket style for tag names + const nextJsPattern = expressPatternToNextJs(routePattern); + + // Identify concrete-path hierarchy tags generated by __pageCacheTags + const concreteBareSegs = urlPath.split("/").filter(Boolean); + const oldHierarchy = new Set(["_N_T_/layout"]); + let cBuilt = ""; + for (const seg of concreteBareSegs) { + cBuilt += "/" + seg; + oldHierarchy.add(`_N_T_${cBuilt}/layout`); + } + oldHierarchy.add(`_N_T_${cBuilt}/page`); + + // Build hierarchy tags from the Next.js-style route pattern + const patternSegs = nextJsPattern.split("/").filter(Boolean); + const newHierarchy: string[] = ["_N_T_/layout"]; + let pBuilt = ""; + for (const seg of patternSegs) { + pBuilt += "/" + seg; + newHierarchy.push(`_N_T_${pBuilt}/layout`); + } + newHierarchy.push(`_N_T_${pBuilt}/page`); + + // Separate remaining tags: bare pathname, instance tag, explicit fetch tags + const instanceTag = `_N_T_${urlPath}`; + const fetchTags = tags.filter((t) => !oldHierarchy.has(t) && t !== urlPath && t !== instanceTag); + + return [urlPath, ...newHierarchy, instanceTag, ...fetchTags]; +} + // ─── Build index ────────────────────────────────────────────────────────────── /** @@ -1157,6 +1326,9 @@ export function writePrerenderIndex( revalidate: r.revalidate, router: r.router, ...(r.path ? { path: r.path } : {}), + ...(r.tags && r.tags.length > 0 ? { tags: r.tags } : {}), + ...(r.httpStatus && r.httpStatus !== 200 ? { httpStatus: r.httpStatus } : {}), + ...(r.fetchMetrics && r.fetchMetrics.length > 0 ? { fetchMetrics: r.fetchMetrics } : {}), }; } if (r.status === "skipped") { diff --git a/packages/vinext/src/build/run-prerender.ts b/packages/vinext/src/build/run-prerender.ts index 23298d390..b9a01a650 100644 --- a/packages/vinext/src/build/run-prerender.ts +++ b/packages/vinext/src/build/run-prerender.ts @@ -29,6 +29,7 @@ import { readPrerenderSecret, } from "./prerender.js"; import { loadNextConfig, resolveNextConfig } from "../config/next-config.js"; +import { readBuildId } from "./server-manifest.js"; import { pagesRouter, apiRouter } from "../routing/pages-router.js"; import { appRouter } from "../routing/app-router.js"; import { findDir } from "./report.js"; @@ -93,6 +94,19 @@ export type RunPrerenderOptions = { * Intended for tests that build to a custom outDir. */ rscBundlePath?: string; + /** + * Override the output directory where prerendered HTML/RSC files are written. + * Defaults to `/dist/server/prerendered-routes` (non-export) or + * `/dist/client` (export). + * Intended for tests that build to a custom outDir. + */ + outDir?: string; + /** + * Override the directory where `vinext-prerender.json` manifest is written. + * Defaults to `/dist/server`. + * Intended for tests that build to a custom outDir. + */ + manifestDir?: string; }; /** @@ -126,16 +140,28 @@ export async function runPrerender(options: RunPrerenderOptions): Promise ep ? getImportVar(ep) : "null", ); + const layoutLoadingVars = (route.layoutLoadingPaths || []).map((lp) => + lp ? getImportVar(lp) : "null", + ); return ` { pattern: ${JSON.stringify(route.pattern)}, patternParts: ${JSON.stringify(route.patternParts)}, @@ -209,6 +216,7 @@ ${interceptEntries.join(",\n")} layoutTreePositions: ${JSON.stringify(route.layoutTreePositions)}, templates: [${templateVars.join(", ")}], errors: [${layoutErrorVars.join(", ")}], + layoutLoadings: [${layoutLoadingVars.join(", ")}], slots: { ${slotEntries.join(",\n")} }, @@ -367,6 +375,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from ${JSON.st import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -388,7 +397,7 @@ import { } from ${JSON.stringify(appRouteHandlerResponsePath)}; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from ${JSON.stringify(requestContextShimPath)}; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from ${JSON.stringify(routeTriePath)}; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -474,6 +483,7 @@ function __pageCacheTags(pathname, extraTags) { // headers — but that case is already prevented by the dynamic-usage opt-out. // TODO: capture render-time response headers for full Next.js parity. const __pendingRegenerations = new Map(); +const __edgeForceStaticWarned = new Set(); function __triggerBackgroundRegeneration(key, renderFn) { if (__pendingRegenerations.has(key)) return; const promise = renderFn() @@ -673,6 +683,7 @@ function rscOnError(error, requestInfo, errorContext) { if (process.env.NODE_ENV === "production" && error) { const msg = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? (error.stack || "") : ""; + console.error("[vinext] Server Component error:", msg, stack ? "\\n" + stack : ""); return __errorDigest(msg + stack); } return undefined; @@ -993,7 +1004,23 @@ async function buildPageElement(route, params, opts, searchParams) { // Next.js 16 passes params/searchParams as Promises (async pattern) // but pre-16 code accesses them as plain objects (params.id). // makeThenableParams() normalises null-prototype + preserves both patterns. - const asyncParams = makeThenableParams(params); + // + // Reorder params in routeSegments order. The trie matcher inserts params + // deepest-first (due to recursion unwinding), but Next.js expects them in + // outermost-first (URL left-to-right) order. Build an ordered copy by + // iterating routeSegments so that JSON.stringify / Object.keys give the + // same order as Next.js (e.g. {"category":"books","id":"hello-world"}). + const _orderedPageParams = {}; + for (const _s of (route.routeSegments || [])) { + let _ppn; + if (_s.startsWith("[[...") && _s.endsWith("]]")) { _ppn = _s.slice(5, -2); } + else if (_s.startsWith("[...") && _s.endsWith("]")) { _ppn = _s.slice(4, -1); } + else if (_s.startsWith("[") && _s.endsWith("]")) { _ppn = _s.slice(1, -1); } + if (_ppn !== undefined && Object.prototype.hasOwnProperty.call(params, _ppn)) { + _orderedPageParams[_ppn] = params[_ppn]; + } + } + const asyncParams = makeThenableParams(_orderedPageParams); const pageProps = { params: asyncParams }; if (searchParams) { // Always provide searchParams prop when the URL object is available, even @@ -1111,7 +1138,30 @@ async function buildPageElement(route, params, opts, searchParams) { } } - const layoutProps = { children: element, params: makeThenableParams(params) }; + // Compute params scoped to this layout's depth: each layout only receives + // params from segments at or above its own level in the route tree. + // layoutTreePositions[i] is the index of this layout's first *child* segment + // in routeSegments. All segments before that index belong to this layout and + // above, so we extract the dynamic param keys from those segments only. + const _tpForParams = route.layoutTreePositions ? route.layoutTreePositions[i] : (route.routeSegments || []).length; + const _segsForLayout = (route.routeSegments || []).slice(0, _tpForParams); + // Build layout-scoped params in segment order (mirrors Next.js key ordering). + const _layoutParams = {}; + for (const _seg of _segsForLayout) { + let _pn; + if (_seg.startsWith("[[...") && _seg.endsWith("]]")) { + _pn = _seg.slice(5, -2); + } else if (_seg.startsWith("[...") && _seg.endsWith("]")) { + _pn = _seg.slice(4, -1); + } else if (_seg.startsWith("[") && _seg.endsWith("]")) { + _pn = _seg.slice(1, -1); + } + if (_pn !== undefined && Object.prototype.hasOwnProperty.call(params, _pn)) { + _layoutParams[_pn] = params[_pn]; + } + } + + const layoutProps = { children: element, params: makeThenableParams(_layoutParams) }; // Add parallel slot elements to the layout that defines them. // Each slot has a layoutIndex indicating which layout it belongs to. @@ -1173,6 +1223,18 @@ async function buildPageElement(route, params, opts, searchParams) { const treePos = route.layoutTreePositions ? route.layoutTreePositions[i] : 0; const childSegs = __resolveChildSegments(route.routeSegments || [], treePos, params); element = createElement(LayoutSegmentProvider, { segmentMap: { children: childSegs } }, element); + + // If there is a loading.js at a "gap" directory level between this layout + // and the parent layout, wrap with Suspense so the fallback appears in + // initial HTML for slow layouts. (e.g. app/foo/loading.js wrapping app/foo/bar/layout.js) + const _gapLoading = route.layoutLoadings && route.layoutLoadings[i]; + if (_gapLoading && _gapLoading.default) { + element = createElement( + Suspense, + { fallback: createElement(_gapLoading.default) }, + element, + ); + } } } @@ -1319,18 +1381,22 @@ async function __readFormDataWithLimit(request, maxBytes) { // Map from route pattern to generateStaticParams function. // Used by the prerender phase to enumerate dynamic route URLs without // loading route modules via the dev server. +// Falls back from the page module to layouts (innermost first) so that +// layout-level generateStaticParams (e.g. a blog/[author]/layout.tsx) is +// correctly used when the page itself does not export generateStaticParams. export const generateStaticParamsMap = { -// TODO: layout-level generateStaticParams — this map only includes routes that -// have a pagePath (leaf pages). Layout segments can also export generateStaticParams -// to provide parent params for nested dynamic routes, but they don't have a pagePath -// so they are excluded here. Supporting layout-level generateStaticParams requires -// scanning layout.tsx files separately and including them in this map. ${routes - .filter((r) => r.isDynamic && r.pagePath) - .map( - (r) => - ` ${JSON.stringify(r.pattern)}: ${getImportVar(r.pagePath!)}?.generateStaticParams ?? null,`, - ) + .filter((r) => r.isDynamic && (r.pagePath || r.layouts.filter(Boolean).length > 0)) + .map((r) => { + const parts: string[] = []; + if (r.pagePath) parts.push(`${getImportVar(r.pagePath)}?.generateStaticParams`); + // Check layouts from innermost to outermost as fallback + for (const layout of [...r.layouts].reverse()) { + if (layout) parts.push(`${getImportVar(layout)}?.generateStaticParams`); + } + parts.push("null"); + return ` ${JSON.stringify(r.pattern)}: ${parts.join(" ?? ")},`; + }) .join("\n")} }; @@ -1353,6 +1419,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -1525,8 +1594,20 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } - const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); + // isRscRequest: client wants RSC payload (affects response format and pages fallback). + // - .rsc suffix: explicit RSC payload request — pages cannot respond to these. + // - Accept: text/x-component: explicit RSC content-type request — same. + // - rsc: 1 header alone (without the above): App Router client marker. Pages routes + // CAN respond to these with HTML; the client accepts HTML from pages routes. + // We still treat rsc:1 as RSC for response-format purposes (Content-Type) when + // an app route matches, but DO allow pages fallback (see !isExplicitRscPayload below). + const isExplicitRscPayload = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); + const isRscRequest = isExplicitRscPayload || request.headers.get("rsc") === "1"; let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -1780,7 +1861,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -1875,8 +1956,8 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setHeadersContext(null); setNavigationContext(null); const redirectHeaders = new Headers({ - "Content-Type": "text/x-component; charset=utf-8", - "Vary": "RSC, Accept", + "Content-Type": "text/x-component", + "Vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", "x-action-redirect": actionRedirect.url, "x-action-redirect-type": actionRedirect.type, "x-action-redirect-status": String(actionRedirect.status), @@ -1896,7 +1977,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -1923,7 +2004,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const actionPendingCookies = getAndClearPendingCookies(); const actionDraftCookie = getDraftModeCookieHeader(); - const actionHeaders = { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" }; + const actionHeaders = { "Content-Type": "text/x-component", "Vary": "RSC, Accept" }; const actionResponse = new Response(rscStream, { headers: actionHeaders }); if (actionPendingCookies.length > 0 || actionDraftCookie) { for (const cookie of actionPendingCookies) { @@ -1960,7 +2041,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); return proxyExternalRequest(request, __afterRewritten); } - cleanPathname = __afterRewritten; + // Parse the rewritten URL so that query params from the destination are + // forwarded to searchParams — mirrors how middleware rewrites are applied. + const __afterParsed = new URL(__afterRewritten, request.url); + cleanPathname = __afterParsed.pathname; + if (__afterParsed.search) url.search = __afterParsed.search; } } @@ -1975,7 +2060,9 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); return proxyExternalRequest(request, __fallbackRewritten); } - cleanPathname = __fallbackRewritten; + const __fallbackParsed = new URL(__fallbackRewritten, request.url); + cleanPathname = __fallbackParsed.pathname; + if (__fallbackParsed.search) url.search = __fallbackParsed.search; match = matchRoute(cleanPathname); } } @@ -1988,10 +2075,24 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // When a request doesn't match any App Router route, delegate to the // Pages Router handler (available in the SSR environment). This covers // both production request serving and prerender fetches from wrangler. - // RSC requests (.rsc suffix or Accept: text/x-component) cannot be + // Explicit RSC payload requests (.rsc suffix or Accept: text/x-component) cannot be // handled by the Pages Router, so skip the delegation for those. - if (!isRscRequest) { + // rsc:1 requests (App Router client marker) CAN fall through to pages — pages + // routes respond with HTML and the App Router client accepts that. + if (!isExplicitRscPayload) { const __pagesEntry = await import.meta.viteRsc.loadModule("ssr", "index"); + // Check Pages Router API routes first (paths under /api/…) + if ( + typeof __pagesEntry.handleApiRoute === "function" && + (cleanPathname.startsWith("/api/") || cleanPathname === "/api") + ) { + const __apiRes = await __pagesEntry.handleApiRoute(request, __decodePathParams(url.pathname) + (url.search || "")); + if (__apiRes.status !== 404) { + setHeadersContext(null); + setNavigationContext(null); + return __apiRes; + } + } if (typeof __pagesEntry.renderPage === "function") { // Use segment-wise decoding to preserve encoded path delimiters (%2F). // decodeURIComponent would turn /admin%2Fpanel into /admin/panel, @@ -2012,19 +2113,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ` : "" } - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -2034,6 +2138,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const handler = route.routeHandler; const method = request.method.toUpperCase(); const revalidateSeconds = __getAppRouteHandlerRevalidateSeconds(handler); + // Apply the route handler's fetchCache export so patchedFetch() overrides + // per-fetch cache directives (e.g. fetchCache='force-cache' caches all fetches). + if (handler.fetchCache) { + _setPageFetchCachePolicy(handler.fetchCache); + } + // dynamic = 'force-static' overrides all internal fetches to force-cache so + // that no-store fetches inside the handler do not mark dynamic usage and + // prevent the route response from being ISR-cached. + if (handler.dynamic === 'force-static' && !handler.fetchCache) { + _setPageFetchCachePolicy('force-cache'); + } if (__hasAppRouteHandlerDefaultExport(handler) && process.env.NODE_ENV === "development") { console.error( "[vinext] Detected default export in route handler " + route.pattern + ". Export a named export for each HTTP method instead.", @@ -2107,6 +2222,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); await _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); + // Re-apply the same fetch cache policy override so the background + // regeneration run sees the same policy as the original request. + if (handler.fetchCache) { + _setPageFetchCachePolicy(handler.fetchCache); + } else if (handler.dynamic === 'force-static') { + _setPageFetchCachePolicy('force-cache'); + } await renderFn(); }); }, @@ -2161,30 +2283,44 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). When ANY component in the route chain exports + // dynamicParams = false, the FULL combination of params must be in the + // cross-product of all generateStaticParams results. We track whether any + // level has dynamicParams = false so we can enforce this below. + const anyDynamicParamsFalse = + route.page?.dynamicParams === false || + (route.layouts || []).some(l => l?.dynamicParams === false); const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; + // Warn once per process when a page combines runtime='edge' with dynamic='force-static'. + // These two options are incompatible — edge routes cannot be statically rendered. + if (isForceStatic && route.page?.runtime === "edge" && !__edgeForceStaticWarned.has(route.pattern)) { + __edgeForceStaticWarned.add(route.pattern); + console.warn(\`Page "\${route.pattern}" is using runtime = 'edge' which is currently incompatible with dynamic = 'force-static'. Please remove either "runtime" or "force-static" for correct behavior\`); + } + // force-static: replace headers/cookies context with empty values and // clear searchParams so dynamic APIs return defaults instead of real data if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); + // force-static routes without an explicit revalidate must also be served + // from the ISR cache — otherwise revalidateSeconds stays null, the ISR + // cache check below is skipped, and every request re-renders the page. + // Use a 1-year TTL so the entry is effectively permanent. + if (revalidateSeconds === null) revalidateSeconds = 31536000; } // dynamic = 'error': install an access error so request APIs fail with the @@ -2200,14 +2336,18 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); } // force-dynamic: set no-store Cache-Control - const isForceDynamic = dynamicConfig === "force-dynamic"; + // Also treat fetchCache = "force-no-store" as force-dynamic so those pages + // bypass the ISR cache and return Cache-Control: no-store, which prevents + // the prerender phase from incorrectly seeding them into the ISR cache. + const isForceDynamic = + dynamicConfig === "force-dynamic" || route.page?.fetchCache === "force-no-store"; // ── ISR cache read (production only) ───────────────────────────────────── // Read from cache BEFORE generateStaticParams and all rendering work. @@ -2223,11 +2363,18 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // force-static and dynamic='error' are compatible with ISR — they control // how dynamic APIs behave during rendering, not whether results are cached. // Only force-dynamic truly bypasses the ISR cache. + // Serve prerendered/ISR-cached pages. Also check the cache when + // revalidateSeconds is null (statically-prerendered pages with no explicit + // revalidate export) so seeded build-time entries are served from cache + // rather than triggering a fresh render on every request. if ( process.env.NODE_ENV === "production" && !isForceDynamic && - revalidateSeconds !== null && revalidateSeconds > 0 && revalidateSeconds !== Infinity + (revalidateSeconds === null || (revalidateSeconds > 0 && revalidateSeconds !== Infinity)) ) { + // For static pages (no explicit revalidate) use a large TTL for + // Cache-Control headers; the actual entry never expires by time. + const __isrRevalidateSeconds = revalidateSeconds ?? 31536000; const __cachedPageResponse = await __readAppPageCacheResponse({ cleanPathname, clearRequestContext: function() { @@ -2240,7 +2387,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { isrHtmlKey: __isrHtmlKey, isrRscKey: __isrRscKey, isrSet: __isrSet, - revalidateSeconds, + revalidateSeconds: __isrRevalidateSeconds, renderFreshPageForCache: async function() { // Re-render the page to produce fresh HTML + RSC data for the cache // Use an empty headers context for background regeneration — not the original @@ -2253,7 +2400,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -2280,23 +2427,60 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. - const __dynamicParamsResponse = await __validateAppPageDynamicParams({ - clearRequestContext() { - setHeadersContext(null); - setNavigationContext(null); - }, - enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, - isDynamicRoute: route.isDynamic, - logGenerateStaticParamsError(err) { - console.error("[vinext] generateStaticParams error:", err); - }, - params, - }); - if (__dynamicParamsResponse) { - return __dynamicParamsResponse; + // + // When ANY layout or page has dynamicParams = false, check ALL gSPs in the + // route hierarchy. A path is only valid if every gSP in the chain covers + // its respective segment params. This correctly handles the partial-gen-params + // pattern where the layout controls one segment (e.g. lang) and the page + // controls another (e.g. slug) — even if the page has dynamicParams = true, + // slugs outside the page's generated set return 404 because the full + // combination was never prerendered. + { + const __gspList = anyDynamicParamsFalse + ? [ + ...(route.layouts || []) + .filter(l => l?.generateStaticParams) + .map(l => l.generateStaticParams), + ...(route.page?.generateStaticParams ? [route.page.generateStaticParams] : []), + ] + : [ + route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, + ].filter(Boolean); + + for (const __gsp of __gspList) { + const __dynamicParamsResponse = await __validateAppPageDynamicParams({ + clearRequestContext() { + setHeadersContext(null); + setNavigationContext(null); + }, + enforceStaticParamsOnly: anyDynamicParamsFalse, + generateStaticParams: __gsp, + isDynamicRoute: route.isDynamic, + logGenerateStaticParamsError(err) { + console.error("[vinext] generateStaticParams error:", err); + }, + params, + }); + if (__dynamicParamsResponse) { + return __dynamicParamsResponse; + } + } + } + + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); } // Check for intercepting routes on RSC requests (client-side navigation). @@ -2331,7 +2515,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // context to still be live. The AsyncLocalStorage scope from runWithRequestContext // handles cleanup naturally when all async continuations complete. return new Response(interceptStream, { - headers: { "Content-Type": "text/x-component; charset=utf-8", "Vary": "RSC, Accept" }, + headers: { "Content-Type": "text/x-component", "Vary": "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept" }, }); }, searchParams: url.searchParams, diff --git a/packages/vinext/src/entries/app-ssr-entry.ts b/packages/vinext/src/entries/app-ssr-entry.ts index 887d09b98..7f82e49eb 100644 --- a/packages/vinext/src/entries/app-ssr-entry.ts +++ b/packages/vinext/src/entries/app-ssr-entry.ts @@ -20,7 +20,7 @@ export { default } from ${JSON.stringify(entryPath)}; ${ hasPagesDir ? ` -export { pageRoutes, renderPage } from "virtual:vinext-server-entry"; +export { pageRoutes, renderPage, handleApiRoute } from "virtual:vinext-server-entry"; ` : "" }`; diff --git a/packages/vinext/src/index.ts b/packages/vinext/src/index.ts index 7495f663a..f849b6ebb 100644 --- a/packages/vinext/src/index.ts +++ b/packages/vinext/src/index.ts @@ -1734,6 +1734,7 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { // on every page — defeating code-splitting for React.lazy() and // next/dynamic boundaries. ...(hasCloudflarePlugin ? { manifest: true } : {}), + ...(options.clientOutDir ? { outDir: options.clientOutDir } : {}), ...withBuildBundlerOptions(viteMajorVersion, { input: { index: VIRTUAL_APP_BROWSER_ENTRY }, output: getClientOutputConfigForVite(viteMajorVersion), @@ -3341,7 +3342,16 @@ export default function vinext(options: VinextOptions = {}): PluginOption[] { const outDir = options.dir; if (!outDir) return; - const manifest = { prerenderSecret }; + const manifest: Record = { prerenderSecret }; + // Include the buildId so the prerender phase (run-prerender.ts) + // can use the exact same buildId that was baked into the compiled + // RSC bundle via Vite's `define`. If the prerender phase loaded + // next.config.js independently it would generate a new random UUID + // that wouldn't match the compiled-in buildId, causing ISR cache + // keys set by seedMemoryCacheFromPrerender to never match the keys + // the prod server looks up at request time. + const bid = nextConfig?.buildId; + if (bid) manifest.buildId = bid; fs.writeFileSync(path.join(outDir, "vinext-server.json"), JSON.stringify(manifest)); }, }, diff --git a/packages/vinext/src/routing/app-router.ts b/packages/vinext/src/routing/app-router.ts index c9142a36a..8a944b0d4 100644 --- a/packages/vinext/src/routing/app-router.ts +++ b/packages/vinext/src/routing/app-router.ts @@ -85,6 +85,18 @@ export type AppRoute = { * ancestor error boundaries catch errors from descendant segments. */ layoutErrorPaths: (string | null)[]; + /** + * Per-layout Suspense loading paths for "gap" levels. + * For layout[i] at depth D[i], this is the loading.js at the outermost + * directory in the gap between D[i-1] and D[i] (i.e., directories with a + * loading.js but no layout.js). When non-null, a Suspense boundary with + * this loading as fallback is placed around layout[i]'s subtree. + * + * Example: app/slow/loading.js (depth 1) + app/slow/inner/layout.js (depth 2) + * → layoutLoadingPaths[innerLayoutIndex] = "app/slow/loading.js" + * → Suspense(fallback=Loading, children=inner/layout.js(...)) + */ + layoutLoadingPaths: (string | null)[]; /** Not-found component path (nearest, walking up from page dir) */ notFoundPath: string | null; /** @@ -322,6 +334,7 @@ function discoverSlotSubRoutes( loadingPath: parentRoute.loadingPath, errorPath: parentRoute.errorPath, layoutErrorPaths: parentRoute.layoutErrorPaths, + layoutLoadingPaths: parentRoute.layoutLoadingPaths, notFoundPath: parentRoute.notFoundPath, notFoundPaths: parentRoute.notFoundPaths, forbiddenPath: parentRoute.forbiddenPath, @@ -414,6 +427,16 @@ function fileToAppRoute( // This array enables interleaving error boundaries with layouts in the rendering. const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir, matcher); + // Discover per-layout loading boundaries for "gap" directory levels. + // loading.js at a level that has no layout.js creates a Suspense boundary + // that wraps the first child layout/segment at a deeper level. + const layoutLoadingPaths = discoverLayoutGapLoadings( + segments, + appDir, + layoutTreePositions, + matcher, + ); + // Discover loading, error in the route's directory const routeDir = dir === "." ? appDir : path.join(appDir, dir); const loadingPath = findFile(routeDir, "loading", matcher); @@ -444,6 +467,7 @@ function fileToAppRoute( loadingPath, errorPath, layoutErrorPaths, + layoutLoadingPaths, notFoundPath, notFoundPaths, forbiddenPath, @@ -470,6 +494,46 @@ function computeLayoutTreePositions(appDir: string, layouts: string[]): number[] }); } +/** + * Discover loading.js files that should act as Suspense boundaries around + * "gap" directory levels — directories that have a loading.js but no layout.js. + * + * Returns an array aligned with the layouts array. For each layout[i]: + * - Computes the gap = directory depths between layout[i-1] and layout[i] + * - Walks those depths looking for loading.js + * - Returns the outermost loading found (closest to the parent layout), or null + * + * This implements Next.js's rule that loading.js at level D wraps the subtree + * at level D+1 and beyond with a Suspense fallback. + */ +function discoverLayoutGapLoadings( + segments: string[], + appDir: string, + layoutTreePositions: number[], + matcher: ValidFileMatcher, +): (string | null)[] { + // Build a map from directory depth → loading path + const loadingAtDepth: (string | null)[] = []; + let currentDir = appDir; + // Depth 0 = appDir + loadingAtDepth[0] = findFile(appDir, "loading", matcher); + for (let d = 0; d < segments.length; d++) { + currentDir = path.join(currentDir, segments[d]); + loadingAtDepth[d + 1] = findFile(currentDir, "loading", matcher); + } + + // For each layout slot, find the outermost loading in the gap above it + return layoutTreePositions.map((depth, i) => { + const prevDepth = i === 0 ? -1 : layoutTreePositions[i - 1]; + // Look from prevDepth+1 up to depth-1 (the gap dirs without a layout) + for (let d = prevDepth + 1; d < depth; d++) { + const lp = loadingAtDepth[d]; + if (lp) return lp; + } + return null; + }); +} + /** * Discover all layout files from root to the given directory. * Each level of the directory tree may have a layout.tsx. diff --git a/packages/vinext/src/server/api-handler.ts b/packages/vinext/src/server/api-handler.ts index 88307b4f3..b84682430 100644 --- a/packages/vinext/src/server/api-handler.ts +++ b/packages/vinext/src/server/api-handler.ts @@ -213,8 +213,55 @@ export async function handleApiRoute( // Enhance req/res with Next.js helpers const { apiReq, apiRes } = enhanceApiObjects(req, res, query, body); - // Call the handler - await handler(apiReq, apiRes); + // Call the handler. + // Edge-runtime API routes return a Response directly instead of using + // the Node.js req/res API. Detect this by checking the module's config + // export or by duck-typing the return value. + const isEdgeRuntime = + (apiModule as { config?: { runtime?: string } }).config?.runtime === "edge"; + + if (isEdgeRuntime) { + // Build a Web API Request from the Node.js IncomingMessage + const protocol = (req as { connection?: { encrypted?: boolean } }).connection?.encrypted + ? "https" + : "http"; + const host = req.headers.host ?? "localhost"; + const webRequest = new Request(`${protocol}://${host}${url}`, { + method: req.method ?? "GET", + headers: Object.fromEntries( + Object.entries(req.headers).flatMap(([k, v]) => + Array.isArray(v) ? v.map((val) => [k, val]) : v != null ? [[k, v]] : [], + ), + ), + }); + const result = await (handler as (req: Request) => Response | Promise)(webRequest); + if (result instanceof Response) { + res.statusCode = result.status; + result.headers.forEach((value, key) => { + res.setHeader(key, value); + }); + const body = await result.arrayBuffer(); + res.end(Buffer.from(body)); + return true; + } + // Fell through — treat as node-style (no return value) + if (!res.writableEnded) res.end(); + return true; + } + + const handlerResult = await handler(apiReq, apiRes); + // Duck-type fallback: if handler returned a Response despite no config, + // write it to the Node.js response. + if (handlerResult instanceof Response) { + const r = handlerResult as Response; + res.statusCode = r.status; + r.headers.forEach((value, key) => { + res.setHeader(key, value); + }); + const body = await r.arrayBuffer(); + res.end(Buffer.from(body)); + return true; + } return true; } catch (e) { if (e instanceof PagesBodyParseError) { diff --git a/packages/vinext/src/server/app-browser-entry.ts b/packages/vinext/src/server/app-browser-entry.ts index dd74e35e8..13d9bf117 100644 --- a/packages/vinext/src/server/app-browser-entry.ts +++ b/packages/vinext/src/server/app-browser-entry.ts @@ -399,6 +399,7 @@ function renderNavigationPayload( updateBrowserTree(payload, navigationSnapshot, renderId, useTransition, true); } catch (error) { // Clean up pending state and decrement counter on synchronous error. + console.error("[vinext:nav] renderNavigationPayload sync error:", error); pendingNavigationPrePaintEffects.delete(renderId); const resolve = pendingNavigationCommits.get(renderId); pendingNavigationCommits.delete(renderId); @@ -607,12 +608,13 @@ async function main(): Promise { } let _snapshotPending = false; + let _debugRscUrl: string | undefined; // Hoist navId above try so the catch block can guard against hard-navigating // to a stale URL when this navigation has already been superseded. const navId = ++activeNavigationId; try { const url = new URL(href, window.location.origin); - const rscUrl = toRscUrl(url.pathname + url.search); + const rscUrl = (_debugRscUrl = toRscUrl(url.pathname + url.search)); // Use startTransition for same-route navigations (searchParam changes) // so React keeps the old UI visible during the transition. For cross-route // navigations (different pathname), use synchronous updates — React's @@ -770,7 +772,7 @@ async function main(): Promise { // Don't hard-navigate to a stale URL if this navigation was superseded by // a newer one — the newer navigation is already in flight and would be clobbered. if (navId !== activeNavigationId) return; - console.error("[vinext] RSC navigation error:", error); + console.error("[vinext] RSC navigation error:", navigationKind, _debugRscUrl ?? href, error); window.location.href = href; } }; diff --git a/packages/vinext/src/server/app-page-boundary.ts b/packages/vinext/src/server/app-page-boundary.ts index 3765cb68e..6e7fbf0eb 100644 --- a/packages/vinext/src/server/app-page-boundary.ts +++ b/packages/vinext/src/server/app-page-boundary.ts @@ -184,7 +184,10 @@ export async function renderAppPageBoundaryResponse( // their ALS-backed state while the stream is being read. return new Response(rscStream, { status: options.status, - headers: { "Content-Type": "text/x-component; charset=utf-8", Vary: "RSC, Accept" }, + headers: { + "Content-Type": "text/x-component", + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", + }, }); } diff --git a/packages/vinext/src/server/app-page-cache.ts b/packages/vinext/src/server/app-page-cache.ts index 3520619cb..873801f4f 100644 --- a/packages/vinext/src/server/app-page-cache.ts +++ b/packages/vinext/src/server/app-page-cache.ts @@ -1,6 +1,66 @@ import type { CachedAppPageValue } from "../shims/cache.js"; import { buildAppPageCacheValue, type ISRCacheEntry } from "./isr-cache.js"; +// --------------------------------------------------------------------------- +// Prerender tag sidecar +// +// During a production build's prerender phase, the RSC handler renders each +// page and calls finalizeAppPageHtmlCacheResponse(). At that point we eagerly +// capture the page's ISR cache tags (which include unstable_cache tags that +// were registered synchronously via _addCollectedFetchTags before any await). +// +// The prerender code (prerender.ts) reads these tags via consumePrerenderPageTags() +// after calling rscHandler(), and writes them to the vinext-prerender.json +// manifest. seedMemoryCacheFromPrerender() then uses the manifest tags so that +// seeded ISR entries are invalidated correctly by revalidateTag/updateTag. +// --------------------------------------------------------------------------- + +const _PRERENDER_TAGS_KEY = Symbol.for("vinext.prerenderPageTags"); +const _gPrerender = globalThis as Record; + +function _getPrerenderTagsMap(): Map { + return (_gPrerender[_PRERENDER_TAGS_KEY] ??= new Map()) as Map; +} + +/** + * Enable tag collection for the current prerender phase. + * Call this from prerender.ts before starting renders. + * Has no effect if collection is already active. + */ +export function enablePrerenderTagCollection(): void { + if (!_gPrerender[_PRERENDER_TAGS_KEY]) { + _gPrerender[_PRERENDER_TAGS_KEY] = new Map(); + } +} + +/** + * Store the ISR tags for a prerendered page. + * No-op unless enablePrerenderTagCollection() was called first. + * Called from renderAppPageLifecycle() after renderToReadableStream() so that + * synchronously-registered unstable_cache tags are captured for all pages, + * including speculative static pages that don't write to the ISR cache. + */ +export function recordPrerenderPageTags(pathname: string, tags: string[]): void { + // Only store if prerender collection was explicitly enabled. + // This prevents memory leaks in live production renders. + const map = _gPrerender[_PRERENDER_TAGS_KEY] as Map | undefined; + if (map) { + map.set(pathname, tags); + } +} + +/** + * Consume (read + delete) the ISR tags recorded for a page during prerender. + * Returns an empty array if no tags were recorded for the given pathname. + */ +export function consumePrerenderPageTags(pathname: string): string[] { + const map = _gPrerender[_PRERENDER_TAGS_KEY] as Map | undefined; + if (!map) return []; + const tags = map.get(pathname); + map.delete(pathname); + return tags ?? []; +} + type AppPageDebugLogger = (event: string, detail: string) => void; type AppPageCacheGetter = (key: string) => Promise; type AppPageCacheSetter = ( @@ -86,8 +146,9 @@ export function buildAppPageCachedResponse( const status = cachedValue.status || 200; const headers = { "Cache-Control": buildAppPageCacheControl(options.cacheState, options.revalidateSeconds), - Vary: "RSC, Accept", + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", "X-Vinext-Cache": options.cacheState, + "X-Nextjs-Cache": options.cacheState, }; if (options.isRscRequest) { @@ -98,7 +159,7 @@ export function buildAppPageCachedResponse( return new Response(cachedValue.rscData, { status, headers: { - "Content-Type": "text/x-component; charset=utf-8", + "Content-Type": "text/x-component", ...headers, }, }); diff --git a/packages/vinext/src/server/app-page-execution.ts b/packages/vinext/src/server/app-page-execution.ts index 806d7214b..73b871f45 100644 --- a/packages/vinext/src/server/app-page-execution.ts +++ b/packages/vinext/src/server/app-page-execution.ts @@ -46,6 +46,66 @@ function getAppPageStatusText(statusCode: number): string { return statusCode === 403 ? "Forbidden" : statusCode === 401 ? "Unauthorized" : "Not Found"; } +/** + * Build the default HTML fallback for HTTP access errors (404/403/401) when + * no custom not-found.tsx / forbidden.tsx / unauthorized.tsx is present. + * + * Matches the Next.js default 404 page text so upstream tests can assert on + * "This page could not be found". + */ +export function buildDefaultNotFoundHtml(statusCode: number): string { + const titles: Record = { + 404: "404: This page could not be found.", + 403: "403: Forbidden", + 401: "401: Unauthorized", + }; + const bodies: Record = { + 404: "This page could not be found.", + 403: "Forbidden", + 401: "Unauthorized", + }; + const title = titles[statusCode] ?? "Error"; + const heading = String(statusCode); + const bodyText = bodies[statusCode] ?? "An error occurred"; + const outerStyle = + "font-family:-apple-system,BlinkMacSystemFont,Roboto," + + '"Segoe UI",sans-serif;height:100vh;text-align:center;' + + "display:flex;flex-direction:column;align-items:center;justify-content:center"; + const h1Style = + "display:inline-block;border-right:1px solid rgba(0,0,0,.3);" + + "margin:0;margin-right:20px;padding:10px 23px 10px 0;" + + "font-size:24px;font-weight:500;vertical-align:top"; + const innerStyle = + "display:inline-block;text-align:left;line-height:49px;" + "height:49px;vertical-align:middle"; + const h2Style = "font-size:14px;font-weight:normal;line-height:inherit;margin:0;padding:0"; + return ( + '' + + '' + + "" + + title + + "" + + "" + + '
' + + "
" + + '

' + + heading + + "

" + + '
' + + '

' + + bodyText + + "

" + + "
" + ); +} + export function resolveAppPageSpecialError(error: unknown): AppPageSpecialError | null { if (!(error && typeof error === "object" && "digest" in error)) { return null; @@ -91,8 +151,10 @@ export async function buildAppPageSpecialErrorResponse( } options.clearRequestContext(); - return new Response(getAppPageStatusText(options.specialError.statusCode), { - status: options.specialError.statusCode, + const statusCode = options.specialError.statusCode; + return new Response(buildDefaultNotFoundHtml(statusCode), { + status: statusCode, + headers: { "Content-Type": "text/html; charset=utf-8" }, }); } diff --git a/packages/vinext/src/server/app-page-render.ts b/packages/vinext/src/server/app-page-render.ts index 8591cae79..4c2e5d7b9 100644 --- a/packages/vinext/src/server/app-page-render.ts +++ b/packages/vinext/src/server/app-page-render.ts @@ -1,7 +1,10 @@ import type { ReactNode } from "react"; import type { CachedAppPageValue } from "../shims/cache.js"; +import { clearPrerenderFetchMetricsForPath, getMinFetchRevalidate } from "../shims/fetch-cache.js"; +import { getDynamicUsageReason } from "../shims/headers.js"; import { finalizeAppPageHtmlCacheResponse, + recordPrerenderPageTags, scheduleAppPageRscCacheWrite, } from "./app-page-cache.js"; import { @@ -136,6 +139,16 @@ export async function renderAppPageLifecycle( return options.runWithSuppressedHookWarning(probe); }, }); + // Record page tags for the prerender sidecar before any early-return path. + // The probe may have awaited the page component (e.g. for notFound detection), + // which means fetch tags collected via _addCollectedFetchTags are already + // available here even if we're about to return a preRenderResponse (404/redirect). + // For normal pages we'll call this again after renderToReadableStream() to also + // capture unstable_cache tags; the second call overwrites the first since the + // sidecar is keyed by pathname. + // recordPrerenderPageTags() is a no-op unless enablePrerenderTagCollection() was called. + recordPrerenderPageTags(options.cleanPathname, options.getPageTags()); + if (preRenderResponse) { return preRenderResponse; } @@ -143,18 +156,31 @@ export async function renderAppPageLifecycle( const compileEnd = options.isProduction ? undefined : performance.now(); const baseOnError = options.createRscOnErrorHandler(options.cleanPathname, options.routePattern); const rscErrorTracker = createAppPageRscErrorTracker(baseOnError); + + // Clear any fetch metrics accumulated during the probe phase so that only + // RSC-render metrics are captured. The probe runs components to detect + // notFound/redirect early, but those executions are an implementation detail + // of vinext — Next.js only records metrics from the RSC render pass. + clearPrerenderFetchMetricsForPath(options.cleanPathname); + const rscStream = options.renderToReadableStream(options.element, { onError: rscErrorTracker.onRenderError, }); + // Record page tags again after renderToReadableStream() to capture unstable_cache tags. + // unstable_cache calls _addCollectedFetchTags() synchronously during React's rendering + // pass inside renderToReadableStream(). This call overwrites the earlier probe-time + // recording, now including any additional unstable_cache tags. + recordPrerenderPageTags(options.cleanPathname, options.getPageTags()); + let revalidateSeconds = options.revalidateSeconds; + // Capture the RSC stream in all production non-force-dynamic renders so that + // if the post-render min-fetch-revalidate reveals a finite TTL (Category E), + // the RSC data is available to write alongside the HTML cache entry. + // Pages that turn out to be uncacheable simply discard the buffered data. const rscCapture = teeAppPageRscStreamForCapture( rscStream, - options.isProduction && - revalidateSeconds !== null && - revalidateSeconds > 0 && - revalidateSeconds !== Infinity && - !options.isForceDynamic, + options.isProduction && !options.isForceDynamic, ); const rscForResponse = rscCapture.responseStream; const isrRscDataPromise = rscCapture.capturedRscDataPromise; @@ -257,10 +283,22 @@ export async function renderAppPageLifecycle( // Eagerly read values that must be captured before the stream is consumed. const draftCookie = options.getDraftModeCookieHeader(); const dynamicUsedDuringRender = options.consumeDynamicUsage(); + // Capture reason AFTER consumeDynamicUsage so the headersContext is still live. + const dynamicUsageReason = dynamicUsedDuringRender ? getDynamicUsageReason() : null; const requestCacheLife = options.getRequestCacheLife(); if (requestCacheLife?.revalidate !== undefined && revalidateSeconds === null) { revalidateSeconds = requestCacheLife.revalidate; } + // Derive page ISR TTL from the minimum fetch-level revalidate seen during + // this render, when no explicit `export const revalidate` is present. + // This matches Next.js semantics: the page revalidates at the rate of its + // shortest-lived fetch (e.g. `next: { revalidate: 3 }` → page TTL = 3s). + if (revalidateSeconds === null) { + const minFetch = getMinFetchRevalidate(); + if (minFetch !== null && minFetch > 0) { + revalidateSeconds = minFetch; + } + } // Defer clearRequestContext() until the HTML stream is fully consumed by the // HTTP layer. The RSC/SSR pipeline is lazy — Server Components execute while @@ -291,6 +329,7 @@ export async function renderAppPageLifecycle( if (htmlResponsePolicy.shouldWriteToCache) { const isrResponse = buildAppPageHtmlResponse(safeHtmlStream, { draftCookie, + dynamicUsageReason, fontLinkHeader, middlewareContext: options.middlewareContext, policy: htmlResponsePolicy, @@ -315,6 +354,7 @@ export async function renderAppPageLifecycle( return buildAppPageHtmlResponse(safeHtmlStream, { draftCookie, + dynamicUsageReason, fontLinkHeader, middlewareContext: options.middlewareContext, policy: htmlResponsePolicy, diff --git a/packages/vinext/src/server/app-page-request.ts b/packages/vinext/src/server/app-page-request.ts index 2e7c68f55..5b81c5fde 100644 --- a/packages/vinext/src/server/app-page-request.ts +++ b/packages/vinext/src/server/app-page-request.ts @@ -1,4 +1,5 @@ import type { AppPageSpecialError } from "./app-page-execution.js"; +import { buildDefaultNotFoundHtml } from "./app-page-execution.js"; export type AppPageParams = Record; @@ -96,19 +97,51 @@ function areStaticParamsAllowed( export async function validateAppPageDynamicParams( options: ValidateAppPageDynamicParamsOptions, ): Promise { - if ( - !options.enforceStaticParamsOnly || - !options.isDynamicRoute || - typeof options.generateStaticParams !== "function" - ) { + if (!options.isDynamicRoute || typeof options.generateStaticParams !== "function") { return null; } try { const staticParams = await options.generateStaticParams({ params: options.params }); - if (Array.isArray(staticParams) && !areStaticParamsAllowed(options.params, staticParams)) { - options.clearRequestContext(); - return new Response("Not Found", { status: 404 }); + + // Validate that all returned params are strings (or string arrays for catch-all). + // Next.js throws a descriptive error when a param is a non-string value. + if (Array.isArray(staticParams)) { + for (const paramSet of staticParams) { + if (paramSet && typeof paramSet === "object") { + for (const [key, value] of Object.entries(paramSet)) { + if ( + value !== null && + value !== undefined && + typeof value !== "string" && + !(Array.isArray(value) && value.every((v) => typeof v === "string")) + ) { + const received = Array.isArray(value) + ? "array" + : typeof value === "object" + ? "object" + : typeof value; + const msg = + `A required parameter (${key}) was not provided as a string ` + + `received ${received} in generateStaticParams for ${options.params ? JSON.stringify(options.params) : "route"}`; + options.logGenerateStaticParamsError?.(new Error(msg)); + options.clearRequestContext(); + return new Response(msg, { status: 500 }); + } + } + } + } + + if ( + options.enforceStaticParamsOnly && + !areStaticParamsAllowed(options.params, staticParams) + ) { + options.clearRequestContext(); + return new Response(buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); + } } } catch (error) { options.logGenerateStaticParamsError?.(error); diff --git a/packages/vinext/src/server/app-page-response.ts b/packages/vinext/src/server/app-page-response.ts index c8784764b..fe079271e 100644 --- a/packages/vinext/src/server/app-page-response.ts +++ b/packages/vinext/src/server/app-page-response.ts @@ -44,6 +44,7 @@ export type BuildAppPageRscResponseOptions = { export type BuildAppPageHtmlResponseOptions = { draftCookie?: string | null; + dynamicUsageReason?: string | null; fontLinkHeader?: string; middlewareContext: AppPageMiddlewareContext; policy: AppPageResponsePolicy; @@ -127,7 +128,7 @@ export function resolveAppPageHtmlResponsePolicy( }; } - if (options.dynamicUsedDuringRender) { + if (options.dynamicUsedDuringRender && !options.isForceStatic) { return { cacheControl: NO_STORE_CACHE_CONTROL, shouldWriteToCache: false, @@ -162,8 +163,8 @@ export function buildAppPageRscResponse( options: BuildAppPageRscResponseOptions, ): Response { const headers = new Headers({ - "Content-Type": "text/x-component; charset=utf-8", - Vary: "RSC, Accept", + "Content-Type": "text/x-component", + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", }); if (options.params && Object.keys(options.params).length > 0) { @@ -176,6 +177,7 @@ export function buildAppPageRscResponse( } if (options.policy.cacheState) { headers.set("X-Vinext-Cache", options.policy.cacheState); + headers.set("X-Nextjs-Cache", options.policy.cacheState); } if (options.middlewareContext.headers) { @@ -207,7 +209,10 @@ export function buildAppPageHtmlResponse( ): Response { const headers = new Headers({ "Content-Type": "text/html; charset=utf-8", - Vary: "RSC, Accept", + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", + // Stub for edge runtime indicator — vinext runs all routes in Node.js but + // Next.js tests check for this header on App Router responses. + "X-Edge-Runtime": "1", }); if (options.policy.cacheControl) { @@ -215,6 +220,7 @@ export function buildAppPageHtmlResponse( } if (options.policy.cacheState) { headers.set("X-Vinext-Cache", options.policy.cacheState); + headers.set("X-Nextjs-Cache", options.policy.cacheState); } if (options.draftCookie) { headers.append("Set-Cookie", options.draftCookie); @@ -222,6 +228,9 @@ export function buildAppPageHtmlResponse( if (options.fontLinkHeader) { headers.set("Link", options.fontLinkHeader); } + if (options.dynamicUsageReason) { + headers.set("X-Vinext-Dynamic-Reason", options.dynamicUsageReason); + } if (options.middlewareContext.headers) { for (const [key, value] of options.middlewareContext.headers) { diff --git a/packages/vinext/src/server/app-page-stream.ts b/packages/vinext/src/server/app-page-stream.ts index 7f2f78365..4965b6e57 100644 --- a/packages/vinext/src/server/app-page-stream.ts +++ b/packages/vinext/src/server/app-page-stream.ts @@ -138,7 +138,7 @@ export async function renderAppPageHtmlResponse( const headers: Record = { "Content-Type": "text/html; charset=utf-8", - Vary: "RSC, Accept", + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", }; if (options.fontLinkHeader) { diff --git a/packages/vinext/src/server/app-route-handler-cache.ts b/packages/vinext/src/server/app-route-handler-cache.ts index 9df6172d9..f5e1834b3 100644 --- a/packages/vinext/src/server/app-route-handler-cache.ts +++ b/packages/vinext/src/server/app-route-handler-cache.ts @@ -3,12 +3,9 @@ import type { ISRCacheEntry } from "./isr-cache.js"; import type { RouteHandlerMiddlewareContext } from "./app-route-handler-response.js"; import { applyRouteHandlerMiddlewareContext, - buildAppRouteCacheValue, buildRouteHandlerCachedResponse, } from "./app-route-handler-response.js"; -import { markKnownDynamicAppRoute } from "./app-route-handler-runtime.js"; import { - runAppRouteHandler, type AppRouteDebugLogger, type AppRouteDynamicUsageFn, type AppRouteHandlerFunction, @@ -80,55 +77,13 @@ export async function readAppRouteHandlerCacheResponse( } if (cached?.isStale && cachedValue) { - const staleValue = cachedValue; - const revalidateSearchParams = new URLSearchParams(options.revalidateSearchParams); - - options.scheduleBackgroundRegeneration(routeKey, async () => { - await options.runInRevalidationContext(async () => { - options.setNavigationContext({ - pathname: options.cleanPathname, - searchParams: revalidateSearchParams, - params: options.params, - }); - - const { dynamicUsedInHandler, response } = await runAppRouteHandler({ - basePath: options.basePath, - consumeDynamicUsage: options.consumeDynamicUsage, - handlerFn: options.handlerFn, - i18n: options.i18n, - markDynamicUsage: options.markDynamicUsage, - params: options.params, - request: new Request(options.requestUrl, { method: "GET" }), - }); - - options.setNavigationContext(null); - - if (dynamicUsedInHandler) { - markKnownDynamicAppRoute(options.routePattern); - options.isrDebug?.("route regen skipped (dynamic usage)", options.cleanPathname); - return; - } - - const routeTags = options.buildPageCacheTags( - options.cleanPathname, - options.getCollectedFetchTags(), - ); - const routeCacheValue = await buildAppRouteCacheValue(response); - await options.isrSet(routeKey, routeCacheValue, options.revalidateSeconds, routeTags); - options.isrDebug?.("route regen complete", routeKey); - }); - }); - - options.isrDebug?.("STALE (route)", options.cleanPathname); - options.clearRequestContext(); - return applyRouteHandlerMiddlewareContext( - buildRouteHandlerCachedResponse(staleValue, { - cacheState: "STALE", - isHead: options.isAutoHead, - revalidateSeconds: options.revalidateSeconds, - }), - options.middlewareContext, - ); + // Route handlers re-render synchronously on expiry (unlike pages which use + // stale-while-revalidate). Returning null here causes the caller to fall + // through to __executeAppRouteHandler which runs the handler and updates + // the cache, then returns fresh data. This matches Next.js behavior where + // `fetch /route-handler` after the TTL returns fresh data on the same request. + options.isrDebug?.("STALE (route) — re-rendering synchronously", options.cleanPathname); + return null; } } catch (routeCacheError) { console.error("[vinext] ISR route cache read error:", routeCacheError); diff --git a/packages/vinext/src/server/app-route-handler-response.ts b/packages/vinext/src/server/app-route-handler-response.ts index 8a84310f7..8cf67bd85 100644 --- a/packages/vinext/src/server/app-route-handler-response.ts +++ b/packages/vinext/src/server/app-route-handler-response.ts @@ -65,6 +65,7 @@ export function buildRouteHandlerCachedResponse( } } headers.set("X-Vinext-Cache", options.cacheState); + headers.set("X-Nextjs-Cache", options.cacheState); headers.set( "Cache-Control", buildRouteHandlerCacheControl(options.cacheState, options.revalidateSeconds), @@ -85,6 +86,7 @@ export function applyRouteHandlerRevalidateHeader( export function markRouteHandlerCacheMiss(response: Response): void { response.headers.set("X-Vinext-Cache", "MISS"); + response.headers.set("X-Nextjs-Cache", "MISS"); } export async function buildAppRouteCacheValue(response: Response): Promise { diff --git a/packages/vinext/src/server/app-ssr-entry.ts b/packages/vinext/src/server/app-ssr-entry.ts index 32d754c47..a3b8559a8 100644 --- a/packages/vinext/src/server/app-ssr-entry.ts +++ b/packages/vinext/src/server/app-ssr-entry.ts @@ -215,7 +215,14 @@ export async function handleSsr( return htmlStream.pipeThrough(createTickBufferedTransform(rscEmbed, injectHTML)); } finally { - setNavigationContext(null); + // Don't clear navigation context here: the `finally` runs when + // renderToReadableStream resolves (shell ready), but deferred Suspense + // content is still streaming. Clearing the context prematurely causes + // client components inside Suspense boundaries (e.g. useSearchParams) + // to render with null context and produce wrong output (e.g. "N/A"). + // The ALS scope from runWithNavigationContext provides per-request + // isolation — the state object lives as long as its async chain does + // and is GC'd when the stream completes. clearServerInsertedHTML(); } }) as Promise>; diff --git a/packages/vinext/src/server/app-ssr-stream.ts b/packages/vinext/src/server/app-ssr-stream.ts index cfd7b53d3..a0bc46bc0 100644 --- a/packages/vinext/src/server/app-ssr-stream.ts +++ b/packages/vinext/src/server/app-ssr-stream.ts @@ -100,20 +100,42 @@ export function createTickBufferedTransform( let injected = false; let buffered: string[] = []; let timeoutId: ReturnType | null = null; + // Hold back everything from "" onwards so finalScripts can be + // injected before rather than appended after . + let trailer: string | null = null; const flushBuffered = (controller: TransformStreamDefaultController): void => { for (const chunk of buffered) { + let out = chunk; + if (!injected) { - const headEnd = chunk.indexOf(""); + const headEnd = out.indexOf(""); if (headEnd !== -1) { - const before = chunk.slice(0, headEnd); - const after = chunk.slice(headEnd); - controller.enqueue(encoder.encode(before + injectHTML + after)); + out = out.slice(0, headEnd) + injectHTML + out.slice(headEnd); injected = true; + } + } + + // Detect and hold back from that point as the "trailer". + // The trailer is emitted in flush() together with finalScripts so + // the document always ends with …scripts…. + if (trailer === null) { + const bodyEnd = out.lastIndexOf(""); + if (bodyEnd !== -1) { + const before = out.slice(0, bodyEnd); + trailer = out.slice(bodyEnd); // "..." + if (before) controller.enqueue(encoder.encode(before)); continue; } + } else { + // Already have a trailer — accumulate subsequent content into it + // (deferred Suspense scripts that arrive after in the same + // flush cycle). + trailer += out; + continue; } - controller.enqueue(encoder.encode(chunk)); + + controller.enqueue(encoder.encode(out)); } buffered = []; }; @@ -130,7 +152,18 @@ export function createTickBufferedTransform( const rscScripts = rscEmbed.flush(); if (rscScripts) { - controller.enqueue(encoder.encode(rscScripts)); + // If the trailer has already been captured, inject mid-stream RSC + // scripts before instead of after it. + if (trailer !== null) { + const bodyEnd = trailer.indexOf(""); + if (bodyEnd !== -1) { + trailer = trailer.slice(0, bodyEnd) + rscScripts + trailer.slice(bodyEnd); + } else { + trailer += rscScripts; + } + } else { + controller.enqueue(encoder.encode(rscScripts)); + } } } catch { // Stream was cancelled between when the timeout was registered and @@ -155,7 +188,21 @@ export function createTickBufferedTransform( } const finalScripts = await rscEmbed.finalize(); - if (finalScripts) { + + // Emit the trailer (...) with finalScripts injected + // just before so the document always ends with . + if (trailer !== null) { + const bodyEnd = trailer.indexOf(""); + if (bodyEnd !== -1) { + const combined = + trailer.slice(0, bodyEnd) + (finalScripts || "") + trailer.slice(bodyEnd); + controller.enqueue(encoder.encode(combined)); + } else { + // Unexpected: no in trailer — emit as-is with scripts appended + controller.enqueue(encoder.encode(trailer + (finalScripts || ""))); + } + } else if (finalScripts) { + // Fallback: was not seen anywhere (e.g. error/empty page) controller.enqueue(encoder.encode(finalScripts)); } }, diff --git a/packages/vinext/src/server/dev-server.ts b/packages/vinext/src/server/dev-server.ts index ad46e2553..73f6e1958 100644 --- a/packages/vinext/src/server/dev-server.ts +++ b/packages/vinext/src/server/dev-server.ts @@ -164,6 +164,10 @@ async function streamPageToResponse( const headers: Record = { "Content-Type": "text/html", "Transfer-Encoding": "chunked", + // Include RSC vary tokens so flight-request responses carry the correct + // caching hints. writeHead() takes precedence over any Vary: Origin + // that the Vite CORS middleware may have set via res.setHeader() earlier. + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept, Accept-Encoding", }; if (extraHeaders) { for (const [key, val] of Object.entries(extraHeaders)) { diff --git a/packages/vinext/src/server/pages-api-route.ts b/packages/vinext/src/server/pages-api-route.ts index 2cf395a48..dda19b629 100644 --- a/packages/vinext/src/server/pages-api-route.ts +++ b/packages/vinext/src/server/pages-api-route.ts @@ -10,7 +10,9 @@ import { } from "./pages-node-compat.js"; type PagesApiRouteModule = { - default?: (req: PagesReqResRequest, res: PagesReqResResponse) => void | Promise; + default?: + | ((req: PagesReqResRequest, res: PagesReqResResponse) => void | Promise) + | ((req: Request) => Response | Promise); }; export type PagesApiRouteMatch = { @@ -54,6 +56,24 @@ export async function handlePagesApiRoute(options: HandlePagesApiRouteOptions): try { const query = buildPagesApiQuery(options.url, params); + + // Detect edge runtime handlers: they accept a Web API Request and return a + // Response directly, rather than using the Node.js-style (req, res) API. + // We identify them by checking the module's config export or by duck-typing + // the return value — if the handler returns a Response instance, use it. + const routeModule = route.module as PagesApiRouteModule & { config?: { runtime?: string } }; + const isEdgeRuntime = routeModule.config?.runtime === "edge"; + + if (isEdgeRuntime) { + const result = await (handler as (req: Request) => Response | Promise)( + options.request, + ); + if (result instanceof Response) { + return result; + } + return new Response("Edge API route did not return a Response", { status: 500 }); + } + const body = await parsePagesApiBody(options.request); const { req, res, responsePromise } = createPagesReqRes({ body, @@ -62,7 +82,15 @@ export async function handlePagesApiRoute(options: HandlePagesApiRouteOptions): url: options.url, }); - await handler(req, res); + // Call the handler. For edge-style handlers that slipped past the config + // check (e.g. no explicit config export), duck-type the return value: if + // it's a Response, return it directly instead of waiting on responsePromise. + const handlerResult = await ( + handler as (req: PagesReqResRequest, res: PagesReqResResponse) => unknown + )(req as PagesReqResRequest, res as PagesReqResResponse); + if (handlerResult instanceof Response) { + return handlerResult; + } res.end(); return await responsePromise; } catch (error) { diff --git a/packages/vinext/src/server/pages-page-data.ts b/packages/vinext/src/server/pages-page-data.ts index 7451a15c0..764bbfd87 100644 --- a/packages/vinext/src/server/pages-page-data.ts +++ b/packages/vinext/src/server/pages-page-data.ts @@ -157,6 +157,7 @@ function buildPagesCacheResponse( const headers: Record = { "Content-Type": "text/html", "X-Vinext-Cache": cacheState, + "X-Nextjs-Cache": cacheState, "Cache-Control": cacheState === "HIT" ? `s-maxage=${revalidateSeconds ?? 60}, stale-while-revalidate` diff --git a/packages/vinext/src/server/pages-page-response.ts b/packages/vinext/src/server/pages-page-response.ts index 73c514b51..6f6acc559 100644 --- a/packages/vinext/src/server/pages-page-response.ts +++ b/packages/vinext/src/server/pages-page-response.ts @@ -266,7 +266,10 @@ export async function renderPagesPageResponse( ); } - const responseHeaders = new Headers({ "Content-Type": "text/html" }); + const responseHeaders = new Headers({ + "Content-Type": "text/html", + Vary: "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept", + }); const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes); if (options.isrRevalidateSeconds) { @@ -275,6 +278,7 @@ export async function renderPagesPageResponse( `s-maxage=${options.isrRevalidateSeconds}, stale-while-revalidate`, ); responseHeaders.set("X-Vinext-Cache", "MISS"); + responseHeaders.set("X-Nextjs-Cache", "MISS"); } if (options.fontLinkHeader) { responseHeaders.set("Link", options.fontLinkHeader); diff --git a/packages/vinext/src/server/prod-server.ts b/packages/vinext/src/server/prod-server.ts index 523910ccc..dfdb6aeec 100644 --- a/packages/vinext/src/server/prod-server.ts +++ b/packages/vinext/src/server/prod-server.ts @@ -783,13 +783,19 @@ async function sendWebResponse( // Use streaming flush modes so progressive HTML remains decodable before the // full response completes. const compressor = createCompressor(encoding!, "streaming"); - pipeline(nodeStream, compressor, res, () => { - /* ignore pipeline errors on closed connections */ - }); + await new Promise((resolve) => + pipeline(nodeStream, compressor, res, () => { + /* ignore pipeline errors on closed connections */ + resolve(); + }), + ); } else { - pipeline(nodeStream, res, () => { - /* ignore pipeline errors on closed connections */ - }); + await new Promise((resolve) => + pipeline(nodeStream, res, () => { + /* ignore pipeline errors on closed connections */ + resolve(); + }), + ); } } @@ -812,8 +818,11 @@ export async function startProdServer(options: ProdServerOptions = {}) { const resolvedOutDir = path.resolve(outDir); const clientDir = path.join(resolvedOutDir, "client"); - // Detect build type - const rscEntryPath = path.join(resolvedOutDir, "server", "index.js"); + // Detect build type — check both .js and .mjs (rolldown emits .mjs when the + // project root has no package.json with "type": "module") + const rscEntryPathJs = path.join(resolvedOutDir, "server", "index.js"); + const rscEntryPathMjs = path.join(resolvedOutDir, "server", "index.mjs"); + const rscEntryPath = fs.existsSync(rscEntryPathJs) ? rscEntryPathJs : rscEntryPathMjs; const serverEntryPath = path.join(resolvedOutDir, "server", "entry.js"); const isAppRouter = fs.existsSync(rscEntryPath); @@ -844,28 +853,47 @@ type WorkerAppRouterEntry = { fetch(request: Request, env?: unknown, ctx?: ExecutionContextLike): Promise | Response; }; -function createNodeExecutionContext(): ExecutionContextLike { +type AppRouterHandlerResult = { response: Response; drain: () => Promise }; + +function createTrackedNodeExecutionContext(): { + ctx: ExecutionContextLike; + drain: () => Promise; +} { + const pending: Promise[] = []; return { - waitUntil(promise: Promise) { - // Node doesn't provide a Workers lifecycle, but we still attach a - // rejection handler so background waitUntil work doesn't surface as an - // unhandled rejection when a Worker-style entry is used with vinext start. - void Promise.resolve(promise).catch(() => {}); + ctx: { + waitUntil(promise: Promise) { + // Track each promise so the Node.js request handler can drain them + // (await background cache writes) before releasing the connection. + pending.push(Promise.resolve(promise).catch(() => {})); + }, + passThroughOnException() {}, }, - passThroughOnException() {}, + drain: () => Promise.allSettled(pending).then(() => {}), }; } -function resolveAppRouterHandler(entry: unknown): (request: Request) => Promise { +function resolveAppRouterHandler( + entry: unknown, +): (request: Request) => Promise { if (typeof entry === "function") { - return (request) => Promise.resolve(entry(request)); + return async (request) => { + const { ctx, drain } = createTrackedNodeExecutionContext(); + const response = await Promise.resolve( + (entry as (r: Request, ctx: ExecutionContextLike) => Promise)(request, ctx), + ); + return { response, drain }; + }; } if (entry && typeof entry === "object" && "fetch" in entry) { const workerEntry = entry as WorkerAppRouterEntry; if (typeof workerEntry.fetch === "function") { - return (request) => - Promise.resolve(workerEntry.fetch(request, undefined, createNodeExecutionContext())); + return async (request) => { + const { ctx, drain } = createTrackedNodeExecutionContext(); + const response = await Promise.resolve(workerEntry.fetch(request, undefined, ctx)); + return { response, drain }; + }; } } @@ -1042,10 +1070,18 @@ async function startAppRouterServer(options: AppRouterServerOptions) { // Convert Node.js request to Web Request and call the RSC handler const request = nodeToWebRequest(req, normalizedUrl); - const response = await rscHandler(request); + const { response, drain } = await rscHandler(request); - // Stream the Web Response back to the Node.js response + // Stream the Web Response back to the Node.js response. + // sendWebResponse awaits the pipeline so the response body is fully + // sent before we drain waitUntil promises (e.g. ISR cache writes). await sendWebResponse(response, req, res, compress); + + // Drain any background work (cache writes) registered via waitUntil. + // On Node.js there is no platform-managed lifecycle, so we await them + // here — AFTER the pipeline — to ensure the ISR cache is populated + // before the next request for the same route arrives. + await drain(); } catch (e) { console.error("[vinext] Server error:", e); if (!res.headersSent) { diff --git a/packages/vinext/src/server/seed-cache.ts b/packages/vinext/src/server/seed-cache.ts index abbc6f43b..3977c121f 100644 --- a/packages/vinext/src/server/seed-cache.ts +++ b/packages/vinext/src/server/seed-cache.ts @@ -49,6 +49,8 @@ type PrerenderManifestRoute = { revalidate?: number | false; path?: string; router?: "app" | "pages"; + /** Extra ISR cache tags (e.g. unstable_cache tags) stored during prerender. */ + tags?: string[]; }; // ─── Public API ─────────────────────────────────────────────────────────────── @@ -89,11 +91,20 @@ export async function seedMemoryCacheFromPrerender(serverDir: string): Promise { - return seconds !== undefined ? { revalidate: seconds } : {}; +function revalidateCtx( + seconds: number | undefined, + pathname: string, + extraTags?: string[], +): Record { + // Generate the same implicit path tags as __pageCacheTags in app-rsc-entry.ts + const tags: string[] = [pathname, `_N_T_${pathname}`]; + tags.push("_N_T_/layout"); + const segments = pathname.split("/"); + let built = ""; + for (let i = 1; i < segments.length; i++) { + if (segments[i]) { + built += "/" + segments[i]; + tags.push(`_N_T_${built}/layout`); + } + } + tags.push(`_N_T_${built}/page`); + + // Merge in extra tags from the prerender manifest (unstable_cache tags, etc.) + if (extraTags) { + for (const t of extraTags) { + if (!tags.includes(t)) tags.push(t); + } + } + + const ctx: Record = { tags }; + if (seconds !== undefined) { + ctx.revalidate = seconds; + } + return ctx; } /** @@ -122,6 +165,7 @@ async function seedHtml( pathname: string, trailingSlash: boolean, revalidateSeconds: number | undefined, + extraTags?: string[], ): Promise { const relPath = getOutputPath(pathname, trailingSlash); const fullPath = path.join(prerenderDir, relPath); @@ -137,7 +181,7 @@ async function seedHtml( }; const key = baseKey + ":html"; - await handler.set(key, htmlValue, revalidateCtx(revalidateSeconds)); + await handler.set(key, htmlValue, revalidateCtx(revalidateSeconds, pathname, extraTags)); if (revalidateSeconds !== undefined) { setRevalidateDuration(key, revalidateSeconds); @@ -156,6 +200,7 @@ async function seedRsc( baseKey: string, pathname: string, revalidateSeconds: number | undefined, + extraTags?: string[], ): Promise { const relPath = getRscOutputPath(pathname); const fullPath = path.join(prerenderDir, relPath); @@ -175,7 +220,7 @@ async function seedRsc( }; const key = baseKey + ":rsc"; - await handler.set(key, rscValue, revalidateCtx(revalidateSeconds)); + await handler.set(key, rscValue, revalidateCtx(revalidateSeconds, pathname, extraTags)); if (revalidateSeconds !== undefined) { setRevalidateDuration(key, revalidateSeconds); diff --git a/packages/vinext/src/shims/cache.ts b/packages/vinext/src/shims/cache.ts index 08f7254e6..5ae625a6e 100644 --- a/packages/vinext/src/shims/cache.ts +++ b/packages/vinext/src/shims/cache.ts @@ -17,7 +17,11 @@ * setCacheHandler(new MyCacheHandler()); */ -import { markDynamicUsage as _markDynamic } from "./headers.js"; +import { + markDynamicUsage as _markDynamic, + getHeadersAccessPhase, + _isDraftModeEnabled, +} from "./headers.js"; import { AsyncLocalStorage } from "node:async_hooks"; import { fnv1a64 } from "../utils/hash.js"; import { @@ -25,6 +29,7 @@ import { getRequestContext, runWithUnifiedStateMutation, } from "./unified-request-context.js"; +import { addCollectedFetchTags as _addCollectedFetchTags } from "./fetch-cache.js"; // --------------------------------------------------------------------------- // Lazy accessor for cache context — avoids circular imports with cache-runtime. @@ -193,7 +198,7 @@ export class MemoryCacheHandler implements CacheHandler { private store = new Map(); private tagRevalidatedAt = new Map(); - async get(key: string, _ctx?: Record): Promise { + async get(key: string, ctx?: Record): Promise { const entry = this.store.get(key); if (!entry) return null; @@ -209,8 +214,19 @@ export class MemoryCacheHandler implements CacheHandler { } // Check time-based expiry — return stale entry with cacheState="stale" - // instead of deleting, so ISR can serve stale-while-revalidate - if (entry.revalidateAt !== null && Date.now() > entry.revalidateAt) { + // instead of deleting, so ISR can serve stale-while-revalidate. + // If the caller provides a revalidate period (ctx.revalidate), compute its + // stale-at timestamp and take the MINIMUM of it and the stored stale-at. + // This matches Next.js semantics for the case where the current fetch uses a + // shorter TTL than what was stored (e.g. a force-cache entry with 1-year TTL + // being fetched with revalidate:3 should be treated as stale after 3 s). + const ctxRevalidate = typeof ctx?.revalidate === "number" ? ctx.revalidate : null; + const ctxStaleAt = ctxRevalidate !== null ? entry.lastModified + ctxRevalidate * 1000 : null; + const staleAt = + ctxStaleAt !== null && entry.revalidateAt !== null + ? Math.min(ctxStaleAt, entry.revalidateAt) + : (ctxStaleAt ?? entry.revalidateAt); + if (staleAt !== null && Date.now() > staleAt) { return { lastModified: entry.lastModified, value: entry.value, @@ -349,6 +365,9 @@ export async function revalidateTag( tag: string, profile?: string | { expire?: number }, ): Promise { + if (profile === undefined) { + console.warn('"revalidateTag" without the second argument is now deprecated'); + } // Resolve the profile to durations for the handler let durations: { expire?: number } | undefined; if (typeof profile === "string") { @@ -401,9 +420,14 @@ export function refresh(): void {} * fetches fresh data. Unlike `revalidateTag`, which uses stale-while-revalidate, * `updateTag` invalidates synchronously within the same request context. */ -export async function updateTag(tag: string): Promise { +export function updateTag(tag: string): Promise { + // Synchronous check — throws before any Promise is created, so callers + // that omit `await` still get a synchronous throw (matching Next.js behavior). + if (getHeadersAccessPhase() !== "action") { + throw new Error("updateTag can only be called from within a Server Action"); + } // Expire the tag immediately (same as revalidateTag without SWR) - await _getActiveHandler().revalidateTag(tag); + return _getActiveHandler().revalidateTag(tag); } /** @@ -696,25 +720,41 @@ export function unstable_cache Promise>( const argsKey = JSON.stringify(args); const cacheKey = `unstable_cache:${baseKey}:${argsKey}`; - // Try to get from cache. Check cacheState so time-expired entries - // trigger a re-fetch instead of being served indefinitely. - const existing = await _getActiveHandler().get(cacheKey, { - kind: "FETCH", - tags, - }); - if (existing?.value && existing.value.kind === "FETCH" && existing.cacheState !== "stale") { - try { - return deserializeUnstableCacheResult(existing.value.data.body) as Awaited>; - } catch { - // Corrupted entry, fall through to re-fetch + // Draft mode bypasses the cache — always fetch fresh data. + const inDraftMode = _isDraftModeEnabled(); + + // Always register the unstable_cache tags with the current render pass so + // the page's ISR cache entry includes them as dependency tags. Without this, + // revalidateTag/updateTag would invalidate the unstable_cache entry but NOT + // the page ISR entry, leaving the page serving stale HTML from cache. + _addCollectedFetchTags(tags); + + if (!inDraftMode) { + // Try to get from cache. Check cacheState so time-expired entries + // trigger a re-fetch instead of being served indefinitely. + const existing = await _getActiveHandler().get(cacheKey, { + kind: "FETCH", + tags, + }); + if (existing?.value && existing.value.kind === "FETCH" && existing.cacheState !== "stale") { + try { + return deserializeUnstableCacheResult(existing.value.data.body) as Awaited>; + } catch { + // Corrupted entry, fall through to re-fetch + } } } - // Cache miss — call the function inside the unstable_cache ALS scope - // so that headers()/cookies()/connection() can detect they're in a - // cache scope and throw an appropriate error. + // Cache miss (or draft mode bypass) — call the function inside the + // unstable_cache ALS scope so that headers()/cookies()/connection() + // can detect they're in a cache scope and throw an appropriate error. const result = await _unstableCacheAls.run(true, () => fn(...args)); + // In draft mode, skip storing to cache so draft results don't pollute it. + if (inDraftMode) { + return result; + } + // Store in cache using the FETCH kind const cacheValue: CachedFetchValue = { kind: "FETCH", diff --git a/packages/vinext/src/shims/fetch-cache.ts b/packages/vinext/src/shims/fetch-cache.ts index 6ccf73c38..32a118f0b 100644 --- a/packages/vinext/src/shims/fetch-cache.ts +++ b/packages/vinext/src/shims/fetch-cache.ts @@ -21,6 +21,8 @@ import { getCacheHandler, type CachedFetchValue } from "./cache.js"; import { getRequestExecutionContext } from "./request-context.js"; +import { getNavigationContext } from "./navigation.js"; +import { markDynamicUsage } from "./headers.js"; import { AsyncLocalStorage } from "node:async_hooks"; import { isInsideUnifiedScope, @@ -461,6 +463,38 @@ const _gFetch = globalThis as unknown as Record; const originalFetch: typeof globalThis.fetch = (_gFetch[_ORIG_FETCH_KEY] ??= globalThis.fetch) as typeof globalThis.fetch; +// --------------------------------------------------------------------------- +// Test-only fetch override — allows intercepting network calls made by +// patchedFetch. Stored on globalThis via Symbol.for so it is visible across +// Vite's separate RSC and SSR module instances without requiring an explicit +// import from test code. +// --------------------------------------------------------------------------- + +const _OVERRIDE_KEY = Symbol.for("vinext.fetchCache.override"); + +/** + * Return the effective fetch function: the override if one is set, otherwise + * the captured originalFetch. Called at the point of each network request so + * overrides set after module load are picked up immediately. + */ +function _getEffectiveFetch(): typeof globalThis.fetch { + return (_gFetch[_OVERRIDE_KEY] as typeof globalThis.fetch | undefined) ?? originalFetch; +} + +/** + * Override the fetch function used for all network calls inside patchedFetch. + * Pass null to remove the override and restore originalFetch. + * + * @internal For testing only — do not use in production code. + */ +export function setFetchOverride(fn: typeof globalThis.fetch | null): void { + if (fn) { + (_gFetch as Record)[_OVERRIDE_KEY] = fn; + } else { + delete (_gFetch as Record)[_OVERRIDE_KEY]; + } +} + // --------------------------------------------------------------------------- // AsyncLocalStorage for request-scoped fetch cache state. // Uses Symbol.for() on globalThis so the storage is shared across Vite's @@ -468,6 +502,18 @@ const originalFetch: typeof globalThis.fetch = (_gFetch[_ORIG_FETCH_KEY] ??= // --------------------------------------------------------------------------- export type FetchCacheState = { currentRequestTags: string[]; + /** Page/segment-level fetchCache export value (e.g. 'force-cache', 'force-no-store'). */ + pageFetchCachePolicy?: string; + /** When true, bypass the fetch cache for this request (incoming Cache-Control: no-cache). */ + bypassFetchCache?: boolean; + /** + * Minimum fetch-level revalidate seen during this render. + * Tracks the smallest `next.revalidate` value across all fetch() calls so + * the page-level ISR TTL can be derived from fetch revalidation times when + * no explicit `export const revalidate` is set on the page. + * `null` = no finite revalidate seen yet; `Infinity` = only force-cache seen. + */ + minFetchRevalidate: number | null; }; const _ALS_KEY = Symbol.for("vinext.fetchCache.als"); @@ -478,6 +524,9 @@ const _als = (_g[_ALS_KEY] ??= const _fallbackState = (_g[_FALLBACK_KEY] ??= { currentRequestTags: [], + pageFetchCachePolicy: undefined, + bypassFetchCache: false, + minFetchRevalidate: null, } satisfies FetchCacheState) as FetchCacheState; function _getState(): FetchCacheState { @@ -493,6 +542,25 @@ function _getState(): FetchCacheState { */ function _resetFallbackState(): void { _fallbackState.currentRequestTags = []; + _fallbackState.pageFetchCachePolicy = undefined; + _fallbackState.bypassFetchCache = false; + _fallbackState.minFetchRevalidate = null; +} + +/** + * Set the page/segment-level fetchCache policy for the current render. + * Called by the RSC entry before rendering, based on the route's `fetchCache` export. + */ +export function setPageFetchCachePolicy(policy: string | undefined): void { + _getState().pageFetchCachePolicy = policy; +} + +/** + * Set the bypass-fetch-cache flag for the current request. + * Called when the incoming HTTP request has `Cache-Control: no-cache`. + */ +export function setBypassFetchCache(bypass: boolean): void { + _getState().bypassFetchCache = bypass; } /** @@ -504,6 +572,42 @@ export function getCollectedFetchTags(): string[] { return [..._getState().currentRequestTags]; } +/** + * Register additional tags with the current render pass so they are included + * in `getCollectedFetchTags()` and thus written to the page's ISR cache tags. + * + * Called by `unstable_cache()` so that page ISR entries are also invalidated + * when `revalidateTag(tag)` is called for a tag used by `unstable_cache`. + */ +export function addCollectedFetchTags(tags: string[]): void { + if (tags.length === 0) return; + const reqTags = _getState().currentRequestTags; + for (const tag of tags) { + if (!reqTags.includes(tag)) { + reqTags.push(tag); + } + } +} + +/** + * Get the minimum fetch-level revalidate seen during this render. + * Used to derive the page ISR TTL when no explicit `export const revalidate` + * is set: if all fetches have `next: { revalidate: N }`, the page should + * revalidate at the same rate as the shortest-lived fetch. + * Returns null when no finite revalidate was observed. + */ +export function getMinFetchRevalidate(): number | null { + return _getState().minFetchRevalidate; +} + +/** Update the running minimum fetch revalidate for this render. */ +function _trackFetchRevalidate(seconds: number): void { + const state = _getState(); + if (state.minFetchRevalidate === null || seconds < state.minFetchRevalidate) { + state.minFetchRevalidate = seconds; + } +} + /** * Create a patched fetch function with Next.js caching semantics. * @@ -522,31 +626,86 @@ function createPatchedFetch(): typeof globalThis.fetch { const nextOpts = (init as ExtendedRequestInit | undefined)?.next as | NextFetchOptions | undefined; - const cacheDirective = init?.cache; + const rawCacheDirective = init?.cache; + + // ── Request-level bypass ────────────────────────────────────────────────── + // Incoming Cache-Control: no-cache from the HTTP client means "bypass the + // fetch cache for this request" — return a fresh response without reading + // from or writing to the cache. + const fetchState = _getState(); + if (fetchState.bypassFetchCache) { + const cleanInit = stripNextFromInit(init); + return _getEffectiveFetch()(input, cleanInit); + } + + // ── Page-level fetchCache policy override ───────────────────────────────── + // A route can export `fetchCache = 'force-cache'` to override per-fetch + // cache directives. When active, ALL fetches on the page are cached, + // regardless of per-fetch cache or next.revalidate options, matching + // Next.js segment-config semantics. + const pageFetchCachePolicy = fetchState.pageFetchCachePolicy; + const pageForceCacheAll = pageFetchCachePolicy === "force-cache"; + const cacheDirective: RequestInit["cache"] = pageForceCacheAll + ? "force-cache" + : rawCacheDirective; // Determine caching behavior: - // - cache: 'no-store' → skip cache entirely + // - cache: 'no-store' → skip cache entirely, mark page as dynamic // - cache: 'force-cache' → cache indefinitely (revalidate = Infinity) // - next.revalidate: false → same as 'no-store' - // - next.revalidate: 0 → same as 'no-store' + // - next.revalidate: 0 → same as 'no-store', mark page as dynamic // - next.revalidate: N → cache for N seconds + // - No cache/next options + default-cache policy → treat as force-cache // - No cache/next options → default behavior (no caching, pass-through) - // If no caching options at all, just pass through to original fetch + // If no caching options at all, handle based on page-level policy. if (!nextOpts && !cacheDirective) { - return originalFetch(input, init); + if (pageFetchCachePolicy === "default-cache") { + // `fetchCache = 'default-cache'`: unconfigured fetches use the Next.js + // production default, which is to cache them (same as force-cache). + // Fall through to the caching path below with implicit force-cache TTL. + } else { + // Pass-through with no caching. Record a metric when prerender metrics + // collection is active (mirrors Next.js diagnostics which logs all fetches + // during static generation, not just cached ones). + if (_gm[_METRICS_KEY]) { + const _ptUrl = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const _ptStart = Date.now(); + const _ptResp = await _getEffectiveFetch()(input, init); + _recordFetchMetric( + { + url: _ptUrl, + status: _ptResp.status, + cacheStatus: "MISS", + start: _ptStart, + end: Date.now(), + cacheReason: "no cache options", + }, + true, // dedupeByUrl: probe + render both call this for uncached fetches + ); + return _ptResp; + } + return _getEffectiveFetch()(input, init); + } } - // Explicit no-store or no-cache — bypass cache entirely + // Explicit no-store or no-cache — bypass cache entirely. + // Page-level force-cache overrides these individual-fetch bypass conditions. + // Mark dynamic usage so the page response gets Cache-Control: no-store, + // preventing the prerender phase from incorrectly ISR-seeding the page. + // NOTE: next.revalidate === false means "cache permanently" in Next.js, + // NOT "no-store". Only revalidate === 0 or cache: 'no-store' opt out. if ( - cacheDirective === "no-store" || - cacheDirective === "no-cache" || - nextOpts?.revalidate === false || - nextOpts?.revalidate === 0 + !pageForceCacheAll && + (cacheDirective === "no-store" || cacheDirective === "no-cache" || nextOpts?.revalidate === 0) ) { + // no-store / revalidate:0 fetches make the page dynamic — mark it so + // the response policy emits Cache-Control: no-store. + markDynamicUsage("no-store fetch"); // Strip the `next` property before passing to real fetch const cleanInit = stripNextFromInit(init); - return originalFetch(input, cleanInit); + return _getEffectiveFetch()(input, cleanInit); } // Safety: when per-user auth headers are present and the developer hasn't @@ -556,16 +715,23 @@ function createPatchedFetch(): typeof globalThis.fetch { // caching by using `cache: 'force-cache'` or `next: { revalidate: N }`. const hasExplicitCacheOpt = cacheDirective === "force-cache" || + nextOpts?.revalidate === false || (typeof nextOpts?.revalidate === "number" && nextOpts.revalidate > 0); if (!hasExplicitCacheOpt && hasAuthHeaders(input, init)) { const cleanInit = stripNextFromInit(init); - return originalFetch(input, cleanInit); + return _getEffectiveFetch()(input, cleanInit); } // Determine revalidation period let revalidateSeconds: number; - if (cacheDirective === "force-cache") { - // force-cache means cache indefinitely (we use a very large number) + if ( + cacheDirective === "force-cache" || + nextOpts?.revalidate === false || + (!nextOpts && !cacheDirective && pageFetchCachePolicy === "default-cache") + ) { + // force-cache / revalidate:false / default-cache: cache indefinitely. + // next: { revalidate: false } is Next.js's way of saying "cache permanently" + // (equivalent to force-cache, no expiration). This is NOT the same as no-store. revalidateSeconds = nextOpts?.revalidate && typeof nextOpts.revalidate === "number" ? nextOpts.revalidate @@ -581,11 +747,29 @@ function createPatchedFetch(): typeof globalThis.fetch { } else { // next: {} with no revalidate or tags — pass through const cleanInit = stripNextFromInit(init); - return originalFetch(input, cleanInit); + return _getEffectiveFetch()(input, cleanInit); } } + // Track the minimum finite revalidate for page ISR TTL derivation. + // Infinite (force-cache) fetches don't constrain the page TTL. + if (revalidateSeconds < 31536000) { + _trackFetchRevalidate(revalidateSeconds); + } + const tags = nextOpts?.tags ?? []; + + // Next.js enforces a maximum of 128 tags per fetch call and warns when exceeded. + const MAX_FETCH_TAGS = 128; + if (tags.length > MAX_FETCH_TAGS) { + const excessTags = tags.slice(MAX_FETCH_TAGS); + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + console.warn( + `[vinext] exceeded max tag count for ${url}, ignoring tags: ${excessTags.join(", ")}`, + ); + } + const effectiveTags = tags.length > MAX_FETCH_TAGS ? tags.slice(0, MAX_FETCH_TAGS) : tags; + let cacheKey: string; try { cacheKey = await buildFetchCacheKey(input, init); @@ -595,7 +779,7 @@ function createPatchedFetch(): typeof globalThis.fetch { err instanceof SkipCacheKeyGenerationError ) { const cleanInit = stripNextFromInit(init); - return originalFetch(input, cleanInit); + return _getEffectiveFetch()(input, cleanInit); } throw err; } @@ -603,37 +787,91 @@ function createPatchedFetch(): typeof globalThis.fetch { // Collect tags for this render pass const reqTags = _getState().currentRequestTags; - if (tags.length > 0) { - for (const tag of tags) { + if (effectiveTags.length > 0) { + for (const tag of effectiveTags) { if (!reqTags.includes(tag)) { reqTags.push(tag); } } } + // Capture the URL string once for metrics recording. + const _fetchUrl = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + const _mStart = Date.now(); + // Try cache first try { - const cached = await handler.get(cacheKey, { kind: "FETCH", tags }); + const cached = await handler.get(cacheKey, { + kind: "FETCH", + tags, + revalidate: revalidateSeconds, + }); if (cached?.value && cached.value.kind === "FETCH" && cached.cacheState !== "stale") { const cachedData = cached.value.data; - // Reconstruct a Response from the cached data - return new Response(cachedData.body, { + // Reconstruct a Response from the cached data, preserving the original URL. + // Response.url is read-only so we use Object.defineProperty. + const res = new Response(cachedData.body, { status: cachedData.status ?? 200, headers: cachedData.headers, }); + if (cachedData.url) { + Object.defineProperty(res, "url", { value: cachedData.url, configurable: true }); + } + + // Tag maintenance: ensure the current page's implicit path tag is included + // in the cache entry's tags so that revalidatePath() for the current page + // will invalidate this entry on the next access, even if the entry was + // originally cached during a different page's render. + // Example: a layout fetch cached during /page-A render carries tag + // _N_T_/page-A. When /page-B (same layout) renders and gets a HIT, + // revalidatePath('/page-B') would not invalidate it — until we add + // _N_T_/page-B here. We update the stored entry (fire-and-forget) so + // the tag is persisted for the next revalidatePath() check. + const _hitNavPathname = getNavigationContext()?.pathname; + const _hitPathTag = _hitNavPathname ? `_N_T_${_hitNavPathname}` : null; + const _hitStoredTags: string[] = Array.isArray(cached.value.tags) ? cached.value.tags : []; + if (_hitPathTag && !_hitStoredTags.includes(_hitPathTag)) { + const _hitUpdatedTags = [..._hitStoredTags, _hitPathTag]; + handler + .set( + cacheKey, + { ...cached.value, tags: _hitUpdatedTags }, + { + fetchCache: true, + tags: _hitUpdatedTags, + revalidate: (cached.value as CachedFetchValue).revalidate ?? revalidateSeconds, + }, + ) + .catch(() => {}); + } + + _recordFetchMetric({ + url: _fetchUrl, + status: cachedData.status ?? 200, + cacheStatus: "HIT", + start: _mStart, + end: Date.now(), + cacheReason: "fetch cache hit", + }); + return res; } - // Stale entry — we could do stale-while-revalidate here, but for fetch() - // the simpler approach is to just re-fetch (the page-level ISR handles SWR). - // However, if we have a stale entry, return it and trigger background refetch. + // Stale entry — stale-while-revalidate: return the stale response + // immediately while refreshing in the background (production/Workers). + // In dev mode (no executionContext / waitUntil), perform a synchronous + // foreground refetch so the response is always fresh and test-timing is + // deterministic — no reliance on background tasks completing before + // the next retry. if (cached?.value && cached.value.kind === "FETCH" && cached.cacheState === "stale") { const staleData = cached.value.data; - // Background refetch — deduped so only one in-flight refetch runs - // per cache key, preventing thundering herd on popular endpoints. + // Stale-while-revalidate: return the stale response immediately while + // refreshing in the background. Deduped so only one in-flight refetch + // runs per cache key. if (!pendingRefetches.has(cacheKey)) { const cleanInit = stripNextFromInit(init); - const refetchPromise = originalFetch(input, cleanInit) + const refetchPromise = _getEffectiveFetch()(input, cleanInit) .then(async (freshResp) => { // Only cache 200 responses — a transient error or unexpected // status must not overwrite previously-good cached data. @@ -703,11 +941,23 @@ function createPatchedFetch(): typeof globalThis.fetch { getRequestExecutionContext()?.waitUntil(refetchPromise); } - // Return stale data immediately - return new Response(staleData.body, { + // Return stale data immediately, preserving the original URL. + const staleRes = new Response(staleData.body, { status: staleData.status ?? 200, headers: staleData.headers, }); + if (staleData.url) { + Object.defineProperty(staleRes, "url", { value: staleData.url, configurable: true }); + } + _recordFetchMetric({ + url: _fetchUrl, + status: staleData.status ?? 200, + cacheStatus: "STALE", + start: _mStart, + end: Date.now(), + cacheReason: "stale-while-revalidate", + }); + return staleRes; } } catch (cacheErr) { // Cache read failed — fall through to network @@ -715,14 +965,37 @@ function createPatchedFetch(): typeof globalThis.fetch { } // Cache miss — fetch from network + const _netStart = Date.now(); const cleanInit = stripNextFromInit(init); - const response = await originalFetch(input, cleanInit); + const response = await _getEffectiveFetch()(input, cleanInit); + _recordFetchMetric({ + url: _fetchUrl, + status: response.status, + cacheStatus: "MISS", + start: _netStart, + end: Date.now(), + cacheReason: "fetch cache miss", + }); // Only cache 200 responses if (response.status === 200) { - // Clone before reading body + // Clone before reading so the original response body remains intact for + // the caller to consume normally. const cloned = response.clone(); const body = await cloned.text(); + + // Next.js refuses to cache responses over 2MB in development mode. + // Warn and skip caching to match the upstream behavior. + const DEV_CACHE_MAX_BYTES = 2 * 1024 * 1024; + if (process.env.NODE_ENV !== "production" && body.length > DEV_CACHE_MAX_BYTES) { + const url = + typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; + console.warn( + `Failed to set Next.js data cache for ${url}, items over 2MB can not be cached`, + ); + return response; + } + const headers: Record = {}; cloned.headers.forEach((v, k) => { // Never cache Set-Cookie headers — they are per-user and must not @@ -731,6 +1004,14 @@ function createPatchedFetch(): typeof globalThis.fetch { headers[k] = v; }); + // Build the full tag set for storage: developer-supplied tags + an + // implicit path tag derived from the current navigation context. + // This ensures revalidatePath() busts fetch cache entries that were + // stored during a render of that path, matching Next.js semantics. + const navPathname = getNavigationContext()?.pathname; + const pathTag = navPathname ? `_N_T_${navPathname}` : null; + const storageTags = pathTag && !tags.includes(pathTag) ? [...tags, pathTag] : tags; + const cacheValue: CachedFetchValue = { kind: "FETCH", data: { @@ -740,7 +1021,7 @@ function createPatchedFetch(): typeof globalThis.fetch { typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url, status: cloned.status, }, - tags, + tags: storageTags, revalidate: revalidateSeconds, }; @@ -748,7 +1029,7 @@ function createPatchedFetch(): typeof globalThis.fetch { handler .set(cacheKey, cacheValue, { fetchCache: true, - tags, + tags: storageTags, revalidate: revalidateSeconds, }) .catch((err) => { @@ -828,7 +1109,7 @@ export async function runWithFetchCache(fn: () => Promise): Promise { uCtx.currentRequestTags = []; }, fn); } - return _als.run({ currentRequestTags: [] }, fn); + return _als.run({ currentRequestTags: [], minFetchRevalidate: null }, fn); } /** @@ -850,3 +1131,84 @@ export function ensureFetchPatch(): void { export function getOriginalFetch(): typeof globalThis.fetch { return originalFetch; } + +// ─── Prerender fetch metrics collection ────────────────────────────────────── +// +// When enabled (via enablePrerenderMetricsCollection), every cacheable fetch +// call records a metric entry keyed by the current page's navPathname. +// Callers (prerender.ts) consume per-page metrics via consumePrerenderFetchMetrics(). + +/** Single fetch metric entry as logged to diagnostics/fetch-metrics.json. */ +export type PrerenderFetchMetric = { + url: string; + status: number; + /** 'HIT' | 'MISS' — whether the response came from cache */ + cacheStatus: string; + /** Unix ms timestamp when the fetch started */ + start: number; + /** Unix ms timestamp when the response was returned */ + end: number; + /** Human-readable reason for the cache decision */ + cacheReason: string; +}; + +const _METRICS_KEY = Symbol.for("vinext.prerenderFetchMetrics"); +const _gm = globalThis as unknown as Record; + +/** Start recording fetch metrics (called before prerender). */ +export function enablePrerenderMetricsCollection(): void { + _gm[_METRICS_KEY] = new Map(); +} + +/** Stop recording and clear the metrics store (called in finally). */ +export function disablePrerenderMetricsCollection(): void { + delete _gm[_METRICS_KEY]; +} + +/** + * Discard any metrics recorded so far for a specific pathname without returning them. + * Called in app-page-render.ts just before renderToReadableStream() so that + * probe-phase metrics are excluded from the final output — matching Next.js which + * has no separate probe phase and only records metrics from the RSC render. + */ +export function clearPrerenderFetchMetricsForPath(pathname: string): void { + const store = _gm[_METRICS_KEY] as Map | undefined; + if (!store) return; + store.delete(pathname); +} + +/** + * Return and remove the recorded metrics for a specific pathname. + * Used by renderUrl() in prerender.ts to collect per-page metrics. + */ +export function consumePrerenderFetchMetrics(pathname: string): PrerenderFetchMetric[] { + const store = _gm[_METRICS_KEY] as Map | undefined; + if (!store) return []; + const metrics = store.get(pathname) ?? []; + store.delete(pathname); + return metrics; +} + +/** + * Internal: record a single metric for the current render's navPathname. + * + * @param dedupeByUrl - When true, skip recording if the same URL has already + * been recorded for this pathname. Used for pass-through (uncached) fetches + * to avoid double-counting if the same uncached URL is fetched multiple + * times within a single render (e.g. via React Suspense retries or parallel + * fetches). Not used for cached fetches (HIT/MISS) which are always distinct + * diagnostic entries. + */ +function _recordFetchMetric(metric: PrerenderFetchMetric, dedupeByUrl = false): void { + const store = _gm[_METRICS_KEY] as Map | undefined; + if (!store) return; + const navPathname = getNavigationContext()?.pathname; + if (!navPathname) return; + const existing = store.get(navPathname); + if (existing) { + if (dedupeByUrl && existing.some((m) => m.url === metric.url)) return; + existing.push(metric); + } else { + store.set(navPathname, [metric]); + } +} diff --git a/packages/vinext/src/shims/headers.ts b/packages/vinext/src/shims/headers.ts index d27133016..c1e000a3a 100644 --- a/packages/vinext/src/shims/headers.ts +++ b/packages/vinext/src/shims/headers.ts @@ -28,6 +28,12 @@ export type HeadersContext = { mutableCookies?: RequestCookies; readonlyCookies?: RequestCookies; readonlyHeaders?: Headers; + /** + * The first dynamic-usage reason recorded during this request's render. + * Set by markDynamicUsage() when called with a reason. Used by the prerender + * phase to emit human-readable bailout diagnostics (e.g. "reason: headers"). + */ + dynamicUsageReason?: string; }; export type HeadersAccessPhase = "render" | "action" | "route-handler"; @@ -79,9 +85,19 @@ function _getState(): VinextHeadersShimState { /** * Mark the current render as requiring dynamic (uncached) rendering. * Called by connection(), cookies(), headers(), and noStore(). + * + * @param reason - Human-readable cause (e.g. "headers", "no-store fetch"). + * The first reason set during a render wins; subsequent calls do not + * overwrite it. Used by the prerender phase for bailout diagnostics. */ -export function markDynamicUsage(): void { - _getState().dynamicUsageDetected = true; +export function markDynamicUsage(reason?: string): void { + const state = _getState(); + state.dynamicUsageDetected = true; + // Record the first reason only; write through to headersContext so the + // prerender phase can read it from the shared reference after the render. + if (reason && state.headersContext && !state.headersContext.dynamicUsageReason) { + state.headersContext.dynamicUsageReason = reason; + } } // --------------------------------------------------------------------------- @@ -142,6 +158,15 @@ export function consumeDynamicUsage(): boolean { return used; } +/** + * Return the first dynamic-usage reason recorded during the current render, + * or null if none was set. Must be called while the request context is still + * active (before clearRequestContext / ALS scope exit). + */ +export function getDynamicUsageReason(): string | null { + return _getState().headersContext?.dynamicUsageReason ?? null; +} + function _setStatePhase( state: VinextHeadersShimState, phase: HeadersAccessPhase, @@ -160,6 +185,10 @@ export function setHeadersAccessPhase(phase: HeadersAccessPhase): HeadersAccessP return _setStatePhase(_getState(), phase); } +export function getHeadersAccessPhase(): HeadersAccessPhase { + return _getState().phase; +} + /** * Set the headers/cookies context for the current RSC render. * Called by the framework's RSC entry before rendering each request. @@ -519,7 +548,7 @@ export function headers(): Promise & Headers { return _decorateRejectedRequestApiPromise(state.headersContext.accessError); } - markDynamicUsage(); + markDynamicUsage("headers"); const readonlyHeaders = _getReadonlyHeaders(state.headersContext); return _decorateRequestApiPromise(Promise.resolve(readonlyHeaders), readonlyHeaders); } @@ -548,7 +577,7 @@ export function cookies(): Promise & RequestCookies { return _decorateRejectedRequestApiPromise(state.headersContext.accessError); } - markDynamicUsage(); + markDynamicUsage("headers"); const cookieStore = _areCookiesMutableInCurrentPhase() ? _getMutableCookies(state.headersContext) : _getReadonlyCookies(state.headersContext); @@ -611,6 +640,23 @@ type DraftModeResult = { disable(): void; }; +/** + * Check if draft mode is currently enabled for the request. + * Unlike draftMode(), this does not throw inside cache scopes or mark dynamic usage. + * Used by unstable_cache to bypass the cache in draft mode. + * @internal + */ +export function _isDraftModeEnabled(): boolean { + const state = _getState(); + if (!state.headersContext) return false; + try { + const secret = getDraftSecret(); + return state.headersContext.cookies.get(DRAFT_MODE_COOKIE) === secret; + } catch { + return false; + } +} + /** * Draft mode — check/toggle via a `__prerender_bypass` cookie. * @@ -619,13 +665,15 @@ type DraftModeResult = { * - `disable()`: clears the bypass cookie */ export async function draftMode(): Promise { - throwIfInsideCacheScope("draftMode()"); + // Note: draftMode() is intentionally NOT restricted inside unstable_cache or + // "use cache" — it is a special case that must be readable inside cached + // functions so that callers can check whether the cache should be bypassed. const state = _getState(); if (state.headersContext?.accessError) { throw state.headersContext.accessError; } - markDynamicUsage(); + markDynamicUsage("headers"); const secret = getDraftSecret(); const isEnabled = state.headersContext ? state.headersContext.cookies.get(DRAFT_MODE_COOKIE) === secret diff --git a/packages/vinext/src/shims/navigation.ts b/packages/vinext/src/shims/navigation.ts index bb1c1ab1f..74fd45be7 100644 --- a/packages/vinext/src/shims/navigation.ts +++ b/packages/vinext/src/shims/navigation.ts @@ -169,11 +169,24 @@ function _getGlobalAccessors(): _StateAccessors | undefined { let _serverContext: NavigationContext | null = null; let _serverInsertedHTMLCallbacks: Array<() => unknown> = []; +// Browser-side cross-module-instance key (issue #688 client analogue). +// Vite dev mode can create separate module instances for "use client" +// components. The ALS-backed globalAccessors fix only works server-side +// (navigation-state.ts uses node:async_hooks). On the browser we use a +// separate globalThis slot so every module instance reads the same context +// that app-browser-entry.ts wrote via setNavigationContext(). +const _BROWSER_CTX_KEY = Symbol.for("vinext.navigation.browserCtx"); +type _GlobalWithBrowserCtx = typeof globalThis & { [_BROWSER_CTX_KEY]?: NavigationContext | null }; + // These are overridden by navigation-state.ts on the server to use ALS. // The defaults check globalThis for cross-module-instance access (issue #688). let _getServerContext = (): NavigationContext | null => { const g = _getGlobalAccessors(); - return g ? g.getServerContext() : _serverContext; + if (g) return g.getServerContext(); + // Browser: read from shared globalThis slot so separate module instances + // (e.g. pre-bundled "use client" components) see the same context. + const slot = (globalThis as _GlobalWithBrowserCtx)[_BROWSER_CTX_KEY]; + return slot !== undefined ? slot : _serverContext; }; let _setServerContext = (ctx: NavigationContext | null): void => { const g = _getGlobalAccessors(); @@ -181,6 +194,8 @@ let _setServerContext = (ctx: NavigationContext | null): void => { g.setServerContext(ctx); } else { _serverContext = ctx; + // Also share via globalThis for browser cross-module-instance access. + (globalThis as _GlobalWithBrowserCtx)[_BROWSER_CTX_KEY] = ctx; } }; let _getInsertedHTMLCallbacks = (): Array<() => unknown> => { @@ -579,6 +594,17 @@ function syncCommittedUrlStateFromLocation(): boolean { } function getServerSearchParamsSnapshot(): ReadonlyURLSearchParams { + if (!isServer) { + // Browser: always use the same source as getSearchParamsSnapshot() so the + // server and client snapshots are identical during hydration. Using the + // navigation context here would risk a mismatch when the context is from + // a different module instance or hasn't been restored yet, which causes + // React to discard the SSR HTML and re-render from scratch. + // window.location.search is always correct: afterFiles rewrites change the + // internal path but not the browser URL or query string. + return getSearchParamsSnapshot(); + } + // Server: read from the request-scoped navigation context. const ctx = _getServerContext() as NavigationContextWithReadonlyCache | null; if (!ctx) { @@ -1335,6 +1361,16 @@ export function unauthorized(): never { throw new VinextNavigationError("NEXT_UNAUTHORIZED", `${HTTP_ERROR_FALLBACK_ERROR_CODE};401`); } +/** + * Rethrow errors thrown by notFound(), redirect(), forbidden(), unauthorized(). + * Call this in catch blocks to ensure navigation errors propagate correctly. + */ +export function unstable_rethrow(error: unknown): void { + if (error instanceof VinextNavigationError) { + throw error; + } +} + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- diff --git a/packages/vinext/src/shims/router.ts b/packages/vinext/src/shims/router.ts index f3bb4ea68..552294a6d 100644 --- a/packages/vinext/src/shims/router.ts +++ b/packages/vinext/src/shims/router.ts @@ -691,6 +691,12 @@ let _lastPathnameAndSearch = // any component calls useRouter(). if (typeof window !== "undefined") { window.addEventListener("popstate", (e: PopStateEvent) => { + // App Router pages handle popstate via window.__VINEXT_RSC_NAVIGATE__ in + // app-browser-entry.ts. Skip Pages Router handling to avoid triggering a + // hard reload (navigateClient falls back to window.location.href when + // window.__VINEXT_ROOT__ is not set, which it isn't for App Router pages). + if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") return; + const browserUrl = window.location.pathname + window.location.search; const appUrl = stripBasePath(window.location.pathname, __basePath) + window.location.search; diff --git a/packages/vinext/src/shims/unified-request-context.ts b/packages/vinext/src/shims/unified-request-context.ts index 975e009c4..6db9365b5 100644 --- a/packages/vinext/src/shims/unified-request-context.ts +++ b/packages/vinext/src/shims/unified-request-context.ts @@ -94,6 +94,7 @@ export function createRequestContext(opts?: Partial): Uni requestScopedCacheLife: null, _privateCache: null, currentRequestTags: [], + minFetchRevalidate: null, executionContext: _getInheritedExecutionContext(), // inherits from standalone ALS if present requestCache: new WeakMap(), ssrContext: null, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b5d5fbb5..ba2419486 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,7 +137,7 @@ catalogs: version: 0.3.5 nitro: specifier: npm:nitro-nightly@latest - version: 3.0.1-20260328-013310-5cd5cb25 + version: 3.0.1-20260320-182900-2218d454 nuqs: specifier: ^2.8.8 version: 2.8.8 @@ -212,7 +212,7 @@ importers: version: 3.1.1 '@unpic/react': specifier: 'catalog:' - version: 1.0.2(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.0.2(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) devDependencies: '@playwright/test': specifier: 'catalog:' @@ -231,13 +231,13 @@ importers: version: 7.0.0-dev.20260217.1 '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) image-size: specifier: 'catalog:' version: 2.0.2 next: specifier: 'catalog:' - version: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) playwright: specifier: 'catalog:' version: 1.58.2 @@ -252,19 +252,19 @@ importers: version: 5.9.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) vitest: specifier: npm:@voidzero-dev/vite-plus-test@0.1.12 - version: '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' benchmarks/vinext: dependencies: '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) react: specifier: 'catalog:' version: 19.2.4 @@ -280,25 +280,25 @@ importers: devDependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) examples/app-router-cloudflare: dependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -313,7 +313,7 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) @@ -323,16 +323,16 @@ importers: version: 4.20260313.1 vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) examples/app-router-nitro: dependencies: '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) nitro: specifier: 'catalog:' - version: nitro-nightly@3.0.1-20260328-013310-5cd5cb25(@emnapi/runtime@1.8.1)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(better-sqlite3@12.6.2)(chokidar@5.0.0)(jiti@2.6.1) + version: nitro-nightly@3.0.1-20260320-182900-2218d454(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(better-sqlite3@12.6.2)(chokidar@5.0.0)(jiti@2.6.1)(lru-cache@11.2.7)(miniflare@4.20260217.0)(sqlite3@6.0.1) react: specifier: 'catalog:' version: 19.2.4 @@ -344,11 +344,11 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) examples/app-router-playground: dependencies: @@ -360,7 +360,7 @@ importers: version: 2.0.13 '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) clsx: specifier: 'catalog:' version: 2.1.1 @@ -394,7 +394,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@tailwindcss/forms': specifier: 'catalog:' version: 0.5.10(tailwindcss@4.1.4) @@ -418,7 +418,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) postcss: specifier: 'catalog:' version: 8.5.3 @@ -430,10 +430,10 @@ importers: version: 5.9.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) @@ -445,19 +445,19 @@ importers: version: 1.6.0(@phosphor-icons/react@2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@phosphor-icons/react': specifier: 'catalog:' version: 2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 4.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -475,14 +475,14 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) examples/fumadocs-docs-template: dependencies: @@ -491,19 +491,19 @@ importers: version: 0.72.0 fumadocs-core: specifier: 'catalog:' - version: 16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) + version: 16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) fumadocs-mdx: specifier: 'catalog:' - version: 14.2.10(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 14.2.10(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) fumadocs-ui: specifier: 'catalog:' - version: 16.6.17(@takumi-rs/image-response@0.72.0)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.4) + version: 16.6.17(@takumi-rs/image-response@0.72.0)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.4) lucide-react: specifier: 'catalog:' version: 0.577.0(react@19.2.4) next: specifier: 'catalog:' - version: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -516,13 +516,13 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@tailwindcss/postcss': specifier: 'catalog:' version: 4.1.4 '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 4.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@types/mdx': specifier: 'catalog:' version: 2.0.13 @@ -537,10 +537,10 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) postcss: specifier: 'catalog:' version: 8.5.3 @@ -558,7 +558,7 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) @@ -567,13 +567,13 @@ importers: dependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) ms: specifier: 'catalog:' version: 3.0.0-canary.1 @@ -594,20 +594,20 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) examples/nextra-docs-template: dependencies: '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) react: specifier: 'catalog:' version: 19.2.4 @@ -623,7 +623,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@mdx-js/rollup': specifier: 'catalog:' version: 3.1.1 @@ -638,16 +638,16 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) typescript: specifier: 'catalog:' version: 5.9.3 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) @@ -656,10 +656,10 @@ importers: dependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) react: specifier: 'catalog:' version: 19.2.4 @@ -671,20 +671,20 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) examples/realworld-api-rest: dependencies: '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) react: specifier: 'catalog:' version: 19.2.4 @@ -699,11 +699,11 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' devDependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@types/node': specifier: 'catalog:' version: 25.2.3 @@ -718,7 +718,7 @@ importers: version: 5.9.3 vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) @@ -727,10 +727,10 @@ importers: dependencies: '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -745,7 +745,7 @@ importers: version: link:../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' devDependencies: '@types/node': specifier: 'catalog:' @@ -761,13 +761,13 @@ importers: version: 5.9.3 vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) packages/vinext: dependencies: '@unpic/react': specifier: 'catalog:' - version: 1.0.2(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.0.2(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og': specifier: 'catalog:' version: 0.8.6 @@ -779,13 +779,13 @@ importers: version: 0.0.7 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plugin-commonjs: specifier: 'catalog:' version: 0.10.4 vite-tsconfig-paths: specifier: 'catalog:' - version: 6.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(typescript@5.9.3) + version: 6.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(typescript@5.9.3) devDependencies: '@types/node': specifier: 'catalog:' @@ -798,22 +798,197 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react-server-dom-webpack: specifier: 'catalog:' version: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) + + tests/fixtures-repos/next.js: + dependencies: + '@vitejs/plugin-rsc': + specifier: 'catalog:' + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + cheerio: + specifier: ^0.22.0 + version: 0.22.0 + fs-extra: + specifier: ^11.3.4 + version: 11.3.4 + nanoid: + specifier: ^5.1.7 + version: 5.1.7 + react: + specifier: 'catalog:' + version: 19.2.4 + react-dom: + specifier: 'catalog:' + version: 19.2.4(react@19.2.4) + react-server-dom-webpack: + specifier: 'catalog:' + version: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + strip-ansi: + specifier: ^7.2.0 + version: 7.2.0 + vinext: + specifier: workspace:* + version: link:../../../packages/vinext + devDependencies: + '@next/mdx': + specifier: ^16.2.1 + version: 16.2.1(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4)) + '@next/playwright': + specifier: ^16.2.1 + version: 16.2.1(@playwright/test@1.58.2) + '@next/third-parties': + specifier: ^16.2.1 + version: 16.2.1(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + '@opentelemetry/api': + specifier: ^1.9.1 + version: 1.9.1 + '@opentelemetry/context-async-hooks': + specifier: ^2.6.1 + version: 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': + specifier: ^2.6.1 + version: 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': + specifier: ^0.214.0 + version: 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': + specifier: ^0.214.0 + version: 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-express': + specifier: ^0.62.0 + version: 0.62.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': + specifier: ^0.214.0 + version: 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': + specifier: ^2.6.1 + version: 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node': + specifier: ^0.214.0 + version: 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': + specifier: ^2.6.1 + version: 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': + specifier: ^2.6.1 + version: 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': + specifier: ^1.40.0 + version: 1.40.0 + '@react-three/offscreen': + specifier: ^0.0.8 + version: 0.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/cheerio': + specifier: 0.22.16 + version: 0.22.16 + '@types/cookie': + specifier: ^1.0.0 + version: 1.0.0 + '@types/cross-spawn': + specifier: ^6.0.6 + version: 6.0.6 + '@types/escape-string-regexp': + specifier: ^2.0.3 + version: 2.0.3 + '@types/expect': + specifier: ^24.3.2 + version: 24.3.2 + '@types/glob': + specifier: 7.1.1 + version: 7.1.1 + '@types/http-proxy': + specifier: ^1.17.17 + version: 1.17.17 + '@types/jspdf': + specifier: ^2.0.0 + version: 2.0.0 + '@types/node-fetch': + specifier: ^2.6.13 + version: 2.6.13 + '@types/pino': + specifier: ^7.0.5 + version: 7.0.5 + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 + '@types/sqlite3': + specifier: ^5.1.0 + version: 5.1.0 + '@vercel/og': + specifier: 'catalog:' + version: 0.8.6 + cookie: + specifier: ^1.1.1 + version: 1.1.1 + cross-spawn: + specifier: ^7.0.6 + version: 7.0.6 + escape-string-regexp: + specifier: ^5.0.0 + version: 5.0.0 + execa: + specifier: ^9.6.1 + version: 9.6.1 + expect: + specifier: ^30.3.0 + version: 30.3.0 + get-port: + specifier: ^7.2.0 + version: 7.2.0 + glob: + specifier: 7.1.7 + version: 7.1.7 + http-proxy: + specifier: ^1.18.1 + version: 1.18.1 + jspdf: + specifier: ^4.2.1 + version: 4.2.1 + monaco-editor: + specifier: ^0.55.1 + version: 0.55.1 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 + outdent: + specifier: ^0.8.0 + version: 0.8.0 + pino: + specifier: ^10.3.1 + version: 10.3.1 + react-syntax-highlighter: + specifier: ^16.1.1 + version: 16.1.1(react@19.2.4) + sqlite3: + specifier: ^6.0.1 + version: 6.0.1 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vite: + specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' + vite-plus: + specifier: 'catalog:' + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) + vitest: + specifier: npm:@voidzero-dev/vite-plus-test@0.1.12 + version: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' tests/fixtures/app-basic: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) fake-context-lib: specifier: file:./__test_packages__/fake-context-lib version: file:tests/fixtures/app-basic/__test_packages__/fake-context-lib @@ -837,17 +1012,17 @@ importers: version: link:../../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/app-cjs-violation: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -862,17 +1037,17 @@ importers: version: link:../../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/app-with-src: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -887,23 +1062,23 @@ importers: version: link:../../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/cf-app-basic: dependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) + version: 1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1)) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + version: 6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -918,23 +1093,23 @@ importers: version: link:../../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: specifier: 'catalog:' version: 4.66.0(@cloudflare/workers-types@4.20260313.1) devDependencies: vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/better-auth: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) better-auth: specifier: 'catalog:' - version: 1.4.18(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(better-sqlite3@12.6.2)(esbuild@0.27.3)(jiti@2.6.1)(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 1.4.18(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(better-sqlite3@12.6.2)(esbuild@0.27.3)(jiti@2.6.1)(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(yaml@2.8.3) better-sqlite3: specifier: 'catalog:' version: 12.6.2 @@ -953,19 +1128,19 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/next-intl: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) next-intl: specifier: 'catalog:' - version: 4.8.3(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + version: 4.8.3(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react: specifier: 'catalog:' version: 19.2.4 @@ -981,16 +1156,16 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/next-themes: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) next-themes: specifier: 'catalog:' version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1009,19 +1184,19 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/next-view-transitions: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) next-view-transitions: specifier: 'catalog:' - version: 0.3.5(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 0.3.5(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -1037,19 +1212,19 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/nuqs: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) nuqs: specifier: 'catalog:' - version: 2.8.8(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 2.8.8(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -1065,10 +1240,10 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/shadcn: dependencies: @@ -1083,7 +1258,7 @@ importers: version: 1.2.4(@types/react@19.2.14)(react@19.2.4) '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) class-variance-authority: specifier: 'catalog:' version: 0.7.1 @@ -1108,10 +1283,10 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/ecosystem/validator: dependencies: @@ -1130,10 +1305,10 @@ importers: version: link:../../../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/pages-basic: dependencies: @@ -1149,16 +1324,16 @@ importers: devDependencies: vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' vite-plus: specifier: 'catalog:' - version: 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) tests/fixtures/static-export: dependencies: '@vitejs/plugin-rsc': specifier: 'catalog:' - version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) react: specifier: 'catalog:' version: 19.2.4 @@ -1173,7 +1348,7 @@ importers: version: link:../../../packages/vinext vite: specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 - version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' packages: @@ -1181,6 +1356,14 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -1301,9 +1484,15 @@ packages: '@date-fns/tz@1.4.1': resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -1498,6 +1687,19 @@ packages: tailwindcss: optional: true + '@gar/promise-retry@1.0.3': + resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@grpc/grpc-js@1.14.3': + resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.8.0': + resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==} + engines: {node: '>=6'} + hasBin: true + '@heroicons/react@2.2.0': resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} peerDependencies: @@ -1656,6 +1858,34 @@ packages: cpu: [x64] os: [win32] + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jest/diff-sequences@30.3.0': + resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.3.0': + resolution: {integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.3.0': + resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1675,6 +1905,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -1689,15 +1922,31 @@ packages: peerDependencies: rollup: '>=2' - '@napi-rs/wasm-runtime@1.1.2': - resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} '@next/env@16.1.7': resolution: {integrity: sha512-rJJbIdJB/RQr2F1nylZr/PJzamvNNhfr3brdKP6s/GW850jbtR70QlSfFselvIBbcPUOlQwBakexjFzqLzF6pg==} + '@next/mdx@16.2.1': + resolution: {integrity: sha512-w0YOkOc+WEnsTJ8uxzBOvpe3R+9BnJOxWCE7qcI/62CzJiUEd8JKtF25e3R8cW5BGsKyRW8p4zE2JLyXKa8xdw==} + peerDependencies: + '@mdx-js/loader': '>=0.15.0' + '@mdx-js/react': '>=0.15.0' + peerDependenciesMeta: + '@mdx-js/loader': + optional: true + '@mdx-js/react': + optional: true + + '@next/playwright@16.2.1': + resolution: {integrity: sha512-I5pjOT0emZyXYtz+m10hdThE+6XvBZlFilsj8CkKEYnRFxDUFRfRRl2HVb3pAKFoLNfAPVHy9YR9KaTo0atYRg==} + peerDependencies: + '@playwright/test': '>=1.0.0' + peerDependenciesMeta: + '@playwright/test': + optional: true + '@next/swc-darwin-arm64@16.1.7': resolution: {integrity: sha512-b2wWIE8sABdyafc4IM8r5Y/dS6kD80JRtOGrUiKTsACFQfWWgUQ2NwoUX1yjFMXVsAwcQeNpnucF2ZrujsBBPg==} engines: {node: '>= 10'} @@ -1750,6 +1999,12 @@ packages: cpu: [x64] os: [win32] + '@next/third-parties@16.2.1': + resolution: {integrity: sha512-XyeT9WVBUdVXMrKFz0wTSrMc+O5JN2B08yU7JpK8YJiP/qBgc3q1kIfjop/pdnhT4W4oLjwXaqrMh7uWaoYILQ==} + peerDependencies: + next: ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + '@noble/ciphers@2.1.1': resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} engines: {node: '>= 20.19.0'} @@ -1770,6 +2025,198 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@npmcli/agent@4.0.0': + resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/fs@5.0.0': + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/redact@4.0.0': + resolution: {integrity: sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@opentelemetry/api-logs@0.214.0': + resolution: {integrity: sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/configuration@0.214.0': + resolution: {integrity: sha512-Q+awuEwxhETwIAXuxHvIY5ZMEP0ZqvxLTi9kclrkyVJppEUXYL3Bhiw3jYrxdHYMh0Y0tVInQH9FEZ1aMinvLA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/context-async-hooks@2.6.1': + resolution: {integrity: sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.6.1': + resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.214.0': + resolution: {integrity: sha512-SwmFRwO8mi6nndzbsjPgSFg7qy1WeNHRFD+s6uCsdiUDUt3+yzI2qiHE3/ub2f37+/CbeGcG+Ugc8Gwr6nu2Aw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.214.0': + resolution: {integrity: sha512-9qv2Tl/Hq6qc5pJCbzFJnzA0uvlb9DgM70yGJPYf3bA5LlLkRCpcn81i4JbcIH4grlQIWY6A+W7YG0LLvS1BAw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.214.0': + resolution: {integrity: sha512-IWAVvCO1TlpotRjFmhQFz9RSfQy5BsLtDRBtptSrXZRwfyRPpuql/RMe5zdmu0Gxl3ERDFwOzOqkf3bwy7Jzcw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-grpc@0.214.0': + resolution: {integrity: sha512-0NGxWHVYHgbp51SEzmsP+Hdups81eRs229STcSWHo3WO0aqY6RpJ9csxfyEtFgaNrBDv6UfOh0je4ss/ROS6XA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-http@0.214.0': + resolution: {integrity: sha512-Tx/59RmjBgkXJ3qnsD04rpDrVWL53LU/czpgLJh+Ab98nAroe91I7vZ3uGN9mxwPS0jsZEnmqmHygVwB2vRMlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-metrics-otlp-proto@0.214.0': + resolution: {integrity: sha512-pJIcghFGhx3VSCgP5U+yZx+OMNj0t+ttnhC8IjL5Wza7vWIczctF6t3AGcVQffi2dEqX+ZHANoBwoPR8y6RMKA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-prometheus@0.214.0': + resolution: {integrity: sha512-4TGYoZKebUWVuYkV6r5wS2dUF4zH7EbWFw/Uqz1ZM1tGHQeFT9wzHGXq3iSIXMUrwu5jRdxjfMaXrYejPu2kpQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.214.0': + resolution: {integrity: sha512-FWRZ7AWoTryYhthralHkfXUuyO3l7cRsnr49WcDio1orl2a7KxT8aDZdwQtV1adzoUvZ9Gfo+IstElghCS4zfw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.214.0': + resolution: {integrity: sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.214.0': + resolution: {integrity: sha512-ON0spYWb2yAdQ9b+ItNyK0c6qdtcs+0eVR4YFJkhJL7agfT8sHFg0e5EesauSRiTHPZHiDobI92k77q0lwAmqg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@2.6.1': + resolution: {integrity: sha512-km2/hD3inLTqtLnUAHDGz7ZP/VOyZNslrC/iN66x4jkmpckwlONW54LRPNI6fm09/musDtZga9EWsxgwnjGUlw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation-express@0.62.0': + resolution: {integrity: sha512-Tvx+vgAZKEQxU3Rx+xWLiR0mLxHwmk69/8ya04+VsV9WYh8w6Lhx5hm5yAMvo1wy0KqWgFKBLwSeo3sHCwdOww==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.214.0': + resolution: {integrity: sha512-FlkDhZDRjDJDcO2LcSCtjRpkal1NJ8y0fBqBhTvfAR3JSYY2jAIj1kSS5IjmEBt4c3aWv+u/lqLuoCDrrKCSKg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.214.0': + resolution: {integrity: sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.214.0': + resolution: {integrity: sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.214.0': + resolution: {integrity: sha512-IDP6zcyA24RhNZ289MP6eToIZcinlmirHjX8v3zKCQ2ZhPpt5cGwkN91tCth337lqHIgWcTy90uKRiX/SzALDw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.214.0': + resolution: {integrity: sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@2.6.1': + resolution: {integrity: sha512-Dvz9TA6cPqIbxolSzQ5x9br6iQlqdGhVYrm+oYc7pfJ7LaVXz8F0XIqhWbnKB5YvfZ6SUmabBUUxnvHs/9uhxA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@2.6.1': + resolution: {integrity: sha512-kKFMxBcjBZAC1vBch5mtZ/dJQvcAEKWga+c+q5iGgRLPIE6Mc649zEwMaCIQCzalziMJQiyUadFYMHmELB7AFw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@2.6.1': + resolution: {integrity: sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.214.0': + resolution: {integrity: sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.6.1': + resolution: {integrity: sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-node@0.214.0': + resolution: {integrity: sha512-gl2XvQBJuPjhGcw9SsnQO5qxChAPMuGRPFaD8lqtF+Cey91NgGUQ0sio2vlDFOSm3JOLzc44vL+OAfx1dXuZjg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.6.1': + resolution: {integrity: sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@2.6.1': + resolution: {integrity: sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.40.0': + resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} + engines: {node: '>=14'} + '@orama/orama@3.1.18': resolution: {integrity: sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA==} engines: {node: '>= 20.0.0'} @@ -1781,8 +2228,8 @@ packages: '@oxc-project/types@0.115.0': resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} - '@oxc-project/types@0.122.0': - resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@oxc-project/types@0.120.0': + resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==} '@oxfmt/binding-android-arm-eabi@0.40.0': resolution: {integrity: sha512-S6zd5r1w/HmqR8t0CTnGjFTBLDq2QKORPwriCHxo4xFNuhmOTABGjPaNvCJJVnrKBLsohOeiDX3YqQfJPF+FXw==} @@ -2153,6 +2600,9 @@ packages: react: '>= 16.8' react-dom: '>= 16.8' + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.58.2': resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} @@ -2170,6 +2620,36 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -2561,107 +3041,115 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-three/offscreen@0.0.8': + resolution: {integrity: sha512-YXPWYKSCqsqXBcOqLPhY9vA3PThJCOfzGFj2RfNqkEAOeClNsJ+PN9rxREu2QC3yWuKgFS5oD2pK9yuhErvI9w==} + peerDependencies: + '@react-three/fiber': '>=8.0.0' + react: '>=18.0' + react-dom: '>=18.0' + three: '>=0.133' + '@resvg/resvg-wasm@2.4.0': resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} engines: {node: '>= 10'} - '@rolldown/binding-android-arm64@1.0.0-rc.12': - resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + '@rolldown/binding-android-arm64@1.0.0-rc.10': + resolution: {integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.12': - resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.10': + resolution: {integrity: sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.12': - resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + '@rolldown/binding-darwin-x64@1.0.0-rc.10': + resolution: {integrity: sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.12': - resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.10': + resolution: {integrity: sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': - resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10': + resolution: {integrity: sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': - resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': + resolution: {integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': - resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': + resolution: {integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': - resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': + resolution: {integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': - resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': + resolution: {integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': - resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.10': + resolution: {integrity: sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': - resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10': + resolution: {integrity: sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': - resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10': + resolution: {integrity: sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.12': - resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@rolldown/pluginutils@1.0.0-rc.10': + resolution: {integrity: sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==} '@rolldown/pluginutils@1.0.0-rc.5': resolution: {integrity: sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==} @@ -2681,6 +3169,9 @@ packages: '@schummar/icu-type-parser@1.21.5': resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@shikijs/core@4.0.2': resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} engines: {node: '>=20'} @@ -2725,10 +3216,17 @@ packages: engines: {node: '>= 8.0.0'} hasBin: true + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@speed-highlight/core@1.2.14': resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} @@ -3087,47 +3585,126 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/cheerio@0.22.16': + resolution: {integrity: sha512-bSbnU/D4yzFdzLpp3+rcDj0aQQMIRUBNJU7azPxdqMpnexjUSvGJyDuOBQBHeOZh1mMKgsJm6Dy+LLh80Ew4tQ==} + + '@types/cookie@1.0.0': + resolution: {integrity: sha512-mGFXbkDQJ6kAXByHS7QAggRXgols0mAdP4MuXgloGY1tXokvzaFFM4SMqWvf7AH0oafI7zlFJwoGWzmhDqTZ9w==} + deprecated: This is a stub types definition. cookie provides its own type definitions, so you do not need this installed. + + '@types/cross-spawn@6.0.6': + resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/escape-string-regexp@2.0.3': + resolution: {integrity: sha512-OZjkKRpXVi7OccivLmCEYSKIhS8YJj5mC79yzguHlO69jTK0xkOHjmL7w7SHRiUQFYYZWFGyU4VYfeXH+GkYlQ==} + deprecated: This is a stub types definition. escape-string-regexp provides its own type definitions, so you do not need this installed. + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/events@3.0.3': + resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==} + + '@types/expect@24.3.2': + resolution: {integrity: sha512-5ev4tL5eBuX9wyC/SFHku1Sizyerg457LiwMgde3sq61TMHbnKjikzwsBLxLpFMflvKuWXfWVW0w3hZg4qml9w==} + deprecated: This is a stub types definition. expect provides its own type definitions, so you do not need this installed. + + '@types/glob@7.1.1': + resolution: {integrity: sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/http-proxy@1.17.17': + resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jspdf@2.0.0': + resolution: {integrity: sha512-oonYDXI4GegGaG7FFVtriJ+Yqlh4YR3L3NVDiwCEBVG7sbya19SoGx4MW4kg1MCMRPgkbbFTck8YKJL8PrkDfA==} + deprecated: This is a stub types definition. jspdf provides its own type definitions, so you do not need this installed. + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/minimatch@6.0.0': + resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==} + deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed. + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + '@types/node@25.2.3': resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} + '@types/pako@2.0.4': + resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + + '@types/pino@7.0.5': + resolution: {integrity: sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg==} + deprecated: This is a stub types definition. pino provides its own type definitions, so you do not need this installed. + + '@types/prismjs@1.26.6': + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} + + '@types/raf@3.4.3': + resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 + '@types/react-syntax-highlighter@15.5.13': + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/sqlite3@5.1.0': + resolution: {integrity: sha512-w25Gd6OzcN0Sb6g/BO7cyee0ugkiLgonhgGYfG+H0W9Ub6PUsC2/4R+KXy2tc80faPIWO3Qytbvr8gP1fU4siA==} + deprecated: This is a stub types definition. sqlite3 provides its own type definitions, so you do not need this installed. + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260217.1': resolution: {integrity: sha512-Mj8Mh+aTaGWURK65VuMxyOBhy+9OfXCHCL63zJPr/PqTNKub+GgGpGYKfSlbDBkqkB8UgURlI2CDRUoyOfvufQ==} cpu: [arm64] @@ -3334,6 +3911,15 @@ packages: cpu: [x64] os: [win32] + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3348,9 +3934,29 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-sequence-parser@1.1.1: resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -3366,9 +3972,27 @@ packages: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + base64-js@0.0.8: resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} engines: {node: '>= 0.4'} @@ -3461,6 +4085,16 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.13: + resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -3472,15 +4106,31 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cacache@20.0.4: + resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} + engines: {node: ^20.17.0 || >=22.9.0} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} caniuse-lite@1.0.30001776: resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==} + canvg@3.0.11: + resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} + engines: {node: '>=10.0.0'} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -3493,6 +4143,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + cheerio@0.22.0: + resolution: {integrity: sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==} + engines: {node: '>= 0.6'} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -3500,12 +4154,27 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -3516,15 +4185,26 @@ packages: collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -3533,6 +4213,9 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3559,9 +4242,18 @@ packages: resolution: {integrity: sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==} engines: {node: '>=16'} + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + css-select@1.2.0: + resolution: {integrity: sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==} + css-to-react-native@3.2.0: resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + css-what@2.1.3: + resolution: {integrity: sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -3570,6 +4262,10 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + date-fns-jalali@4.1.0-0: resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} @@ -3625,6 +4321,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3643,10 +4343,38 @@ packages: resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} engines: {node: '>=0.3.1'} + dom-serializer@0.1.1: + resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==} + + domelementtype@1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} + + domhandler@2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + + dompurify@3.3.3: + resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + + domutils@1.5.1: + resolution: {integrity: sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==} + + domutils@1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + emoji-regex-xs@2.0.1: resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} engines: {node: '>=10.0.0'} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -3654,31 +4382,51 @@ packages: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} + entities@1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} - env-runner@0.1.7: - resolution: {integrity: sha512-i7h96jxETJYhXy5grgHNJ9xNzCzWIn9Ck/VkkYgOlE4gOqknsLX3CmlVb5LmwNex8sOoLFVZLz+TIw/+b5rktA==} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + env-runner@0.1.6: + resolution: {integrity: sha512-fSb7X1zdda8k6611a6/SdSQpDe7a/bqMz2UWdbHjk9YWzpUR4/fn9YtE/hqgGQ2nhvVN0zUtcL1SRMKwIsDbAA==} hasBin: true peerDependencies: - '@netlify/runtime': ^4 - miniflare: ^4.20260317.3 + miniflare: ^4.0.0 peerDependenciesMeta: - '@netlify/runtime': - optional: true miniflare: optional: true error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} @@ -3690,9 +4438,17 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -3724,12 +4480,23 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + expect@30.3.0: + resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -3747,9 +4514,15 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-png@6.4.0: + resolution: {integrity: sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3759,9 +4532,20 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + fflate@0.7.4: resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -3769,11 +4553,35 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - framer-motion@12.38.0: - resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + + framer-motion@12.38.0: + resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@emotion/is-prop-valid': @@ -3786,6 +4594,17 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3906,10 +4725,33 @@ packages: next: optional: true + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + get-port@7.2.0: + resolution: {integrity: sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==} + engines: {node: '>=16'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -3920,14 +4762,26 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + glob@7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - h3@2.0.1-rc.20: - resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==} + h3@2.0.1-rc.18: + resolution: {integrity: sha512-2EdYEOIJwZHfhfdxvqZsmmUz4tgwzQSuzre+l50j+voHJV4m7j3zw2lYLgHoyfkCF9EAZcaH4ea0zH/hgcs9Yg==} engines: {node: '>=20.11.1'} hasBin: true peerDependencies: @@ -3936,6 +4790,22 @@ packages: crossws: optional: true + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} @@ -3970,14 +4840,50 @@ packages: resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} engines: {node: '>=6'} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + hookable@6.1.0: resolution: {integrity: sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw==} html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - httpxy@0.5.0: - resolution: {integrity: sha512-qwX7QX/rK2visT10/b7bSeZWQOMlSm3svTD0pZpU+vJjNUP0YHtNv4c3z+MO+MSnGuRFWJFdCZiV+7F7dXIOzg==} + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + htmlparser2@3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + httpxy@0.3.1: + resolution: {integrity: sha512-XjG/CEoofEisMrnFr0D6U6xOZ4mRfnwcYQ9qvvnT4lvnX8BoeA3x3WofB75D+vZwpaobFVkBIHrZzoK40w8XSw==} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} icu-minify@4.8.3: resolution: {integrity: sha512-65Av7FLosNk7bPbmQx5z5XG2Y3T2GFppcjiXh4z1idHeVgQxlDpAmkGoYI0eFzAvrOnjpWTL5FmPDhsdfRMPEA==} @@ -3990,6 +4896,14 @@ packages: engines: {node: '>=16.x'} hasBin: true + import-in-the-middle@3.0.0: + resolution: {integrity: sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==} + engines: {node: '>=18'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -4002,6 +4916,13 @@ packages: intl-messageformat@11.1.2: resolution: {integrity: sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==} + iobuffer@5.4.0: + resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -4015,6 +4936,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -4033,9 +4958,45 @@ packages: is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} + + jest-diff@30.3.0: + resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.3.0: + resolution: {integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.3.0: + resolution: {integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.3.0: + resolution: {integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.3.0: + resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -4043,6 +5004,9 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -4050,6 +5014,12 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jspdf@4.2.1: + resolution: {integrity: sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==} + kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -4203,18 +5173,65 @@ packages: linebreak@1.1.0: resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + lodash.assignin@4.2.0: + resolution: {integrity: sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==} + + lodash.bind@4.2.1: + resolution: {integrity: sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.filter@4.6.0: + resolution: {integrity: sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.foreach@4.5.0: + resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.map@4.6.0: + resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.pick@4.4.0: + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + deprecated: This package is deprecated. Use destructuring assignment syntax instead. + + lodash.reduce@4.6.0: + resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==} + + lodash.reject@4.6.0: + resolution: {integrity: sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==} + + lodash.some@4.6.0: + resolution: {integrity: sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + + lru-cache@11.2.7: + resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} + engines: {node: 20 || >=22} + lucide-react@0.577.0: resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} peerDependencies: @@ -4223,6 +5240,10 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-fetch-happen@15.0.5: + resolution: {integrity: sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==} + engines: {node: ^20.17.0 || >=22.9.0} + markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -4230,6 +5251,15 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -4391,6 +5421,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -4404,12 +5442,60 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@5.0.2: + resolution: {integrity: sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + motion-dom@12.38.0: resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} @@ -4446,6 +5532,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.1.7: + resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==} + engines: {node: ^18 || >=20} + hasBin: true + nanostores@1.1.0: resolution: {integrity: sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==} engines: {node: ^20.0.0 || >=22.0.0} @@ -4507,18 +5598,18 @@ packages: sass: optional: true - nf3@0.3.14: - resolution: {integrity: sha512-MjG9u/IlvSq5txxY0oug1sjrGZ2l37IuhExI1iPuwV4S3RcyRNGoy6xLwznH3ATK6PUAM4fbQVb4Rzy1L1nlzw==} + nf3@0.3.13: + resolution: {integrity: sha512-drDt0yl4d/yUhlpD0GzzqahSpA5eUNeIfFq0/aoZb0UlPY0ZwP4u1EfREVvZrYdEnJ3OU9Le9TrzbvWgEkkeKw==} - nitro-nightly@3.0.1-20260328-013310-5cd5cb25: - resolution: {integrity: sha512-Xz6qUb2y+9SLK4tYrPWDCbANcbj4hmE4ntXMLfhxhXrYLtvCC537icLTcP4Csghndp2clvoZfynfje6E/gSGOQ==} + nitro-nightly@3.0.1-20260320-182900-2218d454: + resolution: {integrity: sha512-WMom6BKMOh6n//WMcXUoeHxePoEyg21BkuXdYGWfTzESMbIIPmuvhoPryjKUOJcWAJXscf2cp+I5fWy0z3LPRg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: dotenv: '*' giget: '*' jiti: ^2.6.1 - rollup: ^4.60.0 + rollup: ^4.59.0 vite: ^7 || ^8 xml2js: ^0.6.2 zephyr-agent: ^0.1.15 @@ -4545,10 +5636,40 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-addon-api@8.7.0: + resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} + engines: {node: ^18 || ^20 || >= 21} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-gyp@12.2.0: + resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + npm-to-yarn@3.0.1: resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + nth-check@1.0.2: + resolution: {integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==} + nuqs@2.8.8: resolution: {integrity: sha512-LF5sw9nWpHyPWzMMu9oho3r9C5DvkpmBIg4LQN78sexIzGaeRx8DWr0uy3YiFx5i2QGZN1Qqcb+OAtEVRa2bnA==} peerDependencies: @@ -4582,6 +5703,10 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -4591,6 +5716,9 @@ packages: oniguruma-to-es@4.3.5: resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + outdent@0.8.0: + resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + oxfmt@0.40.0: resolution: {integrity: sha512-g0C3I7xUj4b4DcagevM9kgH6+pUHytikxUcn3/VUkvzTNaaXBeyZqb7IBsHwojeXm4mTBEC/aBjBTMVUkZwWUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4610,22 +5738,45 @@ packages: oxlint-tsgolint: optional: true + p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + pako@0.2.9: resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + parse-css-color@0.2.1: resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -4635,6 +5786,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + periscopic@4.0.2: resolution: {integrity: sha512-sqpQDUy8vgB7ycLkendSKS6HnVz1Rneoc3Rc+ZBUCe2pbqlVuCC5vF52l0NJ1aiMg/r1qfYF9/myz8CZeI2rjA==} @@ -4649,6 +5803,16 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pixelmatch@7.1.0: resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} hasBin: true @@ -4699,15 +5863,44 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + pretty-format@30.3.0: + resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4723,6 +5916,9 @@ packages: peerDependencies: react: ^19.2.4 + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-medium-image-zoom@5.4.1: resolution: {integrity: sha512-DD2iZYaCfAwiQGR8AN62r/cDJYoXhezlYJc5HY4TzBUGuGge43CptG0f7m0PEIM72aN6GfpjohvY1yYdtCJB7g==} peerDependencies: @@ -4767,6 +5963,12 @@ packages: '@types/react': optional: true + react-syntax-highlighter@16.1.1: + resolution: {integrity: sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -4779,6 +5981,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} @@ -4793,6 +5999,12 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -4826,6 +6038,17 @@ packages: remark@15.0.1: resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -4833,8 +6056,12 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.0.0-rc.12: - resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + rgbcolor@1.0.1: + resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} + engines: {node: '>= 0.8.15'} + + rolldown@1.0.0-rc.10: + resolution: {integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -4853,6 +6080,13 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + satori@0.16.0: resolution: {integrity: sha512-ZvHN3ygzZ8FuxjSNB+mKBiF/NIoqHzlBGbD0MJiT+MvSsFOvotnWOhdTjxKzhHRT2wPC1QbhLzx2q/Y83VhfYQ==} engines: {node: '>=16'} @@ -4890,6 +6124,10 @@ packages: resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} engines: {node: '>=20'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -4900,6 +6138,25 @@ packages: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4911,19 +6168,38 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - srvx@0.11.13: - resolution: {integrity: sha512-oknN6qduuMPafxKtHucUeG32Q963pjriA5g3/Bl05cwEsUe5VVbIU4qR9LrALHbipSCyBe+VmfDGGydqazDRkw==} - engines: {node: '>=20.16.0'} - hasBin: true + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} - srvx@0.11.9: - resolution: {integrity: sha512-97wWJS6F0KTKAhDlHVmBzMvlBOp5FiNp3XrLoodIgYJpXxgG5tE9rX4Pg7s46n2shI4wtEsMATTS1+rI3/ubzA==} + sqlite3@6.0.1: + resolution: {integrity: sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==} + engines: {node: '>=20.17.0'} + + srvx@0.11.12: + resolution: {integrity: sha512-AQfrGqntqVPXgP03pvBDN1KyevHC+KmYVqb8vVf4N+aomQqdhaZxjvoVp+AOm4u6x+GgNQY3MVzAUIn+TqwkOA==} engines: {node: '>=20.16.0'} hasBin: true + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackblur-canvas@2.7.0: + resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} + engines: {node: '>=0.1.14'} + std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string.prototype.codepointat@0.2.1: resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} @@ -4933,6 +6209,18 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -4963,6 +6251,14 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + svg-pathdata@6.0.3: + resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==} + engines: {node: '>=12.0.0'} + swr@2.4.0: resolution: {integrity: sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw==} peerDependencies: @@ -4994,6 +6290,20 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tar@7.5.13: + resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + engines: {node: '>=18'} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + third-party-capital@1.0.20: + resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==} + + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -5063,6 +6373,10 @@ packages: unicode-trie@2.0.0: resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -5087,6 +6401,10 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unpic@4.2.2: resolution: {integrity: sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw==} @@ -5207,6 +6525,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + validator@13.15.26: resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} engines: {node: '>= 0.10'} @@ -5247,6 +6568,10 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + webpack-sources@3.3.4: resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} @@ -5256,6 +6581,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + workerd@1.20260217.0: resolution: {integrity: sha512-6jVisS6wB6KbF+F9DVoDUy9p7MON8qZCFSaL8OcDUioMwknsUPFojUISu3/c30ZOZ24D4h7oqaahFc5C6huilw==} engines: {node: '>=16'} @@ -5271,6 +6601,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -5298,6 +6632,34 @@ packages: utf-8-validate: optional: true + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + yoga-layout@3.2.1: resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} @@ -5323,6 +6685,14 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/runtime@7.28.6': {} '@base-ui/react@1.2.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': @@ -5392,12 +6762,12 @@ snapshots: optionalDependencies: workerd: 1.20260217.0 - '@cloudflare/vite-plugin@1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1))': + '@cloudflare/vite-plugin@1.25.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(workerd@1.20260217.0)(wrangler@4.66.0(@cloudflare/workers-types@4.20260313.1))': dependencies: '@cloudflare/unenv-preset': 2.13.0(unenv@2.0.0-rc.24)(workerd@1.20260217.0) miniflare: 4.20260217.0 unenv: 2.0.0-rc.24 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' wrangler: 4.66.0(@cloudflare/workers-types@4.20260313.1) ws: 8.18.0 transitivePeerDependencies: @@ -5432,11 +6802,22 @@ snapshots: '@date-fns/tz@1.4.1': {} + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.27.3': optional: true @@ -5565,6 +6946,21 @@ snapshots: optionalDependencies: tailwindcss: 4.1.4 + '@gar/promise-retry@1.0.3': + optional: true + + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.4 + yargs: 17.7.2 + '@heroicons/react@2.2.0(react@19.2.4)': dependencies: react: 19.2.4 @@ -5665,23 +7061,54 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@jridgewell/gen-mapping@0.3.13': + '@isaacs/fs-minipass@4.0.1': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + minipass: 7.1.3 - '@jridgewell/remapping@2.3.5': + '@jest/diff-sequences@30.3.0': {} + + '@jest/expect-utils@30.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@jest/get-type': 30.1.0 - '@jridgewell/resolve-uri@3.1.2': {} + '@jest/get-type@30.1.0': {} - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 25.2.3 + jest-regex-util: 30.0.1 - '@jridgewell/trace-mapping@0.3.31': + '@jest/schemas@30.0.5': dependencies: - '@jridgewell/resolve-uri': 3.1.2 + '@sinclair/typebox': 0.34.49 + + '@jest/types@30.3.0': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.2.3 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping@0.3.9': @@ -5689,6 +7116,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@js-sdsl/ordered-map@4.4.2': {} + '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -5734,14 +7163,25 @@ snapshots: transitivePeerDependencies: - supports-color - '@napi-rs/wasm-runtime@1.1.2(@emnapi/runtime@1.8.1)': + '@napi-rs/wasm-runtime@1.1.1': dependencies: + '@emnapi/core': 1.8.1 '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true '@next/env@16.1.7': {} + '@next/mdx@16.2.1(@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.4))': + dependencies: + source-map: 0.7.6 + optionalDependencies: + '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.4) + + '@next/playwright@16.2.1(@playwright/test@1.58.2)': + optionalDependencies: + '@playwright/test': 1.58.2 + '@next/swc-darwin-arm64@16.1.7': optional: true @@ -5766,6 +7206,12 @@ snapshots: '@next/swc-win32-x64-msvc@16.1.7': optional: true + '@next/third-parties@16.2.1(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': + dependencies: + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + third-party-capital: 1.0.20 + '@noble/ciphers@2.1.1': {} '@noble/hashes@2.0.1': {} @@ -5782,13 +7228,289 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@npmcli/agent@4.0.0': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 11.2.7 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + optional: true + + '@npmcli/fs@5.0.0': + dependencies: + semver: 7.7.4 + optional: true + + '@npmcli/redact@4.0.0': + optional: true + + '@opentelemetry/api-logs@0.214.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/configuration@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + yaml: 2.8.3 + + '@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.214.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-logs-otlp-http@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.214.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-logs-otlp-proto@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-metrics-otlp-grpc@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-metrics-otlp-http@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-metrics-otlp-proto@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-prometheus@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-trace-otlp-http@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-trace-otlp-proto@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/exporter-zipkin@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/instrumentation-express@0.62.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + import-in-the-middle: 3.0.0 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-grpc-exporter-base@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.214.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + protobufjs: 7.5.4 + + '@opentelemetry/propagator-b3@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/propagator-jaeger@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-logs@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-metrics@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-node@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + '@opentelemetry/configuration': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + + '@opentelemetry/sdk-trace-node@2.6.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/semantic-conventions@1.40.0': {} + '@orama/orama@3.1.18': {} '@oxc-project/runtime@0.115.0': {} '@oxc-project/types@0.115.0': {} - '@oxc-project/types@0.122.0': {} + '@oxc-project/types@0.120.0': {} '@oxfmt/binding-android-arm-eabi@0.40.0': optional: true @@ -5987,6 +7709,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + '@pinojs/redact@0.4.0': {} + '@playwright/test@1.58.2': dependencies: playwright: 1.58.2 @@ -6005,6 +7729,29 @@ snapshots: '@poppinss/exception@1.2.3': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -6402,59 +8149,62 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@react-three/offscreen@0.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + mitt: 3.0.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + '@resvg/resvg-wasm@2.4.0': {} - '@rolldown/binding-android-arm64@1.0.0-rc.12': + '@rolldown/binding-android-arm64@1.0.0-rc.10': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + '@rolldown/binding-darwin-arm64@1.0.0-rc.10': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.12': + '@rolldown/binding-darwin-x64@1.0.0-rc.10': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + '@rolldown/binding-freebsd-x64@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/runtime@1.8.1)': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.10': dependencies: - '@napi-rs/wasm-runtime': 1.1.2(@emnapi/runtime@1.8.1) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10': optional: true - '@rolldown/pluginutils@1.0.0-rc.12': {} + '@rolldown/pluginutils@1.0.0-rc.10': {} '@rolldown/pluginutils@1.0.0-rc.5': {} @@ -6468,6 +8218,8 @@ snapshots: '@schummar/icu-type-parser@1.21.5': {} + '@sec-ant/readable-stream@0.4.1': {} + '@shikijs/core@4.0.2': dependencies: '@shikijs/primitive': 4.0.2 @@ -6527,8 +8279,12 @@ snapshots: fflate: 0.7.4 string.prototype.codepointat: 0.2.1 + '@sinclair/typebox@0.34.49': {} + '@sindresorhus/is@7.2.0': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@speed-highlight/core@1.2.14': {} '@standard-schema/spec@1.0.0': {} @@ -6731,12 +8487,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.1.4 - '@tailwindcss/vite@4.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))': + '@tailwindcss/vite@4.2.0(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.0 '@tailwindcss/oxide': 4.2.0 tailwindcss: 4.2.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' '@takumi-rs/core-darwin-arm64@0.72.0': optional: true @@ -6793,46 +8549,133 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/cheerio@0.22.16': + dependencies: + '@types/node': 25.2.3 + + '@types/cookie@1.0.0': + dependencies: + cookie: 1.1.1 + + '@types/cross-spawn@6.0.6': + dependencies: + '@types/node': 25.2.3 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 '@types/deep-eql@4.0.2': {} + '@types/escape-string-regexp@2.0.3': + dependencies: + escape-string-regexp: 5.0.0 + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 '@types/estree@1.0.8': {} + '@types/events@3.0.3': {} + + '@types/expect@24.3.2': + dependencies: + expect: 30.3.0 + + '@types/glob@7.1.1': + dependencies: + '@types/events': 3.0.3 + '@types/minimatch': 6.0.0 + '@types/node': 25.2.3 + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 + '@types/http-proxy@1.17.17': + dependencies: + '@types/node': 25.2.3 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jspdf@2.0.0': + dependencies: + jspdf: 4.2.1 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 '@types/mdx@2.0.13': {} + '@types/minimatch@6.0.0': + dependencies: + minimatch: 10.2.5 + '@types/ms@2.1.0': {} + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 25.2.3 + form-data: 4.0.5 + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 + '@types/pako@2.0.4': {} + + '@types/pino@7.0.5': + dependencies: + pino: 10.3.1 + + '@types/prismjs@1.26.6': {} + + '@types/raf@3.4.3': + optional: true + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 + '@types/react-syntax-highlighter@15.5.13': + dependencies: + '@types/react': 19.2.14 + '@types/react@19.2.14': dependencies: csstype: 3.2.3 + '@types/sqlite3@5.1.0': + dependencies: + sqlite3: 6.0.1 + transitivePeerDependencies: + - supports-color + + '@types/stack-utils@2.0.3': {} + + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260217.1': optional: true @@ -6870,25 +8713,25 @@ snapshots: dependencies: unpic: 4.2.2 - '@unpic/react@1.0.2(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@unpic/react@1.0.2(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@unpic/core': 1.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@vercel/og@0.8.6': dependencies: '@resvg/resvg-wasm': 2.4.0 satori: 0.16.0 - '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))': + '@vitejs/plugin-react@6.0.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' - '@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': + '@vitejs/plugin-rsc@0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)': dependencies: '@rolldown/pluginutils': 1.0.0-rc.5 es-module-lexer: 2.0.0 @@ -6897,15 +8740,15 @@ snapshots: periscopic: 4.0.2 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - srvx: 0.11.9 + srvx: 0.11.12 strip-literal: 3.1.0 turbo-stream: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' - vitefu: 1.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)) + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' + vitefu: 1.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)) optionalDependencies: react-server-dom-webpack: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)': + '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)': dependencies: '@oxc-project/runtime': 0.115.0 '@oxc-project/types': 0.115.0 @@ -6917,6 +8760,7 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 typescript: 5.9.3 + yaml: 2.8.3 '@voidzero-dev/vite-plus-darwin-arm64@0.1.12': optional: true @@ -6930,11 +8774,11 @@ snapshots: '@voidzero-dev/vite-plus-linux-x64-gnu@0.1.12': optional: true - '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)': + '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) es-module-lexer: 1.7.0 obug: 2.1.1 pixelmatch: 7.1.0 @@ -6944,9 +8788,10 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.0.2 tinyglobby: 0.2.15 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' ws: 8.19.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 25.2.3 transitivePeerDependencies: - '@arethetypeswrong/core' @@ -6975,6 +8820,13 @@ snapshots: '@voidzero-dev/vite-plus-win32-x64-msvc@0.1.12': optional: true + abbrev@4.0.0: + optional: true + + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -6985,8 +8837,21 @@ snapshots: acorn@8.15.0: {} + agent-base@7.1.4: + optional: true + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + ansi-sequence-parser@1.1.1: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -6997,15 +8862,26 @@ snapshots: astring@1.9.0: {} + asynckit@0.4.0: {} + + atomic-sleep@1.0.0: {} + bail@2.0.2: {} + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + base64-arraybuffer@1.0.2: + optional: true + base64-js@0.0.8: {} base64-js@1.5.1: {} baseline-browser-mapping@2.10.0: {} - better-auth@1.4.18(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(better-sqlite3@12.6.2)(esbuild@0.27.3)(jiti@2.6.1)(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + better-auth@1.4.18(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(better-sqlite3@12.6.2)(esbuild@0.27.3)(jiti@2.6.1)(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(yaml@2.8.3): dependencies: '@better-auth/core': 1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0) '@better-auth/telemetry': 1.4.18(@better-auth/core@1.4.18(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)) @@ -7018,11 +8894,11 @@ snapshots: jose: 6.1.3 kysely: 0.28.11 nanostores: 1.1.0 - vitest: '@voidzero-dev/vite-plus-test@0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vitest: '@voidzero-dev/vite-plus-test@0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' zod: 4.3.6 optionalDependencies: better-sqlite3: 12.6.2 - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: @@ -7079,6 +8955,17 @@ snapshots: blake3-wasm@2.1.5: {} + boolbase@1.0.0: {} + + brace-expansion@1.1.13: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -7090,12 +8977,48 @@ snapshots: cac@6.7.14: {} + cacache@20.0.4: + dependencies: + '@npmcli/fs': 5.0.0 + fs-minipass: 3.0.3 + glob: 13.0.6 + lru-cache: 11.2.7 + minipass: 7.1.3 + minipass-collect: 2.0.1 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + p-map: 7.0.4 + ssri: 13.0.1 + optional: true + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + camelize@1.0.1: {} caniuse-lite@1.0.30001776: {} + canvg@3.0.11: + dependencies: + '@babel/runtime': 7.28.6 + '@types/raf': 3.4.3 + core-js: 3.49.0 + raf: 3.4.1 + regenerator-runtime: 0.13.11 + rgbcolor: 1.0.1 + stackblur-canvas: 2.7.0 + svg-pathdata: 6.0.3 + optional: true + ccount@2.0.1: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -7104,18 +9027,49 @@ snapshots: character-reference-invalid@2.0.1: {} + cheerio@0.22.0: + dependencies: + css-select: 1.2.0 + dom-serializer: 0.1.1 + entities: 1.1.2 + htmlparser2: 3.10.1 + lodash.assignin: 4.2.0 + lodash.bind: 4.2.1 + lodash.defaults: 4.2.0 + lodash.filter: 4.6.0 + lodash.flatten: 4.4.0 + lodash.foreach: 4.5.0 + lodash.map: 4.6.0 + lodash.merge: 4.6.2 + lodash.pick: 4.4.0 + lodash.reduce: 4.6.0 + lodash.reject: 4.6.0 + lodash.some: 4.6.0 + chokidar@5.0.0: dependencies: readdirp: 5.0.0 chownr@1.1.4: {} + chownr@3.0.0: {} + + ci-info@4.4.0: {} + + cjs-module-lexer@2.2.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} codehike@1.0.7: @@ -7130,25 +9084,38 @@ snapshots: collapse-white-space@2.1.0: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} compute-scroll-into-view@3.1.1: {} + concat-map@0.0.1: {} + consola@3.4.2: {} cookie@1.1.1: {} + core-js@3.49.0: + optional: true + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - crossws@0.4.4(srvx@0.11.13): + crossws@0.4.4(srvx@0.11.12): optionalDependencies: - srvx: 0.11.13 + srvx: 0.11.12 css-background-parser@0.1.0: {} @@ -7158,23 +9125,40 @@ snapshots: css-gradient-parser@0.0.16: {} + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + optional: true + + css-select@1.2.0: + dependencies: + boolbase: 1.0.0 + css-what: 2.1.3 + domutils: 1.5.1 + nth-check: 1.0.2 + css-to-react-native@3.2.0: dependencies: camelize: 1.0.1 css-color-keywords: 1.0.0 postcss-value-parser: 4.2.0 + css-what@2.1.3: {} + cssesc@3.0.0: {} csstype@3.2.3: {} + data-uri-to-buffer@4.0.1: {} + date-fns-jalali@4.1.0-0: {} date-fns@4.1.0: {} - db0@0.3.4(better-sqlite3@12.6.2): + db0@0.3.4(better-sqlite3@12.6.2)(sqlite3@6.0.1): optionalDependencies: better-sqlite3: 12.6.2 + sqlite3: 6.0.1 debug@4.4.3: dependencies: @@ -7194,6 +9178,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -7206,8 +9192,46 @@ snapshots: diff@5.2.2: {} + dom-serializer@0.1.1: + dependencies: + domelementtype: 1.3.1 + entities: 1.1.2 + + domelementtype@1.3.1: {} + + domhandler@2.4.2: + dependencies: + domelementtype: 1.3.1 + + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dompurify@3.3.3: + optionalDependencies: + '@types/trusted-types': 2.0.7 + optional: true + + domutils@1.5.1: + dependencies: + dom-serializer: 0.1.1 + domelementtype: 1.3.1 + + domutils@1.7.0: + dependencies: + dom-serializer: 0.1.1 + domelementtype: 1.3.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + emoji-regex-xs@2.0.1: {} + emoji-regex@8.0.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -7217,21 +9241,42 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + entities@1.1.2: {} + entities@6.0.1: {} - env-runner@0.1.7: + env-paths@2.2.1: + optional: true + + env-runner@0.1.6(miniflare@4.20260217.0): dependencies: - crossws: 0.4.4(srvx@0.11.13) - exsolve: 1.0.8 - httpxy: 0.5.0 - srvx: 0.11.13 + crossws: 0.4.4(srvx@0.11.12) + httpxy: 0.3.1 + srvx: 0.11.12 + optionalDependencies: + miniflare: 4.20260217.0 error-stack-parser-es@1.0.5: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -7275,8 +9320,12 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@5.0.0: {} estree-util-attach-comments@3.0.0: @@ -7318,9 +9367,36 @@ snapshots: dependencies: '@types/estree': 1.0.8 + eventemitter3@4.0.7: {} + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + expand-template@2.0.3: {} - exsolve@1.0.8: {} + expect@30.3.0: + dependencies: + '@jest/expect-utils': 30.3.0 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.3.0 + jest-message-util: 30.3.0 + jest-mock: 30.3.0 + jest-util: 30.3.0 + + exponential-backoff@3.1.3: + optional: true extend@3.0.2: {} @@ -7338,22 +9414,61 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fastq@1.20.1: + fast-png@6.4.0: + dependencies: + '@types/pako': 2.0.4 + iobuffer: 5.4.0 + pako: 2.1.0 + + fastq@1.20.1: dependencies: reusify: 1.1.0 + fault@1.0.4: + dependencies: + format: 0.2.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + fflate@0.7.4: {} + fflate@0.8.2: {} + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-uri-to-path@1.0.0: {} fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + format@0.2.2: {} + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded-parse@2.1.2: {} + framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: motion-dom: 12.38.0 @@ -7365,13 +9480,26 @@ snapshots: fs-constants@1.0.0: {} + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.3 + optional: true + + fs.realpath@1.0.0: {} + fsevents@2.3.2: optional: true fsevents@2.3.3: optional: true - fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6): + fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6): dependencies: '@formatjs/intl-localematcher': 0.8.1 '@orama/orama': 3.1.18 @@ -7403,21 +9531,21 @@ snapshots: '@types/mdast': 4.0.4 '@types/react': 19.2.14 lucide-react: 0.577.0(react@19.2.4) - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) zod: 4.3.6 transitivePeerDependencies: - supports-color - fumadocs-mdx@14.2.10(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): + fumadocs-mdx@14.2.10(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.27.3 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) + fumadocs-core: 16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) js-yaml: 4.1.1 mdast-util-mdx: 3.0.0 mdast-util-to-markdown: 2.1.2 @@ -7434,13 +9562,13 @@ snapshots: '@types/mdast': 4.0.4 '@types/mdx': 2.0.13 '@types/react': 19.2.14 - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' transitivePeerDependencies: - supports-color - fumadocs-ui@16.6.17(@takumi-rs/image-response@0.72.0)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.4): + fumadocs-ui@16.6.17(@takumi-rs/image-response@0.72.0)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.1.4): dependencies: '@fumadocs/tailwind': 0.0.3(tailwindcss@4.1.4) '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -7454,7 +9582,7 @@ snapshots: '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) class-variance-authority: 0.7.1 - fumadocs-core: 16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) + fumadocs-core: 16.6.17(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.577.0(react@19.2.4))(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) lucide-react: 0.577.0(react@19.2.4) motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -7470,14 +9598,43 @@ snapshots: '@takumi-rs/image-response': 0.72.0 '@types/mdx': 2.0.13 '@types/react': 19.2.14 - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) transitivePeerDependencies: - '@emotion/is-prop-valid' - '@types/react-dom' - tailwindcss + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-port@7.2.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + github-from-package@0.0.0: {} github-slugger@2.0.0: {} @@ -7486,16 +9643,46 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + optional: true + + glob@7.1.7: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + globrex@0.1.2: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} - h3@2.0.1-rc.20(crossws@0.4.4(srvx@0.11.13)): + h3@2.0.1-rc.18(crossws@0.4.4(srvx@0.11.12)): dependencies: rou3: 0.8.1 - srvx: 0.11.13 + srvx: 0.11.12 optionalDependencies: - crossws: 0.4.4(srvx@0.11.13) + crossws: 0.4.4(srvx@0.11.12) + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 hast-util-from-parse5@8.0.3: dependencies: @@ -7611,11 +9798,64 @@ snapshots: hex-rgb@4.3.0: {} + highlight.js@10.7.3: {} + + highlightjs-vue@1.0.0: {} + hookable@6.1.0: {} html-void-elements@3.0.0: {} - httpxy@0.5.0: {} + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + optional: true + + htmlparser2@3.10.1: + dependencies: + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + + http-cache-semantics@4.2.0: + optional: true + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.11 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + + httpxy@0.3.1: {} + + human-signals@8.0.1: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + optional: true icu-minify@4.8.3: dependencies: @@ -7625,6 +9865,18 @@ snapshots: image-size@2.0.2: {} + import-in-the-middle@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} ini@1.3.8: {} @@ -7638,6 +9890,11 @@ snapshots: '@formatjs/icu-messageformat-parser': 3.5.1 tslib: 2.8.1 + iobuffer@5.4.0: {} + + ip-address@10.1.0: + optional: true + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -7649,6 +9906,8 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -7663,18 +9922,87 @@ snapshots: dependencies: '@types/estree': 1.0.8 + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + isexe@2.0.0: {} + isexe@4.0.0: + optional: true + + jest-diff@30.3.0: + dependencies: + '@jest/diff-sequences': 30.3.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.3.0 + + jest-matcher-utils@30.3.0: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.3.0 + pretty-format: 30.3.0 + + jest-message-util@30.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 30.3.0 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + pretty-format: 30.3.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.3.0: + dependencies: + '@jest/types': 30.3.0 + '@types/node': 25.2.3 + jest-util: 30.3.0 + + jest-regex-util@30.0.1: {} + + jest-util@30.3.0: + dependencies: + '@jest/types': 30.3.0 + '@types/node': 25.2.3 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + jiti@2.6.1: {} jose@6.1.3: {} + js-tokens@4.0.0: {} + js-tokens@9.0.1: {} js-yaml@4.1.1: dependencies: argparse: 2.0.1 + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jspdf@4.2.1: + dependencies: + '@babel/runtime': 7.28.6 + fast-png: 6.4.0 + fflate: 0.8.2 + optionalDependencies: + canvg: 3.0.11 + core-js: 3.49.0 + dompurify: 3.3.3 + html2canvas: 1.4.1 + kleur@4.1.5: {} kysely@0.28.11: {} @@ -7778,14 +10106,48 @@ snapshots: base64-js: 0.0.8 unicode-trie: 2.0.0 + lodash.assignin@4.2.0: {} + + lodash.bind@4.2.1: {} + + lodash.camelcase@4.3.0: {} + lodash.castarray@4.4.0: {} + lodash.defaults@4.2.0: {} + + lodash.filter@4.6.0: {} + + lodash.flatten@4.4.0: {} + + lodash.foreach@4.5.0: {} + lodash.isplainobject@4.0.6: {} + lodash.map@4.6.0: {} + lodash.merge@4.6.2: {} + lodash.pick@4.4.0: {} + + lodash.reduce@4.6.0: {} + + lodash.reject@4.6.0: {} + + lodash.some@4.6.0: {} + + long@5.3.2: {} + longest-streak@3.1.0: {} + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + + lru-cache@11.2.7: + optional: true + lucide-react@0.577.0(react@19.2.4): dependencies: react: 19.2.4 @@ -7794,10 +10156,32 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + make-fetch-happen@15.0.5: + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/agent': 4.0.0 + '@npmcli/redact': 4.0.0 + cacache: 20.0.4 + http-cache-semantics: 4.2.0 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 6.1.0 + ssri: 13.0.1 + transitivePeerDependencies: + - supports-color + optional: true + markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} + marked@14.0.0: {} + + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -8232,6 +10616,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-response@3.1.0: {} mini-svg-data-uri@1.4.4: {} @@ -8248,10 +10638,67 @@ snapshots: - bufferutil - utf-8-validate + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.13 + minimist@1.2.8: {} + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.3 + optional: true + + minipass-fetch@5.0.2: + dependencies: + minipass: 7.1.3 + minipass-sized: 2.0.0 + minizlib: 3.1.0 + optionalDependencies: + iconv-lite: 0.7.2 + optional: true + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-sized@2.0.0: + dependencies: + minipass: 7.1.3 + optional: true + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + optional: true + + minipass@7.1.3: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + + mitt@3.0.1: {} + mkdirp-classic@0.5.3: {} + module-details-from-path@1.0.4: {} + + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + motion-dom@12.38.0: dependencies: motion-utils: 12.36.0 @@ -8274,6 +10721,8 @@ snapshots: nanoid@3.3.11: {} + nanoid@5.1.7: {} + nanostores@1.1.0: {} napi-build-utils@2.0.0: {} @@ -8284,14 +10733,14 @@ snapshots: next-intl-swc-plugin-extractor@4.8.3: {} - next-intl@4.8.3(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + next-intl@4.8.3(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@formatjs/intl-localematcher': 0.8.1 '@parcel/watcher': 2.5.6 '@swc/core': 1.15.11 icu-minify: 4.8.3 negotiator: 1.0.0 - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-intl-swc-plugin-extractor: 4.8.3 po-parser: 2.1.1 react: 19.2.4 @@ -8306,13 +10755,13 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next-view-transitions@0.3.5(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next-view-transitions@0.3.5(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 16.1.7 '@swc/helpers': 0.5.15 @@ -8331,33 +10780,34 @@ snapshots: '@next/swc-linux-x64-musl': 16.1.7 '@next/swc-win32-arm64-msvc': 16.1.7 '@next/swc-win32-x64-msvc': 16.1.7 + '@opentelemetry/api': 1.9.1 '@playwright/test': 1.58.2 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nf3@0.3.14: {} + nf3@0.3.13: {} - nitro-nightly@3.0.1-20260328-013310-5cd5cb25(@emnapi/runtime@1.8.1)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(better-sqlite3@12.6.2)(chokidar@5.0.0)(jiti@2.6.1): + nitro-nightly@3.0.1-20260320-182900-2218d454(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(better-sqlite3@12.6.2)(chokidar@5.0.0)(jiti@2.6.1)(lru-cache@11.2.7)(miniflare@4.20260217.0)(sqlite3@6.0.1): dependencies: consola: 3.4.2 - crossws: 0.4.4(srvx@0.11.13) - db0: 0.3.4(better-sqlite3@12.6.2) - env-runner: 0.1.7 - h3: 2.0.1-rc.20(crossws@0.4.4(srvx@0.11.13)) + crossws: 0.4.4(srvx@0.11.12) + db0: 0.3.4(better-sqlite3@12.6.2)(sqlite3@6.0.1) + env-runner: 0.1.6(miniflare@4.20260217.0) + h3: 2.0.1-rc.18(crossws@0.4.4(srvx@0.11.12)) hookable: 6.1.0 - nf3: 0.3.14 + nf3: 0.3.13 ocache: 0.1.4 ofetch: 2.0.0-alpha.3 ohash: 2.0.11 - rolldown: 1.0.0-rc.12(@emnapi/runtime@1.8.1) - srvx: 0.11.13 + rolldown: 1.0.0-rc.10 + srvx: 0.11.12 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@12.6.2)(sqlite3@6.0.1))(lru-cache@11.2.7)(ofetch@2.0.0-alpha.3) optionalDependencies: jiti: 2.6.1 - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -8368,11 +10818,8 @@ snapshots: - '@capacitor/preferences' - '@deno/kv' - '@electric-sql/pglite' - - '@emnapi/core' - - '@emnapi/runtime' - '@libsql/client' - '@netlify/blobs' - - '@netlify/runtime' - '@planetscale/database' - '@upstash/redis' - '@vercel/blob' @@ -8397,14 +10844,54 @@ snapshots: node-addon-api@7.1.1: {} + node-addon-api@8.7.0: {} + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-gyp@12.2.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + graceful-fs: 4.2.11 + make-fetch-happen: 15.0.5 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + tar: 7.5.13 + tinyglobby: 0.2.15 + which: 6.0.1 + transitivePeerDependencies: + - supports-color + optional: true + + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + optional: true + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + npm-to-yarn@3.0.1: {} - nuqs@2.8.8(next@16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): + nth-check@1.0.2: + dependencies: + boolbase: 1.0.0 + + nuqs@2.8.8(next@16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): dependencies: '@standard-schema/spec': 1.0.0 react: 19.2.4 optionalDependencies: - next: 16.1.7(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.7(@opentelemetry/api@1.9.1)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) obug@2.1.1: {} @@ -8416,6 +10903,8 @@ snapshots: ohash@2.0.11: {} + on-exit-leak-free@2.1.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8428,6 +10917,8 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + outdent@0.8.0: {} + oxfmt@0.40.0: dependencies: tinypool: 2.1.0 @@ -8484,8 +10975,13 @@ snapshots: '@oxlint/binding-win32-x64-msvc': 1.55.0 oxlint-tsgolint: 0.17.0 + p-map@7.0.4: + optional: true + pako@0.2.9: {} + pako@2.1.0: {} + parse-css-color@0.2.1: dependencies: color-name: 1.1.4 @@ -8501,18 +10997,33 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-ms@4.0.0: {} + parse5@7.3.0: dependencies: entities: 6.0.1 + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + path-key@4.0.0: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.7 + minipass: 7.1.3 + optional: true + path-to-regexp@6.3.0: {} path-to-regexp@8.3.0: {} pathe@2.0.3: {} + performance-now@2.1.0: + optional: true + periscopic@4.0.2: dependencies: '@types/estree': 1.0.8 @@ -8525,6 +11036,26 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + pixelmatch@7.1.0: dependencies: pngjs: 7.0.0 @@ -8586,8 +11117,40 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + pretty-format@30.3.0: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prismjs@1.30.0: {} + + proc-log@6.1.0: + optional: true + + process-warning@5.0.0: {} + property-information@7.1.0: {} + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.2.3 + long: 5.3.2 + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -8595,6 +11158,13 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + optional: true + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -8614,6 +11184,8 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 + react-is@18.3.1: {} + react-medium-image-zoom@5.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: react: 19.2.4 @@ -8654,6 +11226,16 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + react-syntax-highlighter@16.1.1(react@19.2.4): + dependencies: + '@babel/runtime': 7.28.6 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.30.0 + react: 19.2.4 + refractor: 5.0.0 + react@19.2.4: {} readable-stream@3.6.2: @@ -8664,6 +11246,8 @@ snapshots: readdirp@5.0.0: {} + real-require@0.2.0: {} + recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -8693,6 +11277,16 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + refractor@5.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/prismjs': 1.26.6 + hastscript: 9.0.1 + parse-entities: 4.0.2 + + regenerator-runtime@0.13.11: + optional: true + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -8767,33 +11361,44 @@ snapshots: transitivePeerDependencies: - supports-color + require-directory@2.1.1: {} + + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + requires-port@1.0.0: {} + reselect@5.1.1: {} reusify@1.1.0: {} - rolldown@1.0.0-rc.12(@emnapi/runtime@1.8.1): + rgbcolor@1.0.1: + optional: true + + rolldown@1.0.0-rc.10: dependencies: - '@oxc-project/types': 0.122.0 - '@rolldown/pluginutils': 1.0.0-rc.12 + '@oxc-project/types': 0.120.0 + '@rolldown/pluginutils': 1.0.0-rc.10 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.12 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 - '@rolldown/binding-darwin-x64': 1.0.0-rc.12 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/runtime@1.8.1) - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@rolldown/binding-android-arm64': 1.0.0-rc.10 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.10 + '@rolldown/binding-darwin-x64': 1.0.0-rc.10 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.10 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.10 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.10 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.10 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.10 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.10 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.10 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.10 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.10 rou3@0.7.12: {} @@ -8807,6 +11412,11 @@ snapshots: safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: + optional: true + satori@0.16.0: dependencies: '@shuding/opentype.js': 1.4.0-beta.0 @@ -8881,6 +11491,8 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + signal-exit@4.1.0: {} + simple-concat@1.0.1: {} simple-get@4.0.1: @@ -8895,18 +11507,71 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 + slash@3.0.0: {} + + smart-buffer@4.2.0: + optional: true + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + optional: true + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + optional: true + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} source-map@0.7.6: {} space-separated-tokens@2.0.2: {} - srvx@0.11.13: {} + split2@4.2.0: {} + + sqlite3@6.0.1: + dependencies: + bindings: 1.5.0 + node-addon-api: 8.7.0 + prebuild-install: 7.1.3 + tar: 7.5.13 + optionalDependencies: + node-gyp: 12.2.0 + transitivePeerDependencies: + - supports-color + + srvx@0.11.12: {} - srvx@0.11.9: {} + ssri@13.0.1: + dependencies: + minipass: 7.1.3 + optional: true + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackblur-canvas@2.7.0: + optional: true std-env@4.0.0: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string.prototype.codepointat@0.2.1: {} string_decoder@1.3.0: @@ -8918,6 +11583,16 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + strip-json-comments@2.0.1: {} strip-literal@3.1.0: @@ -8939,6 +11614,13 @@ snapshots: supports-color@10.2.2: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + svg-pathdata@6.0.3: + optional: true + swr@2.4.0(react@19.2.4): dependencies: dequal: 2.0.3 @@ -8972,6 +11654,25 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tar@7.5.13: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + optional: true + + third-party-capital@1.0.20: {} + + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + tiny-inflate@1.0.3: {} tinybench@2.9.0: {} @@ -9022,6 +11723,8 @@ snapshots: pako: 0.2.9 tiny-inflate: 1.0.3 + unicorn-magic@0.3.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -9064,12 +11767,15 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universalify@2.0.1: {} + unpic@4.2.2: {} - unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@12.6.2))(ofetch@2.0.0-alpha.3): + unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(better-sqlite3@12.6.2)(sqlite3@6.0.1))(lru-cache@11.2.7)(ofetch@2.0.0-alpha.3): optionalDependencies: chokidar: 5.0.0 - db0: 0.3.4(better-sqlite3@12.6.2) + db0: 0.3.4(better-sqlite3@12.6.2)(sqlite3@6.0.1) + lru-cache: 11.2.7 ofetch: 2.0.0-alpha.3 use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4): @@ -9110,6 +11816,11 @@ snapshots: util-deprecate@1.0.2: {} + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + optional: true + validator@13.15.26: {} vfile-location@5.0.3: @@ -9140,11 +11851,11 @@ snapshots: fast-glob: 3.3.3 magic-string: 0.30.21 - vite-plus@0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3): + vite-plus@0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3): dependencies: '@oxc-project/types': 0.115.0 - '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) - '@voidzero-dev/vite-plus-test': 0.1.12(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + '@voidzero-dev/vite-plus-core': 0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) + '@voidzero-dev/vite-plus-test': 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3) cac: 6.7.14 cross-spawn: 7.0.6 oxfmt: 0.40.0 @@ -9186,28 +11897,35 @@ snapshots: - vite - yaml - vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(typescript@5.9.3): + vite-tsconfig-paths@6.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3))(typescript@5.9.3): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' transitivePeerDependencies: - supports-color - typescript - vitefu@1.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)): + vitefu@1.1.1(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)): optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + vite: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)(yaml@2.8.3)' web-namespaces@2.0.1: {} + web-streams-polyfill@3.3.3: {} + webpack-sources@3.3.4: {} which@2.0.2: dependencies: isexe: 2.0.0 + which@6.0.1: + dependencies: + isexe: 4.0.0 + optional: true + workerd@1.20260217.0: optionalDependencies: '@cloudflare/workerd-darwin-64': 1.20260217.0 @@ -9233,12 +11951,41 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrappy@1.0.2: {} ws@8.18.0: {} ws@8.19.0: {} + y18n@5.0.8: {} + + yallist@4.0.0: + optional: true + + yallist@5.0.0: {} + + yaml@2.8.3: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yoctocolors@2.1.2: {} + yoga-layout@3.2.1: {} youch-core@0.3.3: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 687a4d0cd..e0bb64f5e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,6 +5,7 @@ packages: - packages/* - examples/* - tests/fixtures/* + - tests/fixtures-repos/* - tests/fixtures/ecosystem/* - benchmarks/vinext diff --git a/scripts/clone-submodule.js b/scripts/clone-submodule.js new file mode 100644 index 000000000..e8d503730 --- /dev/null +++ b/scripts/clone-submodule.js @@ -0,0 +1,87 @@ +#!/usr/bin/env node +// Usage: node scripts/clone-submodule.js --dir= --url= --sha= [--sparse=] + +import { execSync } from "node:child_process"; +import { rmSync, renameSync, readFileSync, writeFileSync } from "node:fs"; +import { resolve, join } from "node:path"; + +const flags = {}; + +for (const arg of process.argv.slice(2)) { + if (arg.startsWith("--")) { + const eq = arg.indexOf("="); + if (eq !== -1) { + flags[arg.slice(2, eq)] = arg.slice(eq + 1); + } else { + flags[arg.slice(2)] = true; + } + } +} + +const { dir, url, sha, sparse: sparseDir = null } = flags; + +if (!dir || !url || !sha) { + console.error( + "Usage: node scripts/clone-submodule.js --dir= --url= --sha= [--sparse=]", + ); + process.exit(1); +} + +const absDir = resolve(dir); + +function git(...args) { + const cmd = `git ${args.join(" ")}`; + console.log(` $ ${cmd}`); + execSync(cmd, { stdio: "inherit" }); +} + +function gitIn(cwd, ...args) { + const cmd = `git ${args.join(" ")}`; + console.log(` $ cd ${cwd} && ${cmd}`); + execSync(cmd, { cwd, stdio: "inherit" }); +} + +console.log(`\nCloning/updating submodule: ${dir}${sparseDir ? ` (sparse: ${sparseDir})` : ""}`); + +// Skip if already at the requested SHA +const shaFile = join(absDir, ".sha"); +try { + const cached = readFileSync(shaFile, "utf8").trim(); + if (cached === sha) { + console.log(` ✓ already at ${sha.slice(0, 12)}, skipping\n`); + process.exit(0); + } +} catch { + // no lockfile yet — proceed with clone +} + +// Always start fresh +rmSync(absDir, { recursive: true, force: true }); +git("init", absDir); + +// Add remote +gitIn(absDir, "remote", "add", "origin", url); + +// Configure sparse checkout before fetching +if (sparseDir) { + gitIn(absDir, "sparse-checkout", "set", "--no-cone", sparseDir); +} + +// Fetch the exact commit, reset, and clean +gitIn(absDir, "fetch", "--depth=1", "origin", sha); +gitIn(absDir, "reset", "--hard", sha); +gitIn(absDir, "clean", "-f", "-q"); + +// Hoist sparse subdir contents up to the target root +if (sparseDir) { + const nested = join(absDir, sparseDir); + const tmp = `${absDir}__tmp`; + renameSync(nested, tmp); + rmSync(absDir, { recursive: true, force: true }); + renameSync(tmp, absDir); +} + +// Write SHA lockfile so subsequent runs can skip +writeFileSync(join(absDir, ".sha"), sha); + +console.log(` ✓ ${dir} is at ${sha.slice(0, 12)}\n`); diff --git a/tests/__snapshots__/entry-templates.test.ts.snap b/tests/__snapshots__/entry-templates.test.ts.snap index 7c8a503e2..06bcf8f38 100644 --- a/tests/__snapshots__/entry-templates.test.ts.snap +++ b/tests/__snapshots__/entry-templates.test.ts.snap @@ -71,6 +71,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from "/p import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -92,7 +93,7 @@ import { } from "/packages/vinext/src/server/app-route-handler-response.js"; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from "/packages/vinext/src/shims/request-context.js"; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from "/packages/vinext/src/routing/route-trie.js"; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -1266,6 +1267,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -1392,6 +1396,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -1506,7 +1514,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -1622,7 +1630,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -1708,19 +1716,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (!match) { - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -1857,18 +1868,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). For validation purposes, use the most specific + // (innermost) value: page first, then layouts from innermost to outermost. + const dynamicParamsConfig = route.page?.dynamicParams ?? + [...(route.layouts || [])].reverse().find(l => l?.dynamicParams !== undefined)?.dynamicParams; // true (default) | false const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; @@ -1877,7 +1887,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -1896,7 +1906,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -1949,7 +1959,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -1976,6 +1986,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. const __dynamicParamsResponse = await __validateAppPageDynamicParams({ @@ -1984,7 +2000,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); }, enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, + // generateStaticParams may be on the page or on a layout (e.g. the blog + // layout exports it while the page exports dynamicParams = false). Fall + // back to the innermost layout that exports it. + generateStaticParams: route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, isDynamicRoute: route.isDynamic, logGenerateStaticParamsError(err) { console.error("[vinext] generateStaticParams error:", err); @@ -1995,6 +2015,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { return __dynamicParamsResponse; } + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); + } + // Check for intercepting routes on RSC requests (client-side navigation). // If the target URL matches an intercepting route in a parallel slot, // render the source route with the intercepting page in the slot. @@ -2265,6 +2292,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from "/p import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -2286,7 +2314,7 @@ import { } from "/packages/vinext/src/server/app-route-handler-response.js"; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from "/packages/vinext/src/shims/request-context.js"; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from "/packages/vinext/src/routing/route-trie.js"; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -3460,6 +3488,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -3589,6 +3620,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -3703,7 +3738,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -3819,7 +3854,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -3905,19 +3940,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (!match) { - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -4054,18 +4092,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). For validation purposes, use the most specific + // (innermost) value: page first, then layouts from innermost to outermost. + const dynamicParamsConfig = route.page?.dynamicParams ?? + [...(route.layouts || [])].reverse().find(l => l?.dynamicParams !== undefined)?.dynamicParams; // true (default) | false const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; @@ -4074,7 +4111,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -4093,7 +4130,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -4146,7 +4183,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -4173,6 +4210,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. const __dynamicParamsResponse = await __validateAppPageDynamicParams({ @@ -4181,7 +4224,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); }, enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, + // generateStaticParams may be on the page or on a layout (e.g. the blog + // layout exports it while the page exports dynamicParams = false). Fall + // back to the innermost layout that exports it. + generateStaticParams: route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, isDynamicRoute: route.isDynamic, logGenerateStaticParamsError(err) { console.error("[vinext] generateStaticParams error:", err); @@ -4192,6 +4239,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { return __dynamicParamsResponse; } + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); + } + // Check for intercepting routes on RSC requests (client-side navigation). // If the target URL matches an intercepting route in a parallel slot, // render the source route with the intercepting page in the slot. @@ -4462,6 +4516,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from "/p import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -4483,7 +4538,7 @@ import { } from "/packages/vinext/src/server/app-route-handler-response.js"; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from "/packages/vinext/src/shims/request-context.js"; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from "/packages/vinext/src/routing/route-trie.js"; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -5666,6 +5721,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -5792,6 +5850,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -5906,7 +5968,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -6022,7 +6084,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -6108,19 +6170,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (!match) { - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -6257,18 +6322,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). For validation purposes, use the most specific + // (innermost) value: page first, then layouts from innermost to outermost. + const dynamicParamsConfig = route.page?.dynamicParams ?? + [...(route.layouts || [])].reverse().find(l => l?.dynamicParams !== undefined)?.dynamicParams; // true (default) | false const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; @@ -6277,7 +6341,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -6296,7 +6360,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -6349,7 +6413,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -6376,6 +6440,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. const __dynamicParamsResponse = await __validateAppPageDynamicParams({ @@ -6384,7 +6454,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); }, enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, + // generateStaticParams may be on the page or on a layout (e.g. the blog + // layout exports it while the page exports dynamicParams = false). Fall + // back to the innermost layout that exports it. + generateStaticParams: route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, isDynamicRoute: route.isDynamic, logGenerateStaticParamsError(err) { console.error("[vinext] generateStaticParams error:", err); @@ -6395,6 +6469,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { return __dynamicParamsResponse; } + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); + } + // Check for intercepting routes on RSC requests (client-side navigation). // If the target URL matches an intercepting route in a parallel slot, // render the source route with the intercepting page in the slot. @@ -6665,6 +6746,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from "/p import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -6686,7 +6768,7 @@ import { } from "/packages/vinext/src/server/app-route-handler-response.js"; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from "/packages/vinext/src/shims/request-context.js"; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from "/packages/vinext/src/routing/route-trie.js"; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -7893,6 +7975,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -8019,6 +8104,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -8133,7 +8222,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -8249,7 +8338,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -8335,19 +8424,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (!match) { - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -8484,18 +8576,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). For validation purposes, use the most specific + // (innermost) value: page first, then layouts from innermost to outermost. + const dynamicParamsConfig = route.page?.dynamicParams ?? + [...(route.layouts || [])].reverse().find(l => l?.dynamicParams !== undefined)?.dynamicParams; // true (default) | false const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; @@ -8504,7 +8595,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -8523,7 +8614,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -8576,7 +8667,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -8603,6 +8694,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. const __dynamicParamsResponse = await __validateAppPageDynamicParams({ @@ -8611,7 +8708,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); }, enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, + // generateStaticParams may be on the page or on a layout (e.g. the blog + // layout exports it while the page exports dynamicParams = false). Fall + // back to the innermost layout that exports it. + generateStaticParams: route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, isDynamicRoute: route.isDynamic, logGenerateStaticParamsError(err) { console.error("[vinext] generateStaticParams error:", err); @@ -8622,6 +8723,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { return __dynamicParamsResponse; } + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); + } + // Check for intercepting routes on RSC requests (client-side navigation). // If the target URL matches an intercepting route in a parallel slot, // render the source route with the intercepting page in the slot. @@ -8892,6 +9000,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from "/p import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -8913,7 +9022,7 @@ import { } from "/packages/vinext/src/server/app-route-handler-response.js"; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from "/packages/vinext/src/shims/request-context.js"; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from "/packages/vinext/src/routing/route-trie.js"; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -10094,6 +10203,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -10220,6 +10332,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -10334,7 +10450,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -10450,7 +10566,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -10536,19 +10652,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (!match) { - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -10685,18 +10804,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). For validation purposes, use the most specific + // (innermost) value: page first, then layouts from innermost to outermost. + const dynamicParamsConfig = route.page?.dynamicParams ?? + [...(route.layouts || [])].reverse().find(l => l?.dynamicParams !== undefined)?.dynamicParams; // true (default) | false const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; @@ -10705,7 +10823,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -10724,7 +10842,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -10777,7 +10895,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -10804,6 +10922,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. const __dynamicParamsResponse = await __validateAppPageDynamicParams({ @@ -10812,7 +10936,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); }, enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, + // generateStaticParams may be on the page or on a layout (e.g. the blog + // layout exports it while the page exports dynamicParams = false). Fall + // back to the innermost layout that exports it. + generateStaticParams: route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, isDynamicRoute: route.isDynamic, logGenerateStaticParamsError(err) { console.error("[vinext] generateStaticParams error:", err); @@ -10823,6 +10951,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { return __dynamicParamsResponse; } + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); + } + // Check for intercepting routes on RSC requests (client-side navigation). // If the target URL matches an intercepting route in a parallel slot, // render the source route with the intercepting page in the slot. @@ -11093,6 +11228,7 @@ import { readAppPageCacheResponse as __readAppPageCacheResponse } from "/p import { buildAppPageFontLinkHeader as __buildAppPageFontLinkHeader, buildAppPageSpecialErrorResponse as __buildAppPageSpecialErrorResponse, + buildDefaultNotFoundHtml as __buildDefaultNotFoundHtml, readAppPageTextStream as __readAppPageTextStream, resolveAppPageSpecialError as __resolveAppPageSpecialError, teeAppPageRscStreamForCapture as __teeAppPageRscStreamForCapture, @@ -11114,7 +11250,7 @@ import { } from "/packages/vinext/src/server/app-route-handler-response.js"; import { _consumeRequestScopedCacheLife, getCacheHandler } from "next/cache"; import { getRequestExecutionContext as _getRequestExecutionContext } from "/packages/vinext/src/shims/request-context.js"; -import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags } from "vinext/fetch-cache"; +import { ensureFetchPatch as _ensureFetchPatch, getCollectedFetchTags, setPageFetchCachePolicy as _setPageFetchCachePolicy, setBypassFetchCache as _setBypassFetchCache } from "vinext/fetch-cache"; import { buildRouteTrie as _buildRouteTrie, trieMatch as _trieMatch } from "/packages/vinext/src/routing/route-trie.js"; // Import server-only state module to register ALS-backed accessors. import "vinext/navigation-state"; @@ -12517,6 +12653,9 @@ export default async function handler(request, ctx) { }); return _runWithUnifiedCtx(__uCtx, async () => { _ensureFetchPatch(); + // Bypass fetch cache when the incoming request sends Cache-Control: no-cache, + // matching Next.js dev behavior of serving fresh data on forced reloads. + if (request.headers.get('cache-control')?.includes('no-cache')) _setBypassFetchCache(true); const __reqCtx = requestContextFromRequest(request); // Per-request container for middleware state. Passed into // _handleRequest which fills in .headers and .status; @@ -12643,6 +12782,10 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("accept")?.includes("text/x-component"); let cleanPathname = pathname.replace(/\\.rsc$/, ""); + // Preserve the user-visible (canonical) pathname before any internal rewrites. + // usePathname() should always return the URL the user navigated to, not the + // internal destination after beforeFiles/afterFiles/fallback rewrites. + const canonicalPathname = cleanPathname; // Middleware response headers and custom rewrite status are stored in // _mwCtx (per-request container) so handler() can merge them into @@ -12892,7 +13035,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { // Set navigation context for Server Components. // Note: Headers context is already set by runWithRequestContext in the handler wrapper. setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: {}, }); @@ -13008,7 +13151,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (match) { const { route: actionRoute, params: actionParams } = match; setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params: actionParams, }); @@ -13094,19 +13237,22 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (!match) { - // Render custom not-found page if available, otherwise plain 404 + // Render custom not-found page if available, otherwise default 404 HTML const notFoundResponse = await renderNotFoundPage(null, isRscRequest, request); if (notFoundResponse) return notFoundResponse; setHeadersContext(null); setNavigationContext(null); - return new Response("Not Found", { status: 404 }); + return new Response(__buildDefaultNotFoundHtml(404), { + status: 404, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); } const { route, params } = match; // Update navigation context with matched params setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: url.searchParams, params, }); @@ -13243,18 +13389,17 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { ); } - // Build the component tree: layouts wrapping the page - const PageComponent = route.page?.default; - if (!PageComponent) { - setHeadersContext(null); - setNavigationContext(null); - return new Response("Page has no default export", { status: 500 }); - } - // Read route segment config from page module exports let revalidateSeconds = typeof route.page?.revalidate === "number" ? route.page.revalidate : null; + // Apply page-level fetchCache policy so patchedFetch() can override per-fetch + // cache directives (e.g. fetchCache='force-cache' overrides cache:'no-cache'). + if (route.page?.fetchCache) _setPageFetchCachePolicy(route.page.fetchCache); const dynamicConfig = route.page?.dynamic; // 'auto' | 'force-dynamic' | 'force-static' | 'error' - const dynamicParamsConfig = route.page?.dynamicParams; // true (default) | false + // dynamicParams can be exported from the page or from layouts (which apply to + // their respective segment). For validation purposes, use the most specific + // (innermost) value: page first, then layouts from innermost to outermost. + const dynamicParamsConfig = route.page?.dynamicParams ?? + [...(route.layouts || [])].reverse().find(l => l?.dynamicParams !== undefined)?.dynamicParams; // true (default) | false const isForceStatic = dynamicConfig === "force-static"; const isDynamicError = dynamicConfig === "error"; @@ -13263,7 +13408,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { if (isForceStatic) { setHeadersContext({ headers: new Headers(), cookies: new Map() }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -13282,7 +13427,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { accessError: new Error(errorMsg), }); setNavigationContext({ - pathname: cleanPathname, + pathname: canonicalPathname, searchParams: new URLSearchParams(), params, }); @@ -13335,7 +13480,7 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { }); return _runWithUnifiedCtx(__revalUCtx, async () => { _ensureFetchPatch(); - setNavigationContext({ pathname: cleanPathname, searchParams: new URLSearchParams(), params }); + setNavigationContext({ pathname: canonicalPathname, searchParams: new URLSearchParams(), params }); const __revalElement = await buildPageElement(route, params, undefined, new URLSearchParams()); const __revalOnError = createRscOnErrorHandler(request, cleanPathname, route.pattern); const __revalRscStream = renderToReadableStream(__revalElement, { onError: __revalOnError }); @@ -13362,6 +13507,12 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { } } + // Build the component tree: layouts wrapping the page + const PageComponent = route.page?.default; + + // Validate generateStaticParams param types BEFORE checking for a default export. + // Next.js surfaces the "not provided as a string" error even when the page has no + // default export, so validation must run first. // dynamicParams = false: only params from generateStaticParams are allowed. // This runs AFTER the ISR cache read so that a cache hit skips this work entirely. const __dynamicParamsResponse = await __validateAppPageDynamicParams({ @@ -13370,7 +13521,11 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { setNavigationContext(null); }, enforceStaticParamsOnly: dynamicParamsConfig === false, - generateStaticParams: route.page?.generateStaticParams, + // generateStaticParams may be on the page or on a layout (e.g. the blog + // layout exports it while the page exports dynamicParams = false). Fall + // back to the innermost layout that exports it. + generateStaticParams: route.page?.generateStaticParams ?? + [...(route.layouts || [])].reverse().find(l => l?.generateStaticParams)?.generateStaticParams, isDynamicRoute: route.isDynamic, logGenerateStaticParamsError(err) { console.error("[vinext] generateStaticParams error:", err); @@ -13381,6 +13536,13 @@ async function _handleRequest(request, __reqCtx, _mwCtx) { return __dynamicParamsResponse; } + // Now check for missing default export after validation has run. + if (!PageComponent) { + setHeadersContext(null); + setNavigationContext(null); + return new Response("Page has no default export", { status: 500 }); + } + // Check for intercepting routes on RSC requests (client-side navigation). // If the target URL matches an intercepting route in a parallel slot, // render the source route with the intercepting page in the slot. diff --git a/tests/app-page-execution.test.ts b/tests/app-page-execution.test.ts index f1548f057..935865910 100644 --- a/tests/app-page-execution.test.ts +++ b/tests/app-page-execution.test.ts @@ -96,7 +96,8 @@ describe("app page execution helpers", () => { }); expect(response.status).toBe(401); - await expect(response.text()).resolves.toBe("Unauthorized"); + expect(response.headers.get("content-type")).toContain("text/html"); + await expect(response.text()).resolves.toContain("Unauthorized"); expect(clearRequestContext).toHaveBeenCalledTimes(1); }); diff --git a/tests/fixtures-repos/next.js/next-internal-shims.d.ts b/tests/fixtures-repos/next.js/next-internal-shims.d.ts new file mode 100644 index 000000000..a5180c0c0 --- /dev/null +++ b/tests/fixtures-repos/next.js/next-internal-shims.d.ts @@ -0,0 +1,196 @@ +/** + * Ambient type declarations for Next.js-internal test packages that are not + * published to npm. These exist in the Next.js monorepo's private test + * infrastructure and are used by the upstream e2e test suite. + * + * We declare them as `any`-typed stubs so TypeScript is satisfied when + * compiling the cloned Next.js tests in this repo. + */ + +// ─── next-webdriver ──────────────────────────────────────────────────────────── +// +// The upstream Next.js test suite uses a Selenium/WebDriver-based browser +// automation wrapper. In this repo, the equivalent is our Playwright-backed +// BrowserInstance from next-test-setup.ts — we alias it at runtime via Vitest. +// Here we just need the types to compile. + +declare module "next-webdriver" { + export type WebdriverOptions = { + waitHydration?: boolean; + disableCache?: boolean; + beforePageLoad?: (page: unknown) => void; + }; + + export type ElementHandle = { + text(): Promise; + html(): Promise; + attr(name: string): Promise; + click(): Promise; + type(text: string): Promise; + getValue(): Promise; + elementById(id: string): Promise; + waitForElementByCss(selector: string, timeoutMs?: number): Promise; + }; + + export type Playwright = { + readonly page: unknown; + elementByCss(selector: string): ElementHandle; + elementById(id: string): ElementHandle; + elementsByCss(selector: string): Promise; + hasElementByCssSelector(selector: string): Promise; + waitForElementByCss(selector: string, timeoutMs?: number): Promise; + waitForIdleNetwork(timeoutMs?: number): Promise; + eval(expression: string): Promise; + url(): Promise; + loadPage(url: string, opts?: { disableCache?: boolean }): Promise; + back(): Promise; + forward(): Promise; + refresh(): Promise; + close(): Promise; + log(): Promise>; + deleteCookies(): Promise; + addCookie(cookie: { + name: string; + value: string; + domain?: string; + path?: string; + expires?: number; + httpOnly?: boolean; + secure?: boolean; + sameSite?: "Strict" | "Lax" | "None"; + }): Promise; + on(event: string, handler: (...args: unknown[]) => void): void; + }; + + function webdriver( + appPort: number | string, + path: string, + opts?: WebdriverOptions, + ): Promise; + + export default webdriver; +} + +// ─── router-act ─────────────────────────────────────────────────────────────── +// +// Internal Next.js helper for wrapping router state updates in React `act()`. + +declare module "router-act" { + export function createRouterAct( + opts?: Record, + ): (...args: unknown[]) => Promise; +} + +// ─── development-sandbox ────────────────────────────────────────────────────── +// +// Internal Next.js helper for spinning up isolated Next.js dev server instances +// inside tests. + +declare module "development-sandbox" { + export type Sandbox = { + session: unknown; + cleanup(): Promise; + }; + + export function createSandbox(opts?: { + nextConfig?: Record; + files?: Record; + [key: string]: unknown; + }): Promise; +} + +// ─── test-data-service/writer ───────────────────────────────────────────────── +// +// Internal Next.js helper for coordinating data between a test server and test +// assertions. + +declare module "test-data-service/writer" { + export type TestDataServer = { + port: number; + url: string; + stop(): Promise; + write(key: string, value: unknown): Promise; + read(key: string): Promise; + waitFor(key: string, expected: unknown, timeoutMs?: number): Promise; + }; + + export function createTestDataServer(opts?: { + port?: number; + [key: string]: unknown; + }): Promise; +} + +// ─── test-log ───────────────────────────────────────────────────────────────── +// +// Internal Next.js helper for capturing and asserting on log output from +// test processes. + +declare module "test-log" { + export type TestLog = { + lines: string[]; + clear(): void; + wait(pattern: string | RegExp, timeoutMs?: number): Promise; + }; + + export function createTestLog(opts?: { port?: number; [key: string]: unknown }): Promise; +} + +// ─── e2e-utils/request-tracker ──────────────────────────────────────────────── +// +// Internal Next.js helper for tracking HTTP requests made during a test. + +declare module "e2e-utils/request-tracker" { + export type RequestTracker = { + requests: Array<{ url: string; method: string; headers: Record }>; + clear(): void; + waitForRequest( + matcher: string | RegExp | ((req: { url: string }) => boolean), + timeoutMs?: number, + ): Promise<{ url: string; method: string; headers: Record }>; + }; + + export function createRequestTracker(opts?: { + port?: number; + [key: string]: unknown; + }): Promise; +} + +// ─── e2e-utils/ppr ──────────────────────────────────────────────────────────── +// +// Internal Next.js helper for testing Partial Pre-Rendering (PPR) responses. + +declare module "e2e-utils/ppr" { + export function splitResponseWithPPRSentinel(html: string): { shell: string; postponed: string }; +} + +// ─── e2e-utils/instant-validation ───────────────────────────────────────────── +// +// Internal Next.js helper for testing the "instant validation" (build-time +// error overlay) feature. + +declare module "e2e-utils/instant-validation" { + export function expectNoBuildValidationErrors( + browser: unknown, + opts?: Record, + ): Promise; + + export function expectBuildValidationSkipped( + browser: unknown, + opts?: Record, + ): Promise; + + export function extractBuildValidationError( + browser: unknown, + opts?: Record, + ): Promise; + + export function parseValidationMessages( + output: string, + opts?: Record, + ): Array<{ type: string; message: string }>; + + export function waitForValidation( + browser: unknown, + opts?: Record, + ): Promise; +} diff --git a/tests/fixtures-repos/next.js/next-test-setup.test.ts b/tests/fixtures-repos/next.js/next-test-setup.test.ts new file mode 100644 index 000000000..97881053f --- /dev/null +++ b/tests/fixtures-repos/next.js/next-test-setup.test.ts @@ -0,0 +1,427 @@ +/** + * Unit tests for next-test-setup.ts + * + * Tests two things: + * + * 1. cheerio.load() — sanity-checks that the real cheerio package works as + * expected for the selectors Next.js tests use. These run without any server. + * + * 2. nextTestSetup smoke test — spins up a vinext dev server against the + * app-basic fixture (the same one used by nextjs-compat tests) and verifies + * that next.render, next.render$, next.fetch, and next.browser all work. + * This lives in the "integration" project because it starts a Vite server. + */ + +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { load as cheerioLoad } from "cheerio"; +import { nextTestSetup } from "./next-test-setup.js"; +import path from "node:path"; + +// ─── cheerio unit tests ─────────────────────────────────────────────────────── + +describe("cheerio.load", () => { + const html = ` + + + Test Page + +

Hello World

+

First paragraph

+

Second paragraph

+ About + Logo +
+ nested span +
+ +
+ + + `; + + // ── $.html() ─────────────────────────────────────────────────────────────── + + it("$.html() returns the serialised document HTML", () => { + const $ = cheerioLoad(html); + // cheerio serialises the document — it won't be byte-for-byte identical to + // the input but must contain the key content we care about. + const out = $.html(); + expect(out).toContain("Hello World"); + expect(out).toContain("First paragraph"); + expect(out).toContain('id="title"'); + }); + + // ── tag selector ────────────────────────────────────────────────────────── + + it("tag selector matches elements by tag name", () => { + const $ = cheerioLoad(html); + expect($("h1").length).toBe(1); + expect($("p").length).toBe(2); + expect($("a").length).toBe(1); + }); + + it("tag selector .text() returns concatenated inner text", () => { + const $ = cheerioLoad(html); + expect($("h1").text()).toBe("Hello World"); + expect($("a").text()).toBe("About"); + }); + + it("tag selector .html() returns inner HTML of first match", () => { + const $ = cheerioLoad(html); + expect($("h1").html()).toBe("Hello World"); + }); + + it("tag selector .html() returns null when no match", () => { + const $ = cheerioLoad(html); + expect($("section").html()).toBeNull(); + }); + + it("tag selector .length is 0 for unmatched selectors", () => { + const $ = cheerioLoad(html); + expect($("nav").length).toBe(0); + }); + + // ── #id selector ────────────────────────────────────────────────────────── + + it("#id selector matches element with that id", () => { + const $ = cheerioLoad(html); + expect($("#title").length).toBe(1); + expect($("#title").text()).toBe("Hello World"); + }); + + it("#id selector returns empty when id not found", () => { + const $ = cheerioLoad(html); + expect($("#nope").length).toBe(0); + expect($("#nope").text()).toBe(""); + }); + + // ── .class selector ─────────────────────────────────────────────────────── + + it(".class selector matches elements with that class", () => { + const $ = cheerioLoad(html); + expect($(".text").length).toBe(2); + }); + + it(".class selector .text() concatenates text of all matches", () => { + const $ = cheerioLoad(html); + expect($(".text").text()).toBe("First paragraphSecond paragraph"); + }); + + it(".class selector matches when element has multiple classes", () => { + const $ = cheerioLoad(html); + // .secondary only matches the second paragraph + expect($(".secondary").length).toBe(1); + expect($(".secondary").text()).toBe("Second paragraph"); + }); + + // ── .attr() ─────────────────────────────────────────────────────────────── + + it(".attr() returns the attribute value of the first match", () => { + const $ = cheerioLoad(html); + expect($("a").attr("href")).toBe("/about"); + expect($("a").attr("data-testid")).toBe("nav-link"); + }); + + it(".attr() returns undefined when attribute is missing", () => { + const $ = cheerioLoad(html); + expect($("a").attr("nonexistent")).toBeUndefined(); + }); + + it(".attr() returns undefined when selector has no matches", () => { + const $ = cheerioLoad(html); + expect($("nav").attr("href")).toBeUndefined(); + }); + + // ── combined selectors ──────────────────────────────────────────────────── + + it("tag + #id combined selector (e.g. h1#title)", () => { + const $ = cheerioLoad(html); + expect($("h1#title").length).toBe(1); + expect($("h1#title").text()).toBe("Hello World"); + }); + + it("tag + .class combined selector (e.g. p.text)", () => { + const $ = cheerioLoad(html); + expect($("p.text").length).toBe(2); + }); + + it("#id + .class combined selector (e.g. h1.heading)", () => { + const $ = cheerioLoad(html); + expect($("#title.heading").length).toBe(1); + }); + + it("multiple .class combined selector (e.g. .text.secondary)", () => { + const $ = cheerioLoad(html); + expect($(".text.secondary").length).toBe(1); + expect($(".text.secondary").text()).toBe("Second paragraph"); + }); + + // ── [attr] and [attr=val] selectors ─────────────────────────────────────── + + it("[attr] selector matches elements that have the attribute", () => { + const $ = cheerioLoad(html); + expect($("[href]").length).toBe(1); + expect($("[href]").text()).toBe("About"); + }); + + it("[attr=val] selector matches elements with the exact attribute value", () => { + const $ = cheerioLoad(html); + expect($("[data-testid=nav-link]").length).toBe(1); + expect($("[data-testid=nav-link]").text()).toBe("About"); + }); + + // ── void / self-closing elements ────────────────────────────────────────── + + it("void elements (img, input, br) are matched with empty inner", () => { + const $ = cheerioLoad(html); + expect($("img").length).toBe(1); + expect($("img").attr("src")).toBe("/logo.png"); + expect($("img").attr("alt")).toBe("Logo"); + expect($("img").text()).toBe(""); + expect($("br").length).toBe(1); + }); + + // ── nested inner HTML ───────────────────────────────────────────────────── + + it("inner HTML of a container element includes child tags", () => { + const $ = cheerioLoad(html); + const inner = $("#container").html(); + expect(inner).toBeTruthy(); + expect(inner).toContain("nested span"); + expect(inner).toContain(" { + const encoded = `

Hello & World <3>

`; + const $ = cheerioLoad(encoded); + expect($("#msg").text()).toBe("Hello & World <3>"); + }); + + it(".attr() decodes HTML entities in attribute values", () => { + const encoded = `link`; + const $ = cheerioLoad(encoded); + expect($("a").attr("href")).toBe("/path?a=1&b=2"); + }); + + // ── descendant selector ─────────────────────────────────────────────────── + + it("supports descendant (space) selectors", () => { + const $ = cheerioLoad(html); + expect($("div span").length).toBe(1); + expect($("div span").text()).toBe("nested span"); + }); + + // ── edge cases ──────────────────────────────────────────────────────────── + + it("handles empty HTML gracefully", () => { + const $ = cheerioLoad(""); + expect($("h1").length).toBe(0); + // cheerio always produces a valid document skeleton even for empty input + expect(typeof $.html()).toBe("string"); + }); + + it("handles HTML with no matching tags", () => { + const $ = cheerioLoad("

only paragraphs

"); + expect($("h1").length).toBe(0); + expect($("p").length).toBe(1); + expect($("p").text()).toBe("only paragraphs"); + }); + + it("handles deeply nested elements correctly", () => { + const nested = ` +
+
+

deep text

+
+
+ `; + const $ = cheerioLoad(nested); + expect($("#inner").text()).toBe("deep text"); + const outerHtml = $("#outer").html(); + expect(outerHtml).toContain("middle"); + expect(outerHtml).toContain("deep text"); + }); + + it("multiple same-tag elements are all returned", () => { + const multi = `
  • one
  • two
  • three
  • `; + const $ = cheerioLoad(multi); + expect($("li").length).toBe(3); + expect($("li").text()).toBe("onetwothree"); + expect($("li").first().html()).toBe("one"); + }); +}); + +// ─── nextTestSetup smoke tests ──────────────────────────────────────────────── +// +// These tests live in the "integration" Vitest project (see vite.config.ts). +// They start a real Vite dev server and verify the full next.* API works. + +const APP_FIXTURE = path.resolve(import.meta.dirname, "./fixtures/app-basic"); + +describe("nextTestSetup — smoke test", () => { + const { next, isNextDev, isNextStart, isNextDeploy, isTurbopack, skipped } = nextTestSetup({ + files: APP_FIXTURE, + }); + + // ── flags ────────────────────────────────────────────────────────────────── + + it("returns correct mode flags", () => { + expect(isNextDev).toBe(true); + expect(isNextStart).toBe(false); + expect(isNextDeploy).toBe(false); + expect(isTurbopack).toBe(false); + expect(skipped).toBe(false); + }); + + it("next.url is a non-empty localhost URL after server start", () => { + expect(next.url).toMatch(/^http:\/\/localhost:\d+$/); + }); + + // ── next.fetch ───────────────────────────────────────────────────────────── + + it("next.fetch('/') returns a 200 response", async () => { + const res = await next.fetch("/"); + expect(res.status).toBe(200); + }); + + it("next.fetch accepts a full URL (next.url + path)", async () => { + const res = await next.fetch(next.url + "/"); + expect(res.status).toBe(200); + }); + + it("next.fetch('/nonexistent-route-xyz') returns 404", async () => { + const res = await next.fetch("/nonexistent-route-xyz-abc"); + expect(res.status).toBe(404); + }); + + // ── next.render ──────────────────────────────────────────────────────────── + + it("next.render('/') returns HTML string", async () => { + const html = await next.render("/"); + expect(typeof html).toBe("string"); + expect(html.length).toBeGreaterThan(0); + expect(html).toContain(""); + }); + + it("next.render passes custom fetch init (e.g. headers)", async () => { + // Sending an RSC request header should return text/x-component, not HTML + const html = await next.render("/", { + headers: { RSC: "1", Accept: "text/x-component" }, + }); + // RSC response is not an HTML document + expect(html).not.toContain(""); + // It should contain RSC payload markers + expect(html.length).toBeGreaterThan(0); + }); + + // ── next.render$ ─────────────────────────────────────────────────────────── + + it("next.render$('/') returns a cheerio function", async () => { + const $ = await next.render$("/"); + expect(typeof $).toBe("function"); + expect(typeof $.html).toBe("function"); + }); + + it("next.render$ $.html() returns the full HTML", async () => { + const $ = await next.render$("/"); + const html = $.html(); + expect(html).toContain(""); + }); + + it("next.render$ selector returns matching elements", async () => { + const $ = await next.render$("/"); + // Every Next.js page has an element + expect($("html").length).toBeGreaterThan(0); + // And a + expect($("body").length).toBeGreaterThan(0); + }); + + // ── next.browser ─────────────────────────────────────────────────────────── + + it("next.browser('/') navigates and returns a BrowserInstance", async () => { + const browser = await next.browser("/"); + try { + const url = await browser.url(); + expect(url).toContain("localhost"); + } finally { + await browser.close(); + } + }); + + it("browser.eval() executes JavaScript in the page", async () => { + const browser = await next.browser("/"); + try { + const result = await browser.eval("1 + 1"); + expect(result).toBe(2); + } finally { + await browser.close(); + } + }); + + it("browser.elementByCss('body').text() returns page text", async () => { + const browser = await next.browser("/"); + try { + const text = await browser.elementByCss("body").text(); + expect(typeof text).toBe("string"); + expect(text.length).toBeGreaterThan(0); + } finally { + await browser.close(); + } + }); + + it("browser.hasElementByCssSelector returns true for existing elements", async () => { + const browser = await next.browser("/"); + try { + expect(await browser.hasElementByCssSelector("body")).toBe(true); + expect(await browser.hasElementByCssSelector("html")).toBe(true); + } finally { + await browser.close(); + } + }); + + it("browser.hasElementByCssSelector returns false for missing elements", async () => { + const browser = await next.browser("/"); + try { + expect( + await browser.hasElementByCssSelector("#this-element-definitely-does-not-exist-xyz"), + ).toBe(false); + } finally { + await browser.close(); + } + }); + + it("browser.log() returns an array of console log entries", async () => { + const browser = await next.browser("/"); + try { + const logs = await browser.log(); + expect(Array.isArray(logs)).toBe(true); + } finally { + await browser.close(); + } + }); + + it("browser.refresh() reloads without throwing", async () => { + const browser = await next.browser("/"); + try { + const urlBefore = await browser.url(); + await browser.refresh(); + const urlAfter = await browser.url(); + expect(urlAfter).toBe(urlBefore); + } finally { + await browser.close(); + } + }); + + it("browser.loadPage() navigates to a new URL", async () => { + const browser = await next.browser("/"); + try { + await browser.loadPage(next.url + "/"); + const url = await browser.url(); + expect(url).toContain("localhost"); + } finally { + await browser.close(); + } + }); +}); diff --git a/tests/fixtures-repos/next.js/next-test-setup.ts b/tests/fixtures-repos/next.js/next-test-setup.ts new file mode 100644 index 000000000..e5bd8a819 --- /dev/null +++ b/tests/fixtures-repos/next.js/next-test-setup.ts @@ -0,0 +1,1591 @@ +/** + * nextTestSetup — vinext-backed shim for the Next.js `e2e-utils` API. + * + * Mirrors the interface used by tests in next.js/test/e2e/ so they can run + * against a Vite + vinext dev server with minimal modification. + * + * Usage (in a ported Next.js test file): + * + * import { nextTestSetup } from '../../next-test-setup.js' + * + * describe('my feature', () => { + * const { next, isNextDev } = nextTestSetup({ files: __dirname }) + * + * it('renders', async () => { + * const html = await next.render('/') + * expect(html).toContain('hello') + * }) + * + * it('parses', async () => { + * const $ = await next.render$('/') + * expect($('h1').text()).toBe('Hello World') + * }) + * + * it('uses a browser', async () => { + * const browser = await next.browser('/') + * expect(await browser.elementByCss('h1').text()).toBe('Hello World') + * await browser.close() + * }) + * }) + * + * The `files` option must be `__dirname`. The directory that contains the test + * file is also the Next.js app fixture — an app/ or pages/ directory lives + * right alongside the test, exactly as the upstream Next.js test runner expects. + * + * ── next.* API ──────────────────────────────────────────────────────────────── + * + * next.url string base URL, no trailing slash + * next.render(path, init?) Promise full HTML text + * next.render$(path, init?) Promise cheerio selector fn + * next.fetch(path, init?) Promise raw fetch + * next.browser(path, opts?) Promise + * + * ── cheerio $ ───────────────────────────────────────────────────────────────── + * + * $('selector').text() string concatenated inner text + * $('selector').html() string | null inner HTML of first match + * $('selector').attr(name) string | undefined attribute value + * $('selector').length number match count + * $.html() string full document HTML + * + * Full cheerio selector support — any CSS selector that cheerio understands + * works, including descendant selectors, pseudo-selectors, etc. + * + * ── BrowserInstance ─────────────────────────────────────────────────────────── + * + * browser.elementByCss(sel) ElementProxy (lazy, chainable) + * browser.elementById(id) ElementProxy (lazy, chainable) + * browser.elementsByCss(sel) Promise + * browser.hasElementByCssSelector(sel) Promise + * browser.waitForElementByCss(sel, ms?) Promise + * browser.waitForIdleNetwork(ms?) Promise + * browser.eval(expr) Promise + * browser.url() Promise + * browser.loadPage(url, opts?) Promise + * browser.back() Promise + * browser.forward() Promise + * browser.refresh() Promise + * browser.close() Promise + * browser.log() Promise> + * browser.deleteCookies() Promise + * browser.addCookie(cookie) Promise + * browser.on(event, handler) void (Playwright Page pass-through) + * + * ── ElementProxy ────────────────────────────────────────────────────────────── + * + * element.text() Promise + * element.html() Promise + * element.attr(name) Promise + * element.click() Promise (chainable) + * element.type(text) Promise + * element.getValue() Promise + * element.waitForElementByCss(sel, ms?) Promise + * + * ── Server mode ─────────────────────────────────────────────────────────────── + * + * Controlled by the NEXT_TEST_MODE environment variable: + * + * NEXT_TEST_MODE=dev (default) — Vite dev server, HMR enabled + * NEXT_TEST_MODE=start — production build + prod server (no HMR) + * NEXT_TEST_MODE=deploy — not yet implemented (throws at startup) + * + * ── Flags ───────────────────────────────────────────────────────────────────── + * + * isNextDev true when NEXT_TEST_MODE=dev (or unset) + * isNextStart true when NEXT_TEST_MODE=start + * isNextDeploy true when NEXT_TEST_MODE=deploy + * isTurbopack false + * skipped false + */ + +import { beforeAll, afterAll } from "vitest"; +import { createServer, createBuilder, type ViteDevServer, transformWithOxc } from "vite"; +import vinext from "vinext"; +import type { AddressInfo } from "node:net"; +import { Page } from "playwright"; +import * as fs from "node:fs"; +import * as path from "node:path"; + +/** dev | start | deploy — controlled by NEXT_TEST_MODE env var */ +export type NextTestMode = "dev" | "start" | "deploy"; +export const nextTestMode: NextTestMode = + (process.env.NEXT_TEST_MODE as NextTestMode | undefined) ?? "dev"; + +// ─── Lazy Playwright singleton ──────────────────────────────────────────────── +// +// We launch one shared Chromium process per Vitest worker. Tests that only use +// next.render / next.fetch never touch Playwright at all. + +type PWBrowser = import("playwright").Browser; +type PWPage = import("playwright").Page; + +let _sharedBrowser: PWBrowser | null = null; + +async function getSharedBrowser(): Promise { + if (!_sharedBrowser) { + const { chromium } = await import("playwright"); + _sharedBrowser = await chromium.launch({ headless: true }); + } + return _sharedBrowser; +} + +// ─── ElementHandle ──────────────────────────────────────────────────────────── +// +// A thenable that is also directly an ElementProxy. This lets upstream Next.js +// tests chain off waitForElementByCss / click without awaiting each step: +// +// browser.waitForElementByCss('#foo').click().waitForElementByCss('#bar').text() +// +// Each method that previously returned Promise now returns +// ElementHandle so the chain stays synchronously composable while still being +// awaitable for the terminal value. + +export type ElementHandle = Promise & ElementProxy; + +// ─── ElementProxy ───────────────────────────────────────────────────────────── +// +// Wraps a Playwright locator in the webdriver-style API used by Next.js tests. + +export type ElementProxy = { + text(): Promise; + html(): Promise; + attr(name: string): Promise; + click(): ElementHandle; + type(text: string): ElementHandle; + getValue(): Promise; + elementById(id: string): ElementHandle; + waitForElementByCss(selector: string, timeoutMs?: number): ElementHandle; +}; + +// Wrap a Promise so it also exposes ElementProxy methods directly, +// enabling synchronous chaining without intermediate awaits. +function makeElementHandle(promise: Promise, page: PWPage): ElementHandle { + // oxlint-disable-next-line typescript/no-explicit-any + const handle = promise as any; + handle.text = () => promise.then((p) => p.text()); + handle.html = () => promise.then((p) => p.html()); + handle.attr = (name: string) => promise.then((p) => p.attr(name)); + handle.click = () => + makeElementHandle( + promise.then((p) => p.click().then(() => p)), + page, + ); + handle.type = (text: string) => + makeElementHandle( + promise.then((p) => p.type(text).then(() => p)), + page, + ); + handle.getValue = () => promise.then((p) => p.getValue()); + handle.elementById = (id: string) => + makeElementHandle( + promise.then(() => makeElementProxy(page, `#${id}`)), + page, + ); + handle.waitForElementByCss = (sel: string, timeoutMs?: number) => + makeElementHandle( + promise.then(() => + page + .waitForSelector(sel, { timeout: timeoutMs ?? 10_000 }) + .then(() => makeElementProxy(page, sel)), + ), + page, + ); + return handle as ElementHandle; +} + +function makeElementProxy(page: PWPage, selector: string): ElementProxy { + const locator = page.locator(selector).first(); + + const proxy: ElementProxy = { + async text() { + return locator.innerText(); + }, + async html() { + return locator.innerHTML(); + }, + async attr(name: string) { + return locator.getAttribute(name); + }, + click() { + return makeElementHandle( + locator.click().then(() => proxy), + page, + ); + }, + type(text: string) { + return makeElementHandle( + locator.fill(text).then(() => proxy), + page, + ); + }, + async getValue() { + return locator.inputValue(); + }, + elementById(id: string) { + return makeElementHandle(Promise.resolve(makeElementProxy(page, `#${id}`)), page); + }, + waitForElementByCss(sel: string, timeoutMs = 10_000) { + return makeElementHandle( + page.waitForSelector(sel, { timeout: timeoutMs }).then(() => makeElementProxy(page, sel)), + page, + ); + }, + }; + return proxy; +} + +// ─── BrowserInstance ────────────────────────────────────────────────────────── + +// A thenable that also exposes ElementProxy methods, returned by +// BrowserInstance.waitForElementByCss so chains like: +// browser.waitForElementByCss('#foo').click().text() +// type-check without intermediate awaits. + +// VoidHandle: returned by waitForIdleNetwork so tests can chain +// .waitForElementByCss off it. +export type VoidHandle = Promise & { + waitForElementByCss(selector: string, timeoutMs?: number): ElementHandle; +}; + +export type BrowserInstance = { + /** Playwright Page — for direct access when needed. */ + readonly page: PWPage; + + elementByCss(selector: string): ElementProxy; + elementById(id: string): ElementProxy; + elementsByCss(selector: string): Promise; + hasElementByCssSelector(selector: string): Promise; + waitForElementByCss(selector: string, timeoutMs?: number): ElementHandle; + waitForIdleNetwork(timeoutMs?: number): VoidHandle; + // oxlint-disable-next-line typescript/no-explicit-any + eval(expression: string): Promise; + url(): Promise; + loadPage(url: string, opts?: { disableCache?: boolean }): Promise; + back(): VoidHandle; + forward(): VoidHandle; + refresh(): VoidHandle; + close(): Promise; + log(): Promise>; + deleteCookies(): Promise; + addCookie(cookie: { + name: string; + value: string; + domain?: string; + path?: string; + expires?: number; + httpOnly?: boolean; + secure?: boolean; + sameSite?: "Strict" | "Lax" | "None"; + }): Promise; + // oxlint-disable-next-line typescript/no-explicit-any + on(event: string, handler: (...args: any[]) => void): void; +}; + +function makeVoidHandle(p: Promise, page: PWPage): VoidHandle { + // oxlint-disable-next-line typescript/no-explicit-any + const handle = p as any; + handle.waitForElementByCss = (sel: string, ms?: number) => + makeElementHandle( + p.then(() => + page + .waitForSelector(sel, { timeout: ms ?? 10_000 }) + .then(() => makeElementProxy(page, sel)), + ), + page, + ); + return handle as VoidHandle; +} + +async function makeBrowserInstance( + baseUrl: string, + urlPath: string, + opts?: BrowserNavigateOptions, +): Promise { + const browser = await getSharedBrowser(); + const context = await browser.newContext({ + baseURL: baseUrl, + ...(opts?.locale ? { locale: opts.locale } : {}), + }); + const page = await context.newPage(); + + // Collect console log entries + const logs: Array<{ source: string; message: string }> = []; + page.on("console", (msg) => logs.push({ source: msg.type(), message: msg.text() })); + + // beforePageLoad hook — fires before navigation + if (opts?.beforePageLoad) { + await opts.beforePageLoad(page); + } + + const fullUrl = urlPath.startsWith("http") ? urlPath : `${baseUrl}${urlPath}`; + await page.goto(fullUrl, { + waitUntil: "domcontentloaded", + ...(opts?.disableCache ? {} : {}), + }); + + // Wait for React hydration to complete so that client-side event handlers + // (onClick, etc.) are attached before tests start interacting with the page. + // app-browser-entry.ts sets window.__VINEXT_HYDRATED_AT after hydration. + const didHydrate = await page + .waitForFunction( + () => typeof (window as Record).__VINEXT_HYDRATED_AT === "number", + { + timeout: 15_000, + }, + ) + .then(() => true) + .catch(() => false); + + // After the hydration marker is set, React useEffect callbacks may still be + // pending (scheduled via MessageChannel, which fires asynchronously). A brief + // sleep gives them time to run and commit state updates (e.g. counters + // incremented via useEffect on mount) before the test reads the DOM. + // 60ms is enough for two rAF cycles — effects typically flush within 5ms. + if (didHydrate) { + await new Promise((resolve) => setTimeout(resolve, 60)); + } + + const instance: BrowserInstance = { + get page() { + return page; + }, + + elementByCss(selector: string) { + return makeElementProxy(page, selector); + }, + + elementById(id: string) { + return makeElementProxy(page, `#${id}`); + }, + + async elementsByCss(selector: string) { + const locators = await page.locator(selector).all(); + // We don't have a per-locator proxy — return a proxy per index + return locators.map((_, i) => makeElementProxy(page, `${selector}:nth-match(${i + 1})`)); + }, + + async hasElementByCssSelector(selector: string) { + return (await page.locator(selector).count()) > 0; + }, + + waitForElementByCss(selector: string, timeoutMs = 10_000) { + return makeElementHandle( + page + .waitForSelector(selector, { timeout: timeoutMs }) + .then(() => makeElementProxy(page, selector)), + page, + ); + }, + + waitForIdleNetwork(timeoutMs = 10_000) { + return makeVoidHandle( + page.waitForLoadState("networkidle", { timeout: timeoutMs }).then(() => undefined as void), + page, + ); + }, + + async eval(expression: string) { + return page.evaluate(expression); + }, + + async url() { + return page.url(); + }, + + async loadPage(url: string, pageOpts?: { disableCache?: boolean }) { + if (pageOpts?.disableCache) { + await context.route("**/*", (route) => route.continue()); + } + await page.goto(url.startsWith("http") ? url : `${baseUrl}${url}`, { + waitUntil: "domcontentloaded", + }); + }, + + back() { + const p = (async () => { + const currentHref = await page.evaluate(() => window.location.href); + // Use window.history.back() instead of page.goBack() (CDP Page.navigateToHistoryEntry) + // because CDP navigation treats pushState entries as cross-document and triggers + // a full page reload, breaking SPA back-navigation that should fire popstate. + void page.evaluate(() => window.history.back()).catch(() => {}); + await page.waitForFunction((prev: string) => window.location.href !== prev, currentHref, { + timeout: 15_000, + }); + })(); + return makeVoidHandle( + p.then(() => undefined as void), + page, + ); + }, + + forward() { + const p = (async () => { + const currentHref = await page.evaluate(() => window.location.href); + void page.evaluate(() => window.history.forward()).catch(() => {}); + await page.waitForFunction((prev: string) => window.location.href !== prev, currentHref, { + timeout: 15_000, + }); + })(); + return makeVoidHandle( + p.then(() => undefined as void), + page, + ); + }, + + refresh() { + return makeVoidHandle( + page.reload().then(() => undefined as void), + page, + ); + }, + + async close() { + await context.close(); + }, + + async log() { + return [...logs]; + }, + + async deleteCookies() { + await context.clearCookies(); + }, + + async addCookie(cookie) { + await context.addCookies([ + { + ...cookie, + domain: cookie.domain ?? new URL(baseUrl).hostname, + path: cookie.path ?? "/", + }, + ]); + }, + + on(event: string, handler: (...args: unknown[]) => void) { + // oxlint-disable-next-line typescript/no-explicit-any + (page as any).on(event, handler); + }, + }; + + return instance; +} + +// ─── nextTestSetup ──────────────────────────────────────────────────────────── + +export type NextTestSetupOptions = { + /** + * The fixture directory — pass `__dirname`. This directory is both the test + * file's location and the root of the Next.js app (contains app/ or pages/). + */ + files: string; + + /** + * Ignored. Exists only for API compatibility with the upstream Next.js + * `nextTestSetup` — deployment tests are never run against vinext. + */ + skipDeployment?: boolean; + + /** + * Ignored. Exists for API compatibility. Dependency resolutions are + * handled by pnpm at the workspace level. + */ + resolutions?: Record; + + /** Ignored — build commands are not used in the vinext dev-server context. */ + buildCommand?: string; + + /** + * When true the server is NOT started immediately — the caller must call + * `next.start()` manually before making requests. + */ + skipStart?: boolean; + + skipBuild?: boolean; + + /** Ignored — package.json overrides are handled at the workspace level. */ + // oxlint-disable-next-line typescript/no-explicit-any + packageJson?: Record; + + /** Ignored — dependencies are managed by pnpm at the workspace level. */ + dependencies?: Record; + + /** Ignored — env vars should be set in the process environment before running. */ + env?: Record; +}; + +type BrowserNavigateOptions = { + locale?: string; + disableCache?: boolean; + /** + * Ignored — vinext always waits for domcontentloaded. Exists for API + * compatibility with the upstream Next.js test suite. + */ + waitHydration?: boolean; + // oxlint-disable-next-line typescript/no-explicit-any + beforePageLoad?: (page: Page) => void | Promise; +}; + +export type NextInstance = { + /** Base URL of the running dev server, e.g. "http://localhost:52341" */ + url: string; + /** + * Root directory used by the test to locate build artifacts. + * In dev mode: the fixture directory (opts.files). + * In start mode: the tmpDir where the production build was written. + * Mirrors the upstream Next.js e2e `next.testDir` property. + */ + testDir: string; + fetch(urlPath: string, init?: RequestInit): Promise; + render( + urlPath: string, + query?: Record | RequestInit, + init?: RequestInit, + ): Promise; + render$( + urlPath: string, + query?: Record | RequestInit, + init?: RequestInit, + ): Promise; + // oxlint-disable-next-line typescript/no-explicit-any + browser(urlPath: string, opts?: BrowserNavigateOptions): Promise; + + /** Read a file from the fixture directory. */ + readFile(filePath: string): Promise; + /** Read and parse a JSON file from the fixture directory. */ + // oxlint-disable-next-line typescript/no-explicit-any + readJSON(filePath: string): Promise; + /** Overwrite a file in the fixture directory. Triggers HMR. */ + patchFile(filePath: string, content: string): Promise; + /** Delete a file from the fixture directory. Triggers HMR. */ + deleteFile(filePath: string): Promise; + /** Server CLI output so far. Stub — returns empty string. */ + cliOutput: string; + /** Subscribe to server events (e.g. 'stderr'). Stub — no-op. */ + // oxlint-disable-next-line typescript/no-explicit-any + on(event: string, handler: (...args: any[]) => void): void; + /** Stop the dev server. */ + stop(): Promise; + /** Start the dev server (used with createNext({ skipStart: true })). */ + start(opts: { skipBuild?: boolean }): Promise; + /** + * Tear down the dev server. Matches the upstream Next.js `next.destroy()` + * API used by tests that call createNext() directly. + */ + destroy(): Promise; + /** The build command string. Mutable for API compat. */ + buildCommand: string; + /** Env vars passed to nextTestSetup. */ + env: Record; +}; + +export type NextTestSetupResult = { + next: NextInstance; + /** True when running in dev mode (NEXT_TEST_MODE=dev or unset). */ + isNextDev: boolean; + /** True when running in start (production) mode (NEXT_TEST_MODE=start). */ + isNextStart: boolean; + /** True when running in deploy mode (NEXT_TEST_MODE=deploy). */ + isNextDeploy: boolean; + /** Always false — vinext does not use Turbopack. */ + isTurbopack: false; + /** Always false — vinext does not use Rspack. */ + isRspack: false; + /** + * Always false. Set to true in the upstream Next.js test suite when the + * test is skipped due to deployment mode. vinext never skips on this basis. + */ + skipped: false; +}; + +/** + * Start a vinext (Vite + Next.js) dev server against the given fixture + * directory and return a `next` object whose API matches the upstream + * Next.js `nextTestSetup` helper. + * + * Call this at the top of a `describe` block. The server is started in + * `beforeAll` and torn down in `afterAll`. + */ +export function nextTestSetup(opts: NextTestSetupOptions): NextTestSetupResult { + let next!: NextInstance; + + // Production builds can take 2–3 min; give them extra headroom. + const setupTimeout = nextTestMode === "start" ? 300_000 : 90_000; + + beforeAll(async () => { + next = await createNext(opts); + }, setupTimeout); + + afterAll(async () => { + await next?.destroy(); + }); + + // Return a proxy so that `next` resolves after beforeAll runs. + const proxy = new Proxy({} as NextInstance, { + get(_t, prop) { + // oxlint-disable-next-line typescript/no-explicit-any + const val = (next as any)[prop]; + return typeof val === "function" ? val.bind(next) : val; + }, + set(_t, prop, value) { + // oxlint-disable-next-line typescript/no-explicit-any + (next as any)[prop] = value; + return true; + }, + }); + + return { + next: proxy, + isNextDev: nextTestMode === "dev", + isNextStart: nextTestMode === "start", + isNextDeploy: nextTestMode === "deploy", + isTurbopack: false, + isRspack: false, + skipped: false, + }; +} + +// ─── Shared helpers ─────────────────────────────────────────────────────────── + +function buildViteConfig( + files: string, + onLog: (msg: string) => void, +): Parameters[0] { + const customLogger = { + info(msg: string) { + onLog(msg); + process.stdout.write(msg + "\n"); + }, + warn(msg: string) { + onLog(msg); + process.stderr.write(msg + "\n"); + }, + warnOnce(msg: string) { + onLog(msg); + process.stderr.write(msg + "\n"); + }, + error(msg: string) { + onLog(msg); + process.stderr.write(msg + "\n"); + }, + clearScreen() {}, + hasErrorLogged() { + return false; + }, + hasWarned: false, + }; + return { + root: files, + configFile: false, + customLogger, + plugins: [ + // Next.js fixture files are plain .js but contain JSX. OXC derives + // lang:"js" from the extension which disables JSX parsing. This + // enforce:"pre" plugin intercepts .js files before vite:oxc and + // transforms them with lang:"jsx" explicitly so JSX is handled. + { + name: "vinext-e2e:js-as-jsx", + enforce: "pre" as const, + async transform(code: string, id: string) { + if (!id.endsWith(".js") || id.includes("node_modules")) return; + // oxlint-disable-next-line typescript/no-explicit-any + return transformWithOxc(code, id, { lang: "jsx" } as any); + }, + }, + vinext({ appDir: files }), + ], + // Hold dep-optimisation until after the first crawl pass to avoid the + // "outdated pre-bundle" 504 responses that occur in non-browser test + // clients (which can't trigger the auto-reload that Vite expects). + // Also tell rolldown's dep scanner to treat .js as JSX so it doesn't + // choke on Next.js fixture files that use JSX in plain .js files. + optimizeDeps: { + holdUntilCrawlEnd: true, + // oxlint-disable-next-line typescript/no-explicit-any + rolldownOptions: { moduleTypes: { ".js": "jsx" } } as any, + }, + server: { port: 0, cors: false }, + logLevel: "info", + } as Parameters[0]; +} + +/** + * Patch a fetch Response so its body supports Node.js EventEmitter-style + * `.on('data', cb)` that some upstream Next.js tests use. + * + * The Web Streams API `ReadableStream` doesn't have `.on()`. We attach a + * shim that, when `'data'` is subscribed, tees the body stream so that: + * 1. The event listener fork drains asynchronously, firing 'data' callbacks. + * 2. The other fork replaces res.body so `.text()` / `.json()` still work. + */ +function patchResponseBodyForNodeCompat(res: Response): Response { + const body = res.body; + if (!body) return res; + // oxlint-disable-next-line typescript/no-explicit-any + const b = body as any; + if (typeof b.on === "function") return res; // already patched or native Node stream + + const listeners: Map void>> = new Map(); + + // We lazily tee on the first 'data' subscription. Until then, body is untouched. + let eventFork: ReadableStream | null = null; + + // Replace the Response body with a getter that returns the consumer fork once + // the stream has been teed, so .text()/.json() read from the right half. + // We need to swap the body on the Response object itself; since Response.body + // is read-only we wrap in a new Response that delegates everything else. + let consumerResponse = res; + + b.on = function (event: string, cb: (...args: unknown[]) => void) { + if (!listeners.has(event)) listeners.set(event, []); + listeners.get(event)!.push(cb); + + if (event === "data" && !b._draining) { + b._draining = true; + + // Tee the original body: one fork for event callbacks, one for .text()/.json(). + const [eventStream, consumerStream] = body.tee(); + eventFork = eventStream; + + // Rebuild the consumer Response with the consumer half of the tee. + // Copy status, statusText, and headers from the original. + consumerResponse = new Response(consumerStream, { + status: res.status, + statusText: res.statusText, + headers: res.headers, + }); + // Keep a reference so fetch() returns the patched version. + // oxlint-disable-next-line typescript/no-explicit-any + (res as any)._consumerResponse = consumerResponse; + + // Start draining the event fork asynchronously. + const reader = eventFork.getReader(); + void (async () => { + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + for (const c of listeners.get("end") ?? []) c(); + break; + } + for (const c of listeners.get("data") ?? []) c(value); + } + } catch (err) { + for (const c of listeners.get("error") ?? []) c(err); + } + })(); + } + return b; + }; + + b.removeListener = function (event: string, cb: (...args: unknown[]) => void) { + const cbs = listeners.get(event); + if (cbs) { + const idx = cbs.indexOf(cb); + if (idx !== -1) cbs.splice(idx, 1); + } + return b; + }; + + // Return a proxy so that after tee, .text()/.json()/.body/.arrayBuffer() etc. + // are forwarded to the consumer half, while .body itself returns the + // original (event-listener-attached) stream object (for further .on() calls). + return new Proxy(res, { + get(target, prop) { + // Always serve .body from the original response so .on() is available. + if (prop === "body") return target.body; + // For everything else that reads the body, use the consumer fork once teed. + // oxlint-disable-next-line typescript/no-explicit-any + const src = (target as any)._consumerResponse ?? target; + // oxlint-disable-next-line typescript/no-explicit-any + const val = (src as any)[prop]; + return typeof val === "function" ? val.bind(src) : val; + }, + }); +} + +function makeNextInstance( + opts: NextTestSetupOptions, + doStart: () => Promise, + doStop: () => Promise, + getBaseUrl: () => string, + getCliOutput?: () => string, + testDir?: string, +): NextInstance { + const next: NextInstance = { + testDir: testDir ?? opts.files, + + get url() { + return getBaseUrl(); + }, + + get cliOutput() { + return getCliOutput ? getCliOutput() : ""; + }, + set cliOutput(_val: string) { + // ignore writes — cliOutput is read-only via getter + }, + buildCommand: opts.buildCommand ?? "", + env: opts.env ?? {}, + + async readFile(filePath: string) { + // In start mode (testDir = tmpDir), check the build output dir first so + // that .next/** paths resolve to build artifacts (e.g. .meta files, .rsc + // files) via the symlink tmpDir/.next/server/app → prerendered-routes. + if (testDir) { + const buildAbs = path.join(testDir, filePath); + try { + return fs.readFileSync(buildAbs, "utf-8"); + } catch { + // Fall through to source dir + } + } + const abs = path.join(opts.files, filePath); + try { + return fs.readFileSync(abs, "utf-8"); + } catch { + return ""; + } + }, + // oxlint-disable-next-line typescript/no-explicit-any + async readJSON(filePath: string): Promise { + // In start mode (testDir = tmpDir), check build output dir first. + if (testDir) { + const buildAbs = path.join(testDir, filePath); + try { + return JSON.parse(fs.readFileSync(buildAbs, "utf-8")); + } catch { + // Fall through to source dir + } + } + const abs = path.join(opts.files, filePath); + try { + return JSON.parse(fs.readFileSync(abs, "utf-8")); + } catch { + return {}; + } + }, + async patchFile(filePath: string, content: string) { + const abs = path.join(opts.files, filePath); + fs.mkdirSync(path.dirname(abs), { recursive: true }); + fs.writeFileSync(abs, content, "utf-8"); + // Brief wait for Vite HMR to pick up the change + await new Promise((r) => setTimeout(r, 200)); + }, + async deleteFile(filePath: string) { + const abs = path.join(opts.files, filePath); + try { + fs.rmSync(abs); + } catch { + // ignore if already gone + } + // Brief wait for Vite HMR to pick up the change + await new Promise((r) => setTimeout(r, 200)); + }, + on(_event: string, _handler: (...args: unknown[]) => void) { + // stub — server event subscription not implemented + }, + + async start() { + await doStart(); + }, + async stop() { + await doStop(); + }, + async destroy() { + await doStop(); + }, + + fetch(urlPath: string, init?: RequestInit) { + // Upstream Next.js tests occasionally omit the leading slash, e.g. + // next.fetch('isr-multiple/nested') + // Normalise so we always produce a valid URL. + const baseUrl = getBaseUrl(); + const normalised = urlPath.startsWith("http") + ? urlPath + : `${baseUrl}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`; + return fetch(normalised, init).then(patchResponseBodyForNodeCompat); + }, + + async render( + urlPath: string, + queryOrInit?: Record | RequestInit, + init?: RequestInit, + ) { + // Support both (path, init?) and (path, query, init?) signatures. + let resolvedInit: RequestInit | undefined; + if (init !== undefined) { + // 3-arg form: (path, query, init) — query is currently ignored (no query support needed) + resolvedInit = init; + } else if ( + queryOrInit && + ("headers" in queryOrInit || + "method" in queryOrInit || + "body" in queryOrInit || + "signal" in queryOrInit || + "redirect" in queryOrInit) + ) { + // 2-arg form: (path, init) — second arg looks like RequestInit + resolvedInit = queryOrInit as RequestInit; + } + // else: 2-arg form with query object — no init + return (await next.fetch(urlPath, resolvedInit)).text(); + }, + + async render$( + urlPath: string, + queryOrInit?: Record | RequestInit, + init?: RequestInit, + ) { + const html = await next.render(urlPath, queryOrInit, init); + // oxlint-disable-next-line typescript/unbound-method + const { load } = await import("cheerio"); + return load(html); + }, + + async browser(urlPath: string, browserOpts?: BrowserNavigateOptions) { + return makeBrowserInstance(getBaseUrl(), urlPath, browserOpts); + }, + }; + return next; +} + +/** + * Create a vinext dev server and return a NextInstance directly (no + * beforeAll/afterAll wiring). Matches the upstream Next.js `createNext` API: + * + * let next: NextInstance + * beforeAll(async () => { + * next = await createNext({ files: __dirname, skipStart: true }) + * await next.start() + * }) + * afterAll(() => next.destroy()) + */ +export async function createNext(opts: NextTestSetupOptions): Promise { + if (nextTestMode === "deploy") { + throw new Error( + "[vinext] NEXT_TEST_MODE=deploy is not yet implemented. " + + "Set NEXT_TEST_MODE to 'dev' (default) or 'start'.", + ); + } + if (nextTestMode === "start") { + return createNextStartServer(opts); + } + return createNextDevServer(opts); +} + +async function createNextStartServer(opts: NextTestSetupOptions): Promise { + const os = await import("node:os"); + const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "vinext-test-start-")); + let _buildOutput = ""; + let _httpServer: import("node:http").Server | null = null; + + // Set process.cwd() to the fixture source directory so that pages that + // use process.cwd() to locate runtime data files (e.g. isr-error-handling/error.txt) + // find them at the expected path. Restored in doStop(). + const _origCwd = process.cwd(); + process.chdir(opts.files); + + try { + // Capture build output + const origLog = console.log.bind(console); + const origWarn = console.warn.bind(console); + const origError = console.error.bind(console); + const capture = (...args: unknown[]) => { + _buildOutput += args.map(String).join(" ") + "\n"; + }; + console.log = (...a) => { + capture(...a); + origLog(...a); + }; + console.warn = (...a) => { + capture(...a); + origWarn(...a); + }; + console.error = (...a) => { + capture(...a); + origError(...a); + }; + + // Symlink node_modules into tmpDir so the production server can resolve + // externalized packages (react, react-dom, etc.) at runtime. Node.js ESM + // walks parent directories, so placing the symlink in tmpDir (the ancestor + // of server/ and server/ssr/) is sufficient. + // The fixture dir itself may not have node_modules — walk up to find it. + let nodeModulesSource: string | null = null; + for (let dir = opts.files; ; dir = path.dirname(dir)) { + const candidate = path.join(dir, "node_modules"); + if (fs.existsSync(candidate)) { + nodeModulesSource = candidate; + break; + } + const parent = path.dirname(dir); + if (parent === dir) break; // reached root + } + if (nodeModulesSource) { + await fs.promises.symlink(nodeModulesSource, path.join(tmpDir, "node_modules")); + } + + // Build: + // rscOutDir / ssrOutDir / clientOutDir are all absolute → go into tmpDir subdirs + const builder = await createBuilder({ + root: opts.files, + configFile: false, + plugins: [ + { + name: "vinext-e2e:js-as-jsx", + enforce: "pre" as const, + async transform(code: string, id: string) { + if (!id.endsWith(".js") || id.includes("node_modules")) return; + // oxlint-disable-next-line typescript/no-explicit-any + return transformWithOxc(code, id, { lang: "jsx" } as any); + }, + }, + // oxlint-disable-next-line typescript/no-explicit-any -- PluginOption type variance + { + // @vitejs/plugin-rsc hardcodes "index.js" in inter-env import paths. + // Rolldown defaults to .mjs when the fixture has no package.json with + // "type": "module". Force .js extension on rsc/ssr entry files so + // the generated import("./ssr/index.js") resolves correctly at runtime. + name: "vinext-e2e:force-js-entry-names", + apply: "build" as const, + enforce: "post" as const, + config(config: Record) { + const envs = (config as Record).environments as + | Record> + | undefined; + if (!envs) return; + const patch: Record> = {}; + for (const envName of ["rsc", "ssr"]) { + if (!envs[envName]) continue; + patch[envName] = { + build: { + rolldownOptions: { output: { entryFileNames: "[name].js" } }, + }, + }; + } + return { environments: patch } as Record; + }, + } as unknown as import("vite").PluginOption, + vinext({ + appDir: opts.files, + rscOutDir: path.join(tmpDir, "server"), + ssrOutDir: path.join(tmpDir, "server", "ssr"), + clientOutDir: path.join(tmpDir, "client"), + }), + ], + resolve: { + dedupe: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"], + }, + build: { outDir: tmpDir }, + logLevel: "warn", + }); + await builder.buildApp(); + + // Run prerender phase so ISR/force-static routes are seeded into the + // memory cache at server startup (via seedMemoryCacheFromPrerender). + // Output goes to tmpDir/server/prerendered-routes/ and the manifest to + // tmpDir/server/vinext-prerender.json — exactly where the prod server + // expects them. + // Debug: show vinext-server.json contents to verify buildId is present + const serverManifestPath = path.join(tmpDir, "server", "vinext-server.json"); + if (fs.existsSync(serverManifestPath)) { + const serverManifest = JSON.parse(fs.readFileSync(serverManifestPath, "utf-8")); + const { buildId } = serverManifest; + origLog( + "[vinext-e2e] vinext-server.json:", + JSON.stringify({ ...serverManifest, prerenderSecret: "" }), + ); + // Check if the RSC bundle contains the buildId + const rscBundlePath = path.join(tmpDir, "server", "index.js"); + if (fs.existsSync(rscBundlePath) && buildId) { + const bundleHead = fs.readFileSync(rscBundlePath, "utf-8").slice(0, 50000); + const hasBuildId = bundleHead.includes(buildId); + origLog( + "[vinext-e2e] RSC bundle contains buildId:", + hasBuildId, + "(looked for:", + buildId, + ")", + ); + } + } else { + origWarn("[vinext-e2e] vinext-server.json NOT FOUND at", serverManifestPath); + } + + // Install the external-API mock BEFORE the prerender phase so that both + // prerender renders and test-time requests use mocked responses. + // The prod server and prerender run in the same process, so the mock is + // shared via globalThis[Symbol.for(...)]. + const _OVERRIDE_KEY = Symbol.for("vinext.fetchCache.override"); + const _rawFetch = + ((globalThis as Record)[ + Symbol.for("vinext.fetchCache.originalFetch") + ] as typeof fetch | undefined) ?? fetch; + (globalThis as Record)[_OVERRIDE_KEY] = async ( + input: string | URL | Request, + init?: RequestInit, + ): Promise => { + const url = + typeof input === "string" + ? input + : input instanceof URL + ? input.href + : (input as Request).url; + if ( + url.startsWith("https://next-data-api-endpoint.vercel.app/api/random") && + !url.includes("status=") + ) { + const resp = new Response(Math.random().toString(), { + status: 200, + headers: { "content-type": "text/plain" }, + }); + // Response.url is read-only; set it via defineProperty so tests that + // read res.url (e.g. the /response-url route) get the correct value. + Object.defineProperty(resp, "url", { get: () => url, configurable: true }); + return resp; + } + return _rawFetch(input as RequestInfo, init); + }; + + try { + const { runPrerender } = await import( + path.resolve(import.meta.dirname, "../../../packages/vinext/dist/build/run-prerender.js") + ); + await runPrerender({ + root: opts.files, + rscBundlePath: path.join(tmpDir, "server", "index.js"), + outDir: path.join(tmpDir, "server", "prerendered-routes"), + manifestDir: path.join(tmpDir, "server"), + }); + } catch (prerenderErr) { + // Prerender failures are non-fatal for test infrastructure — the prod + // server will still start and serve dynamic responses. + origWarn("[vinext-e2e] prerender phase failed (non-fatal):", prerenderErr); + } + + // Create the .next/server/app symlink so tests that read build artifacts + // via next.testDir + '.next/server/app' can find prerendered files. + // vinext writes to tmpDir/server/prerendered-routes/; the symlink maps + // the Next.js-standard path onto our actual output location. + const dotNextServerApp = path.join(tmpDir, ".next", "server", "app"); + await fs.promises.mkdir(path.dirname(dotNextServerApp), { recursive: true }); + const prerenderRoutesDir = path.join(tmpDir, "server", "prerendered-routes"); + await fs.promises.mkdir(prerenderRoutesDir, { recursive: true }); + await fs.promises.symlink(prerenderRoutesDir, dotNextServerApp).catch(() => { + // Ignore if symlink already exists + }); + + // Restore console (build phase is done) + console.log = origLog; + console.warn = origWarn; + console.error = origError; + + // Start prod server + const { startProdServer } = await import( + path.resolve(import.meta.dirname, "../../../packages/vinext/dist/server/prod-server.js") + ); + const { server } = await startProdServer({ + port: 0, + host: "127.0.0.1", + outDir: tmpDir, + }); + _httpServer = server; + + // Re-install console capture for server runtime logs so next.cliOutput + // accumulates them (tests that check cliOutput rely on server-side output). + console.log = (...a) => { + _buildOutput += a.map(String).join(" ") + "\n"; + origLog(...a); + }; + console.warn = (...a) => { + _buildOutput += a.map(String).join(" ") + "\n"; + origWarn(...a); + }; + console.error = (...a) => { + _buildOutput += a.map(String).join(" ") + "\n"; + origError(...a); + }; + + const addr = server.address(); + const port = typeof addr === "object" && addr ? addr.port : 3000; + const baseUrl = `http://127.0.0.1:${port}`; + // Some fixture pages construct self-referential fetch URLs using + // process.env.PORT (e.g. force-cache/large-data). Set it to the actual + // bound port so those pages can reach the local API routes. + process.env.PORT = String(port); + + // Track patched/deleted source files so they can be restored on teardown. + // In start mode the prod server reads files from opts.files (process.cwd()), + // so patchFile edits the originals. Without restoration, a modified file + // (e.g. error.txt patched to 'yes') would persist into the next test run + // and break the prerender phase of that run. + const _patchedFiles = new Map(); // null = file didn't exist before patch + + async function doStop() { + // Restore console before teardown + console.log = origLog; + console.warn = origWarn; + console.error = origError; + // Restore any source files that were patched/deleted during tests + for (const [filePath, originalContent] of _patchedFiles) { + const abs = path.join(opts.files, filePath); + try { + if (originalContent === null) { + try { + fs.rmSync(abs); + } catch { + /* ignore */ + } + } else { + fs.writeFileSync(abs, originalContent, "utf-8"); + } + } catch { + /* ignore */ + } + } + _patchedFiles.clear(); + // Restore the original working directory + try { + process.chdir(_origCwd); + } catch {} + delete process.env.PORT; + delete (globalThis as Record)[Symbol.for("vinext.fetchCache.override")]; + await new Promise((resolve) => _httpServer?.close(() => resolve()) ?? resolve()); + _httpServer = null; + await fs.promises.rm(tmpDir, { recursive: true, force: true }); + } + + const next = makeNextInstance( + opts, + async () => {}, + doStop, + () => baseUrl, + () => _buildOutput, + tmpDir, + ); + + // Override patchFile / deleteFile to track original content so doStop() + // can restore source files after the test. Without this, a test that patches + // app/isr-error-handling/error.txt to 'yes' would leave the fixture modified, + // causing the NEXT test run's prerender to fail with the wrong file content. + const origPatchFile = next.patchFile.bind(next); + next.patchFile = async (filePath: string, content: string) => { + if (!_patchedFiles.has(filePath)) { + const abs = path.join(opts.files, filePath); + try { + _patchedFiles.set(filePath, fs.readFileSync(abs, "utf-8")); + } catch { + _patchedFiles.set(filePath, null); // file didn't exist before patch + } + } + await origPatchFile(filePath, content); + }; + const origDeleteFile = next.deleteFile.bind(next); + next.deleteFile = async (filePath: string) => { + if (!_patchedFiles.has(filePath)) { + const abs = path.join(opts.files, filePath); + try { + _patchedFiles.set(filePath, fs.readFileSync(abs, "utf-8")); + } catch { + _patchedFiles.set(filePath, null); + } + } + await origDeleteFile(filePath); + }; + + // ── Virtual file interception ───────────────────────────────────────────── + // Some tests read Next.js-specific build artifacts (e.g. .meta sidecar files, + // prerender-manifest.json) that we don't write as actual files. Instead we + // generate their content on-demand from vinext-prerender.json. + // For all other paths we fall through to the filesystem (tmpDir first, then + // the source directory via origReadFile). + + /** Lazy-loaded cache for vinext-prerender.json in this build. */ + let _prerenderIndex: Record | null = null; + let _prerenderIndexLoaded = false; + function getPrerenderIndex(): Record | null { + if (_prerenderIndexLoaded) return _prerenderIndex; + try { + _prerenderIndex = JSON.parse( + fs.readFileSync(path.join(tmpDir, "server", "vinext-prerender.json"), "utf-8"), + ); + } catch { + _prerenderIndex = null; + } + _prerenderIndexLoaded = true; + return _prerenderIndex; + } + + /** + * Generate a Next.js .meta sidecar JSON string for a prerendered route. + * Returns null if the route is not found in vinext-prerender.json. + * + * Format: { headers: { "x-next-cache-tags": "..." }, status: number } + */ + function generateMetaForRoute(routePath: string): string | null { + const idx = getPrerenderIndex(); + if (!idx || !Array.isArray(idx.routes)) return null; + // Find the rendered entry whose concrete path (or route pattern for static routes) + // matches routePath. + const entry = (idx.routes as Array>).find( + (r) => + r.status === "rendered" && (r.path === routePath || (!r.path && r.route === routePath)), + ); + if (!entry) return null; + const tags: string[] = Array.isArray(entry.tags) ? (entry.tags as string[]) : []; + // x-next-cache-tags excludes the bare pathname (e.g. /foo/bar) + const metaTags = tags.filter((t) => t !== routePath); + return JSON.stringify({ + headers: { "x-next-cache-tags": metaTags.join(",") }, + status: typeof entry.httpStatus === "number" ? entry.httpStatus : 200, + }); + } + + /** + * Generate .next/diagnostics/fetch-metrics.json content from vinext-prerender.json. + * Returns a JSON string mapping pathname → PrerenderFetchMetric[]. + * Returns null if no prerender index is available. + */ + function generateFetchMetricsJson(): string | null { + const idx = getPrerenderIndex(); + if (!idx || !Array.isArray(idx.routes)) return null; + const result: Record = {}; + for (const r of idx.routes as Array>) { + if (r.status !== "rendered") continue; + if (!Array.isArray(r.fetchMetrics) || r.fetchMetrics.length === 0) continue; + // Use concrete path when present (dynamic routes), otherwise route pattern. + const pathname = typeof r.path === "string" ? r.path : (r.route as string); + result[pathname] = r.fetchMetrics as unknown[]; + } + return JSON.stringify(result); + } + + const origReadFile = next.readFile.bind(next); + next.readFile = async (filePath: string) => { + // .next/prerender-manifest.json — return a stub so tests that only need + // the manifest version/structure don't fail. Tests that validate the + // exact manifest contents are kept in the skip list until we generate + // a proper manifest from vinext-prerender.json. + if (filePath === ".next/prerender-manifest.json") { + return JSON.stringify({ + version: 4, + routes: {}, + dynamicRoutes: {}, + notFoundRoutes: [], + preview: { previewModeId: "", previewModeSigningKey: "", previewModeEncryptionKey: "" }, + }); + } + + // .next/diagnostics/fetch-metrics.json — generate from vinext-prerender.json. + if (filePath === ".next/diagnostics/fetch-metrics.json") { + const metrics = generateFetchMetricsJson(); + if (metrics !== null) return metrics; + } + + // .next/server/app/**.meta — generate from vinext-prerender.json. + // We do not write .meta files to disk; the content is derived from the + // prerender build index which stores tags and HTTP status per route. + const metaMatch = filePath.match(/^\.next\/server\/app\/(.+)\.meta$/); + if (metaMatch) { + // Map file path segment back to URL path. + // "index" → "/" (root route special case) + // "prerendered-not-found/first" → "/prerendered-not-found/first" + const segment = metaMatch[1]; + const routePath = segment === "index" ? "/" : "/" + segment; + const meta = generateMetaForRoute(routePath); + if (meta !== null) return meta; + // Fall through — maybe there's a real file in tmpDir (e.g. from the + // 404 not-found page which might have its own entry). + } + + const tmpPath = path.join(tmpDir, filePath); + try { + return fs.readFileSync(tmpPath, "utf-8"); + } catch { + return origReadFile(filePath); + } + }; + const origReadJSON = next.readJSON.bind(next); + next.readJSON = async (filePath: string) => { + // .next/diagnostics/fetch-metrics.json — generate from vinext-prerender.json. + if (filePath === ".next/diagnostics/fetch-metrics.json") { + const metrics = generateFetchMetricsJson(); + if (metrics !== null) return JSON.parse(metrics); + } + // Intercept .meta reads via readJSON too + const metaMatch = filePath.match(/^\.next\/server\/app\/(.+)\.meta$/); + if (metaMatch) { + const segment = metaMatch[1]; + const routePath = segment === "index" ? "/" : "/" + segment; + const meta = generateMetaForRoute(routePath); + if (meta !== null) return JSON.parse(meta); + } + const tmpPath = path.join(tmpDir, filePath); + try { + return JSON.parse(fs.readFileSync(tmpPath, "utf-8")); + } catch { + return origReadJSON(filePath); + } + }; + + return next; + } catch (err) { + try { + process.chdir(_origCwd); + } catch {} + await fs.promises.rm(tmpDir, { recursive: true, force: true }).catch(() => {}); + throw err; + } +} + +async function createNextDevServer(opts: NextTestSetupOptions): Promise { + let _server: ViteDevServer | null = null; + let _baseUrl = ""; + let _cliOutput = ""; + let _origCwd: string | null = null; + + const viteConfig = buildViteConfig(opts.files, (msg) => { + _cliOutput += msg + "\n"; + }); + + async function _doStart() { + if (_server) return; + // Set process.cwd() to the fixture root so that pages using process.cwd() + // to locate data files (e.g. app/dashboard/deployments/[id]/data.json) + // resolve them correctly. Restored in _doStop(). + _origCwd = process.cwd(); + process.chdir(opts.files); + _server = await createServer(viteConfig); + + // Capture console.warn / console.error from server-side module execution + // into cliOutput so tests can assert on warning messages. + const origWarn = console.warn.bind(console); + const origError = console.error.bind(console); + console.warn = (...args: unknown[]) => { + const msg = args.map(String).join(" "); + _cliOutput += msg + "\n"; + origWarn(...args); + }; + console.error = (...args: unknown[]) => { + const msg = args.map(String).join(" "); + _cliOutput += msg + "\n"; + origError(...args); + }; + const origLog = console.log.bind(console); + console.log = (...args: unknown[]) => { + const msg = args.map(String).join(" "); + _cliOutput += msg + "\n"; + origLog(...args); + }; + + await _server.listen(); + const addr = _server.httpServer?.address() as AddressInfo | null; + _baseUrl = addr ? `http://localhost:${addr.port}` : ""; + // Some fixture pages construct self-referential fetch URLs using + // process.env.PORT (e.g. force-cache/large-data). Set it to the actual + // bound port so those pages can reach the local API routes. + if (addr?.port) process.env.PORT = String(addr.port); + + // Emit a Next.js-compatible startup message for experimental features. + // Tests like "should not have duplicate config warnings" assert that this + // message appears exactly once in cliOutput. + try { + const configPath = path.join(opts.files, "next.config.js"); + // oxlint-disable-next-line typescript/no-explicit-any + const cfg = (await import(configPath)) as any; + const nextCfg = cfg.default ?? cfg; + if (nextCfg?.experimental && Object.keys(nextCfg.experimental).length > 0) { + const experimentalKeys = Object.keys(nextCfg.experimental).join("\n - "); + _cliOutput += `Experiments (use with caution):\n - ${experimentalKeys}\n`; + } + } catch { + // next.config.js not found or not parseable — skip the startup message + } + + // Warm up: trigger Vite's first-request compilation so individual tests + // don't time out waiting for the initial RSC/SSR bundle to build. + await fetch(_baseUrl + "/").catch(() => {}); + + // Mock external API calls to eliminate network latency from tests. + // The vinext server runs in the same process, so globalThis[Symbol.for(...)] + // is shared between test code and the RSC environment. + const _OVERRIDE_KEY = Symbol.for("vinext.fetchCache.override"); + const _rawFetch = + ((globalThis as Record)[ + Symbol.for("vinext.fetchCache.originalFetch") + ] as typeof fetch | undefined) ?? fetch; + (globalThis as Record)[_OVERRIDE_KEY] = async ( + input: string | URL | Request, + init?: RequestInit, + ): Promise => { + const url = + typeof input === "string" + ? input + : input instanceof URL + ? input.href + : (input as Request).url; + // Mock api/random* calls — but NOT ?status=N variants (those test specific HTTP status codes). + if ( + url.startsWith("https://next-data-api-endpoint.vercel.app/api/random") && + !url.includes("status=") + ) { + const resp = new Response(Math.random().toString(), { + status: 200, + headers: { "content-type": "text/plain" }, + }); + Object.defineProperty(resp, "url", { get: () => url, configurable: true }); + return resp; + } + // Mock api/delay* calls — these artificially delay responses by ?delay=N ms. + // Without mocking, the priming request in stale-cache-serving tests waits for + // a real 3-second network round-trip to Vercel, adding 3s to each test run. + if (url.startsWith("https://next-data-api-endpoint.vercel.app/api/delay")) { + const resp = new Response(JSON.stringify({ random: Math.random() }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + Object.defineProperty(resp, "url", { get: () => url, configurable: true }); + return resp; + } + return _rawFetch(input as RequestInfo, init); + }; + } + + async function _doStop() { + delete (globalThis as Record)[Symbol.for("vinext.fetchCache.override")]; + delete process.env.PORT; + await _server?.close(); + _server = null; + _baseUrl = ""; + if (_origCwd) { + process.chdir(_origCwd); + _origCwd = null; + } + } + + const next = makeNextInstance( + opts, + _doStart, + _doStop, + () => _baseUrl, + () => _cliOutput, + ); + + if (!opts.skipStart) { + await _doStart(); + } + + return next; +} + +export const isNextDev = nextTestMode === "dev"; +export const isNextStart = nextTestMode === "start"; +export const isNextDeploy = nextTestMode === "deploy"; + +// ─── Global flags ──────────────────────────────────────────────────────────── +// +// Some ported Next.js tests check `(global as any).isNextDev` to gate +// production-only assertions. Set the global flags so those guards work. +(globalThis as Record).isNextDev = isNextDev; +(globalThis as Record).isNextStart = isNextStart; +(globalThis as Record).isNextDeploy = isNextDeploy; diff --git a/tests/fixtures-repos/next.js/next-test-utils.ts b/tests/fixtures-repos/next.js/next-test-utils.ts new file mode 100644 index 000000000..55d24f3bc --- /dev/null +++ b/tests/fixtures-repos/next.js/next-test-utils.ts @@ -0,0 +1,348 @@ +/** + * next-test-utils shim + * + * Provides the subset of `next-test-utils` exports that the upstream Next.js + * e2e test suite uses, backed by plain Node.js / fetch — no dependency on the + * real `next-test-utils` package. + * + * Aliased as `next-test-utils` in the Vitest integration project config so + * ported test files can keep their original imports unchanged: + * + * import { check, retry, waitFor } from 'next-test-utils' + * + * ── Implemented exports ─────────────────────────────────────────────────────── + * + * check(fn, expected) Poll fn() until it matches expected (string or + * RegExp). Throws after 30 s if never matches. + * + * retry(fn, opts?) Re-run an async assertion fn until it stops + * throwing. Useful with expect() inside the fn. + * + * waitFor(ms) Simple Promise-based sleep. + * + * renderViaHTTP(origin, path) Fetch a page and return its HTML text. + * + * fetchViaHTTP(origin, path, Low-level fetch with optional query params and + * query, init) init — mirrors Next.js test utils signature. + * + * getTitle($) Extract text from a cheerio-like $. + * + * expectVaryHeaderToContain Assert that a Response's Vary header includes + * (res, value) a specific token. + * + * normalizeRegEx(re) Strip the surrounding / delimiters from a + * RegExp.toString() result. + * + * File Re-export of the Node.js `fs/promises` File + * equivalent — exposed as a named export so test + * files that do `const { File } = require(...)` work. + * (In Node 20+ global File exists; this just + * re-exports it for explicitness.) + * + * ── Stub exports (no-op / always-pass) ─────────────────────────────────────── + * + * The following are referenced in some test files but are either irrelevant + * in the vinext context (no real Next.js server process to manage) or are + * only called in branches that are gated behind flags we never set (e.g. + * isNextStart, isNextDeploy). They are provided as stubs so import + * resolution succeeds without runtime errors in the tests we do run. + * + * launchApp, killApp, nextBuild, nextStart, nextTest, runNextCommand, + * findPort, startCleanStaticServer, shouldUseTurbopack, + * findAllTelemetryEvents, getDistDir, getClientReferenceManifest, + * assertNoConsoleErrors, getRedboxHeader, getRedboxDescription, + * getRedboxSource, getUrlFromBackgroundImage, colorToRgb, + * createMultiDomMatcher, getCacheHeader + */ + +// ─── check ─────────────────────────────────────────────────────────────────── + +const CHECK_POLL_INTERVAL_MS = 100; +const CHECK_TIMEOUT_MS = 30_000; + +/** + * Repeatedly call `fn` until the returned value satisfies `expected`. + * + * `expected` can be: + * - a string → strict equality + * - a RegExp → RegExp.test() + * - a function → called with the result, truthy return means pass + * + * Throws if the condition is not met within `timeoutMs` (default 30 s). + */ +export async function check( + fn: () => unknown | Promise<unknown>, + expected: string | RegExp | ((val: unknown) => boolean), + timeoutMs = CHECK_TIMEOUT_MS, +): Promise<void> { + const deadline = Date.now() + timeoutMs; + let lastValue: unknown; + let lastError: unknown; + + while (Date.now() < deadline) { + try { + lastValue = await fn(); + const pass = + typeof expected === "function" + ? expected(lastValue) + : expected instanceof RegExp + ? expected.test(String(lastValue)) + : lastValue === expected; + + if (pass) return; + } catch (e) { + lastError = e; + } + await waitFor(CHECK_POLL_INTERVAL_MS); + } + + if (lastError) throw lastError; + + throw new Error( + `check() timed out after ${timeoutMs} ms.\n` + + ` Expected: ${String(expected)}\n` + + ` Last value: ${JSON.stringify(lastValue)}`, + ); +} + +// ─── retry ──────────────────────────────────────────────────────────────────── + +/** + * Re-run `fn` until it resolves without throwing. + * + * Useful for wrapping expect() assertions that may need to wait for async + * state to settle, e.g.: + * + * await retry(() => expect(browser.elementById('count').text()).resolves.toBe('3')) + * + * Options: + * retries Maximum number of attempts (default 30) + * intervalMs Delay between attempts in ms (default 100) + */ +export async function retry<T>( + fn: () => T | Promise<T>, + opts: { retries?: number; intervalMs?: number } = {}, +): Promise<T> { + const { retries = 30, intervalMs = 100 } = opts; + let lastError: unknown; + + for (let attempt = 0; attempt < retries; attempt++) { + try { + return await fn(); + } catch (e) { + lastError = e; + await waitFor(intervalMs); + } + } + + throw lastError; +} + +// ─── waitFor ────────────────────────────────────────────────────────────────── + +/** + * Resolve after `ms` milliseconds. Drop-in for the Next.js test-utils `waitFor` + * which is a simple sleep (not the React Testing Library variant). + */ +export function waitFor(ms: number): Promise<void> { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// ─── renderViaHTTP ──────────────────────────────────────────────────────────── + +/** + * Fetch `path` from `appPort` (a port number or full origin string) and return + * the response body as text. + * + * const html = await renderViaHTTP(next.url, '/about') + */ +export async function renderViaHTTP( + appPort: number | string, + path: string, + query?: Record<string, string> | string, + opts?: RequestInit, +): Promise<string> { + const res = await fetchViaHTTP(appPort, path, query, opts); + return res.text(); +} + +// ─── fetchViaHTTP ───────────────────────────────────────────────────────────── + +/** + * Fetch `path` from `appPort` (a port number or full origin string). + * + * `query` may be a plain object or a pre-encoded query string. + * Extra fetch options may be passed via `opts`. + */ +export async function fetchViaHTTP( + appPort: number | string, + path: string, + query?: Record<string, string> | string | null, + opts?: RequestInit, +): Promise<Response> { + const origin = typeof appPort === "number" ? `http://localhost:${appPort}` : appPort; + + let qs = ""; + if (query != null) { + if (typeof query === "string") { + qs = query.startsWith("?") ? query : `?${query}`; + } else { + const params = new URLSearchParams(query); + qs = `?${params.toString()}`; + } + } + + const url = `${origin}${path}${qs}`; + return fetch(url, opts); +} + +// ─── getTitle ──────────────────────────────────────────────────────────────── + +/** + * Extract the text content of the first <title> element from a cheerio-like + * `$` object (or any object where `$('title').text()` works). + */ +export function getTitle($: (selector: string) => { text(): string }): string { + return $("title").text(); +} + +// ─── expectVaryHeaderToContain ─────────────────────────────────────────────── + +/** + * Assert that the `Vary` header in `res` contains `token` (case-insensitive). + * + * expectVaryHeaderToContain(res, 'RSC') + */ +export function expectVaryHeaderToContain( + varyOrRes: Response | string | null, + tokenOrTokens: string | string[], +): void { + const vary = + varyOrRes === null + ? "" + : typeof varyOrRes === "string" + ? varyOrRes + : (varyOrRes.headers.get("vary") ?? ""); + const actual = vary.split(",").map((t) => t.trim().toLowerCase()); + const expected = Array.isArray(tokenOrTokens) ? tokenOrTokens : [tokenOrTokens]; + for (const token of expected) { + if (!actual.includes(token.toLowerCase())) { + throw new Error(`Expected Vary header to contain "${token}" but got: "${vary}"`); + } + } +} + +// ─── normalizeRegEx ─────────────────────────────────────────────────────────── + +/** + * Convert a RegExp (or string) to the plain string form used in Next.js route + * manifests — strips the surrounding `/` delimiters and any flags. + * + * normalizeRegEx(/^\/blog\/(.+)$/) → '^\/blog\/(.+)$' + */ +export function normalizeRegEx(re: RegExp | string): string { + if (typeof re === "string") return re; + // RegExp.toString() → '/pattern/flags' + return re.toString().slice(1, re.toString().lastIndexOf("/")); +} + +// ─── File ───────────────────────────────────────────────────────────────────── + +// Node 20+ has a global File. Declare it as a const so test files that import +// File from this module get the real thing without a direct re-export error. +// eslint-disable-next-line no-undef +export const File = globalThis.File; + +// ─── Stubs ─────────────────────────────────────────────────────────────────── +// +// These are referenced in test files but are irrelevant in the vinext context. +// Provided as no-ops / always-resolve stubs so import resolution succeeds. + +// oxlint-disable-next-line typescript/no-explicit-any +type AnyFn = (...args: any[]) => any; + +function stub(name: string): AnyFn { + return (..._args: unknown[]) => { + console.warn( + `[next-test-utils shim] "${name}" is not implemented — ` + + `this call is a no-op in the vinext test environment.`, + ); + return Promise.resolve(undefined); + }; +} + +export const launchApp = stub("launchApp"); +export const killApp = stub("killApp"); +export const nextBuild = stub("nextBuild"); +export const nextStart = stub("nextStart"); +export const nextTest = stub("nextTest"); +export const runNextCommand = stub("runNextCommand"); +export const findPort = stub("findPort"); +export const startCleanStaticServer = stub("startCleanStaticServer"); +export const findAllTelemetryEvents = stub("findAllTelemetryEvents"); +export const getDistDir = stub("getDistDir"); +export const getClientReferenceManifest = stub("getClientReferenceManifest"); +export const assertNoConsoleErrors = stub("assertNoConsoleErrors"); +export const getRedboxHeader = stub("getRedboxHeader"); +export const getRedboxDescription = stub("getRedboxDescription"); +export const getRedboxSource = stub("getRedboxSource"); +export const getUrlFromBackgroundImage = stub("getUrlFromBackgroundImage"); +export const getCacheHeader = stub("getCacheHeader"); + +/** Always returns false — we never run under Turbopack. */ +export function shouldUseTurbopack(): boolean { + return false; +} + +/** + * Convert a CSS colour name or hex string to an `rgb(r, g, b)` string. + * Only implements the small subset used in Next.js tests. + */ +export function colorToRgb(color: string): string { + const named: Record<string, string> = { + red: "rgb(255, 0, 0)", + green: "rgb(0, 128, 0)", + blue: "rgb(0, 0, 255)", + white: "rgb(255, 255, 255)", + black: "rgb(0, 0, 0)", + yellow: "rgb(255, 255, 0)", + }; + const lower = color.toLowerCase(); + if (lower in named) return named[lower]; + + // Hex → rgb + const hex = lower.replace(/^#/, ""); + if (hex.length === 3) { + const [r, g, b] = hex.split("").map((c) => parseInt(c + c, 16)); + return `rgb(${r}, ${g}, ${b})`; + } + if (hex.length === 6) { + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + return `rgb(${r}, ${g}, ${b})`; + } + return color; +} + +/** + * Returns a function that checks whether a given DOM element (from a + * cheerio-like $) matches all of the provided matchers simultaneously. + * The upstream usage is typically: + * + * const matchesAll = createMultiDomMatcher($) + * expect(matchesAll('#id', { content: 'foo', href: '/bar' })).toBe(true) + */ +export function createMultiDomMatcher( + $: (selector: string) => { attr(name: string): string | undefined; text(): string }, +) { + return function matchesAll(selector: string, matchers: Record<string, string>): boolean { + const el = $(selector); + return Object.entries(matchers).every(([key, value]) => { + if (key === "content") return el.text().includes(value); + return el.attr(key) === value; + }); + }; +} + +export const waitForNoErrorToast = stub("waitForNoErrorToast"); diff --git a/tests/fixtures-repos/next.js/package.json b/tests/fixtures-repos/next.js/package.json new file mode 100644 index 000000000..f52f2af47 --- /dev/null +++ b/tests/fixtures-repos/next.js/package.json @@ -0,0 +1,69 @@ +{ + "name": "repos-nextjs", + "private": true, + "scripts": { + "pretest:repos": "node ../../../scripts/clone-submodule.js --dir=clone --url=https://github.com/vercel/next.js.git --sha=0cb1547d599c02abee47cce1d9a4631136e63bc6 --sparse=test/e2e", + "test:repos": "vp test" + }, + "dependencies": { + "@vitejs/plugin-rsc": "catalog:", + "cheerio": "^0.22.0", + "fs-extra": "^11.3.4", + "nanoid": "^5.1.7", + "react": "catalog:", + "react-dom": "catalog:", + "react-server-dom-webpack": "catalog:", + "strip-ansi": "^7.2.0", + "vinext": "workspace:*" + }, + "devDependencies": { + "@next/mdx": "^16.2.1", + "@next/playwright": "^16.2.1", + "@next/third-parties": "^16.2.1", + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/context-async-hooks": "^2.6.1", + "@opentelemetry/core": "^2.6.1", + "@opentelemetry/exporter-trace-otlp-grpc": "^0.214.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/instrumentation-express": "^0.62.0", + "@opentelemetry/instrumentation-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.1", + "@opentelemetry/sdk-node": "^0.214.0", + "@opentelemetry/sdk-trace-base": "^2.6.1", + "@opentelemetry/sdk-trace-node": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@react-three/offscreen": "^0.0.8", + "@types/cheerio": "0.22.16", + "@types/cookie": "^1.0.0", + "@types/cross-spawn": "^6.0.6", + "@types/escape-string-regexp": "^2.0.3", + "@types/expect": "^24.3.2", + "@types/glob": "7.1.1", + "@types/http-proxy": "^1.17.17", + "@types/jspdf": "^2.0.0", + "@types/node-fetch": "^2.6.13", + "@types/pino": "^7.0.5", + "@types/react-syntax-highlighter": "^15.5.13", + "@types/sqlite3": "^5.1.0", + "@vercel/og": "catalog:", + "cookie": "^1.1.1", + "cross-spawn": "^7.0.6", + "escape-string-regexp": "^5.0.0", + "execa": "^9.6.1", + "expect": "^30.3.0", + "get-port": "^7.2.0", + "glob": "7.1.7", + "http-proxy": "^1.18.1", + "jspdf": "^4.2.1", + "monaco-editor": "^0.55.1", + "node-fetch": "^3.3.2", + "outdent": "^0.8.0", + "pino": "^10.3.1", + "react-syntax-highlighter": "^16.1.1", + "sqlite3": "^6.0.1", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:", + "vitest": "catalog:" + } +} diff --git a/tests/fixtures-repos/next.js/skip-manifest.json b/tests/fixtures-repos/next.js/skip-manifest.json new file mode 100644 index 000000000..4561a8d3b --- /dev/null +++ b/tests/fixtures-repos/next.js/skip-manifest.json @@ -0,0 +1,32 @@ +{ + "app-dir": { + "cache-components-errors": { "*": ["*"] }, + "app": { + "index.test.ts": [ + "$contains:window.next.__internal_src_page", + "should encode chunk path correctly", + "$contains:should match redirects in pages correctly", + "should not apply client router filter on shallow", + "should serve polyfills for browsers that do not support modules" + ] + }, + "app-static": { + "app-static.test.ts": [ + "should honor fetch cache in generateStaticParams", + "should not cache request if response data size is greater than 2MB and FetchCache is possible in development mode", + + "should bailout to client rendering - with suspense boundary", + "$mode_start:should load data only at build time even if response data size is greater than 2MB and FetchCache is possible", + + "$mode_start:should not encode dynamic parameters as search parameters in RSC data", + "$mode_start:should output HTML/RSC files for static paths", + "$mode_start:should have correct prerender-manifest entries", + + "$mode_start:should stream properly for '/stale-cache-serving-edge/app-page'" + ], + "*": ["*"] + }, + "*": ["*"] + }, + "*": ["*"] +} diff --git a/tests/fixtures-repos/next.js/tsconfig.json b/tests/fixtures-repos/next.js/tsconfig.json new file mode 100644 index 000000000..e6fc900b4 --- /dev/null +++ b/tests/fixtures-repos/next.js/tsconfig.json @@ -0,0 +1,78 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowJs": true, + "noEmit": true, + + "types": ["vitest/globals", "@types/node"], + + "paths": { + "e2e-utils": ["./next-test-setup.ts"], + "next-test-utils": ["./next-test-utils.ts"], + "next-webdriver": ["./next-internal-shims.d.ts"], + "router-act": ["./next-internal-shims.d.ts"], + "development-sandbox": ["./next-internal-shims.d.ts"], + "test-data-service/writer": ["./next-internal-shims.d.ts"], + "test-log": ["./next-internal-shims.d.ts"], + "e2e-utils/request-tracker": ["./next-internal-shims.d.ts"], + "e2e-utils/ppr": ["./next-internal-shims.d.ts"], + "e2e-utils/instant-validation": ["./next-internal-shims.d.ts"] + } + }, + "include": [ + "clone", + "next-test-setup.ts", + "next-test-utils.ts", + "vitest-matchers.d.ts", + "next-internal-shims.d.ts" + ], + "exclude": [ + "clone/**/node_modules", + "clone/babel/**", + + // test/lib is not included in the sparse checkout (only test/e2e is cloned). + // These fixtures import from relative ../../../lib/ paths that don't exist. + "clone/app-dir/actions-unused-args/**", + "clone/app-dir/app-root-params-getters/**", + "clone/app-dir/instant-validation-causes/**", + "clone/app-dir/instant-validation/**", + "clone/app-dir/navigation-with-queued-actions/**", + "clone/app-dir/next-after-app-static/**", + "clone/app-dir/next-after-app/**", + "clone/app-dir/trace-build-file/**", + "clone/app-dir/segment-cache/prefetch-layout-sharing/**", + "clone/app-dir/segment-cache/search-params/**", + "clone/filesystem-cache/**", + + // These fixtures test tsconfig path resolution themselves — they intentionally + // import from aliases (foo, bar, @/foo) that only resolve within their own + // per-fixture tsconfig, not the root one. + "clone/tsconfig-path/**", + "clone/typescript-custom-tsconfig/**", + "clone/app-dir/next-config-ts/**", + + // Fixture-local or monorepo-internal packages that aren't installed here. + "clone/app-dir/app-alias/**", + "clone/app-dir/next-dist-client-esm-import/**", + "clone/app-dir/non-root-project-monorepo/**", + + // Nx plugin fixture — @nx/next is not installed and is out of scope. + "clone/app-dir/nx-handling/**", + + // PNG imports in .ts files — no declaration file for image assets. + "clone/app-dir/node-worker-threads/app/worker-dir/png-worker.ts", + "clone/app-dir/worker/app/png-worker.ts", + + // next/offline and next/src/... are non-public Next.js internals. + "clone/app-dir/use-offline/**", + "clone/next-form/default/next-form-prefetch.test.ts" + ] +} diff --git a/tests/fixtures-repos/next.js/vite.config.ts b/tests/fixtures-repos/next.js/vite.config.ts new file mode 100644 index 000000000..3be621bf3 --- /dev/null +++ b/tests/fixtures-repos/next.js/vite.config.ts @@ -0,0 +1,48 @@ +import { defineConfig, type Plugin } from "vite-plus"; +import { randomUUID } from "node:crypto"; +import { join } from "node:path"; + +/** + * Some upstream Next.js fixture files use CJS `require('./some.test')` to + * re-run another test file with different env vars (e.g. + * app-static-custom-handler.test.ts requires app-static.test.ts). In our + * ESM-first Vitest setup this fails because Node.js's CJS require can't load + * TypeScript files that use ESM `import` statements with Vite aliases. + * + * The fix: transform `require('./foo.test')` → `await import('./foo.test')`. + * Vitest processes every .ts file through vite-node's async module evaluator, + * so top-level `await` works. The dynamic `import()` goes through Vite's full + * module resolution pipeline, which resolves aliases like `e2e-utils` and + * applies TypeScript transforms correctly. + */ +const requireToImportPlugin: Plugin = { + name: "vinext-test:cjs-require-to-esm-import", + enforce: "pre", + transform(code, id) { + if (!id.includes("/clone/")) return; + if (!code.includes("require(")) return; + return code.replace( + /\brequire\((['"])(\.\/[^'"]+\.test)\1\)/g, + (_match, _quote, specifier) => `await import('${specifier}')`, + ); + }, +}; + +export default defineConfig({ + plugins: [requireToImportPlugin], + test: { + reporters: process.env.CI ? ["default", "github-actions"] : ["default", "agent"], + setupFiles: [join(import.meta.dirname, "./vitest-setup.ts")], + env: { __VINEXT_DRAFT_SECRET: randomUUID() }, + + alias: { + "e2e-utils": join(import.meta.dirname, "./next-test-setup.js"), + "next-test-utils": join(import.meta.dirname, "./next-test-utils.js"), + }, + + fileParallelism: false, + testTimeout: 30_000, + globals: true, + dir: "clone", + }, +}); diff --git a/tests/fixtures-repos/next.js/vitest-matchers.d.ts b/tests/fixtures-repos/next.js/vitest-matchers.d.ts new file mode 100644 index 000000000..f1aa3d6ed --- /dev/null +++ b/tests/fixtures-repos/next.js/vitest-matchers.d.ts @@ -0,0 +1,49 @@ +import "vitest"; + +declare module "vitest" { + type Assertion<T = unknown> = { + toEndWith(suffix: string): void; + toStartWith(prefix: string): void; + toInclude(substring: string): void; + /** + * Inline snapshot matcher for a Redbox that's popped up by default. + * When a Redbox is hidden at first and requires manual display by clicking + * the toast, use {@link toDisplayCollapsedRedbox} instead. + * + * Waits for the Next.js error overlay ("Redbox") to appear in the page, + * reads its structured content (label, description, source, stack, etc.) + * and compares it against an inline snapshot. + * + * @param inlineSnapshot - The expected snapshot string. Omit to + * auto-generate on first run. + * + * @example + * await expect(browser).toDisplayRedbox(` + * { + * "code": "E394", + * "description": "...", + * "environmentLabel": null, + * "label": "Runtime Error", + * "source": "app/page.tsx (10:1) @ Foo\\n> 10 | ...", + * "stack": [ + * "Foo app/page.tsx (10:1)", + * ], + * } + * `) + */ + toDisplayRedbox(inlineSnapshot?: string): Promise<void>; + /** + * Inline snapshot matcher for a Redbox that's collapsed by default. + * When a Redbox is immediately displayed, + * use {@link toDisplayRedbox} instead. + * + * Clicks the dev-tools / error toast to open the full Redbox dialog, + * then reads its structured content and compares it against an inline + * snapshot. + * + * @param inlineSnapshot - The expected snapshot string. Omit to + * auto-generate on first run. + */ + toDisplayCollapsedRedbox(inlineSnapshot?: string): Promise<void>; + }; +} diff --git a/tests/fixtures-repos/next.js/vitest-setup.ts b/tests/fixtures-repos/next.js/vitest-setup.ts new file mode 100644 index 000000000..dd62ee064 --- /dev/null +++ b/tests/fixtures-repos/next.js/vitest-setup.ts @@ -0,0 +1,649 @@ +/** + * vitest-setup.ts — per-file setup that enforces skip-manifest.json. + * + * Vitest runs this file inside every test file's context (via `setupFiles`). + * We read the manifest and wrap the injected globals (`it`, `test`) so that + * any test whose name appears in the skip list for the current file is + * silently converted to `it.skip` / `test.skip`. + * + * ── Manifest format (skip-manifest.json) ───────────────────────────────────── + * + * Paths are relative to the `clone/` directory. Both flat and nested forms + * are supported and may be mixed freely. + * + * Flat: + * { + * "app-dir/app/index.test.ts": ["exact test name"] + * } + * + * Nested: + * { + * "app-dir": { + * "app": { + * "index.test.ts": ["exact test name"], + * "*": ["*"] + * }, + * "*": ["*"] + * }, + * "*": ["*"] + * } + * + * ── Wildcard key "*" ────────────────────────────────────────────────────────── + * + * A `"*"` key inside a node is a fallback: any path segment that doesn't + * match an explicit sibling key falls through to it. This lets you say + * "skip everything under this directory except the files I've listed". + * + * { "app-dir": { "app": { "index.test.ts": [...], "*": ["*"] } } } + * + * means: for app-dir/app/index.test.ts use the explicit matchers; for every + * other file under app-dir/app/ skip all tests. + * + * ── Matcher syntax ──────────────────────────────────────────────────────────── + * + * "exact test name" — skip the test whose name matches exactly + * "$contains:some substring" — skip any test whose name contains the substring + * "*" — skip every test in the file + */ + +import { it, test, expect } from "vite-plus/test"; +import { readFileSync } from "node:fs"; +import { join, relative, normalize } from "node:path"; +import type { BrowserInstance } from "./next-test-setup.js"; + +// ─── toDisplayRedbox / toDisplayCollapsedRedbox ─────────────────────────────── +// +// Ported from Next.js: test/lib/add-redbox-matchers.ts +// https://github.com/vercel/next.js/blob/canary/test/lib/add-redbox-matchers.ts +// +// These matchers interact with the Next.js error overlay ("Redbox") that the +// vinext dev server renders inside a <nextjs-portal> shadow host whenever a +// runtime error occurs. +// +// Usage: +// await expect(browser).toDisplayRedbox(` +// { +// "description": "...", +// ... +// } +// `) +// +// If no snapshot is supplied the matcher still waits for the redbox and prints +// what it found — useful when first writing a new test. +// +// The implementation scrapes the live DOM via Playwright `page.evaluate` so it +// works against the vinext dev server (which renders the same Next.js error +// overlay component as the upstream dev server). + +// ── Selector constants (match what the Next.js overlay renders) ──────────────── + +const REDBOX_DIALOG_SELECTOR = "nextjs-portal [aria-labelledby='nextjs__container_errors_label']"; + +// ── DOM helpers ──────────────────────────────────────────────────────────────── + +/** Wait up to `timeoutMs` for the redbox to appear. Rejects with a descriptive + * message if it never shows up. */ +async function waitForRedbox(browser: BrowserInstance, timeoutMs = 10_000): Promise<void> { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + const found = await browser.page.evaluate((sel: string) => { + // The overlay is rendered inside a closed shadow root on <nextjs-portal>. + // We need to pierce it. + const portals = document.querySelectorAll("nextjs-portal"); + for (const portal of portals) { + const root = (portal as Element & { shadowRoot: ShadowRoot | null }).shadowRoot; + if (root && root.querySelector(sel)) return true; + } + return false; + }, REDBOX_DIALOG_SELECTOR); + if (found) return; + await new Promise((r) => setTimeout(r, 100)); + } + throw new Error( + `waitForRedbox: redbox did not appear within ${timeoutMs}ms.\n` + + `Selector: ${REDBOX_DIALOG_SELECTOR}`, + ); +} + +/** Click the error toast to open the redbox dialog when it is collapsed. */ +async function openRedbox(browser: BrowserInstance, timeoutMs = 10_000): Promise<void> { + // First, wait for the toast / dev-tools button to appear. + const toastSelector = "nextjs-portal [data-nextjs-dev-tools-button]"; + const deadline = Date.now() + timeoutMs; + let toastFound = false; + while (Date.now() < deadline) { + toastFound = await browser.page.evaluate((sel: string) => { + const portals = document.querySelectorAll("nextjs-portal"); + for (const portal of portals) { + const root = (portal as Element & { shadowRoot: ShadowRoot | null }).shadowRoot; + if (root && root.querySelector(sel)) return true; + } + return false; + }, toastSelector); + if (toastFound) break; + await new Promise((r) => setTimeout(r, 100)); + } + if (!toastFound) { + throw new Error( + `openRedbox: toast/dev-tools button did not appear within ${timeoutMs}ms. ` + + `Make sure an error occurred and the redbox is collapsed.`, + ); + } + // Click the toast to open the full redbox. + await browser.page.evaluate((sel: string) => { + const portals = document.querySelectorAll("nextjs-portal"); + for (const portal of portals) { + const root = (portal as Element & { shadowRoot: ShadowRoot | null }).shadowRoot; + const btn = root?.querySelector<HTMLElement>(sel); + if (btn) { + btn.click(); + return; + } + } + }, toastSelector); + // Wait for the full redbox dialog. + await waitForRedbox(browser, timeoutMs); +} + +type RedboxTextContent = string | null; + +/** Read a text value from inside the shadow-root redbox dialog. */ +async function readRedboxField( + browser: BrowserInstance, + fieldSelector: string, +): Promise<RedboxTextContent> { + return browser.page.evaluate( + ({ dialogSel, fieldSel }: { dialogSel: string; fieldSel: string }) => { + const portals = document.querySelectorAll("nextjs-portal"); + for (const portal of portals) { + const root = (portal as Element & { shadowRoot: ShadowRoot | null }).shadowRoot; + const dialog = root?.querySelector(dialogSel); + if (!dialog) continue; + const el = dialog.querySelector(fieldSel); + return el ? (el as HTMLElement).innerText.trim() : null; + } + return null; + }, + { dialogSel: REDBOX_DIALOG_SELECTOR, fieldSel: fieldSelector }, + ); +} + +/** Read multiple text values from inside the shadow-root redbox dialog. */ +async function readRedboxFieldAll( + browser: BrowserInstance, + fieldSelector: string, +): Promise<string[]> { + return browser.page.evaluate( + ({ dialogSel, fieldSel }: { dialogSel: string; fieldSel: string }) => { + const portals = document.querySelectorAll("nextjs-portal"); + for (const portal of portals) { + const root = (portal as Element & { shadowRoot: ShadowRoot | null }).shadowRoot; + const dialog = root?.querySelector(dialogSel); + if (!dialog) continue; + const els = dialog.querySelectorAll(fieldSel); + return Array.from(els).map((el) => (el as HTMLElement).innerText.trim()); + } + return []; + }, + { dialogSel: REDBOX_DIALOG_SELECTOR, fieldSel: fieldSelector }, + ); +} + +// ── Data extraction helpers (mirror next-test-utils getRedbox* functions) ────── + +async function getRedboxLabel(browser: BrowserInstance): Promise<string | null> { + return readRedboxField(browser, "[id^='nextjs__container_errors_label']"); +} + +async function getRedboxEnvironmentLabel(browser: BrowserInstance): Promise<string | null> { + return readRedboxField(browser, "[data-nextjs-environment-label]"); +} + +async function getRedboxDescription(browser: BrowserInstance): Promise<string | null> { + return readRedboxField(browser, "[id^='nextjs__container_errors_desc']"); +} + +async function getRedboxSource(browser: BrowserInstance): Promise<string | null> { + return readRedboxField(browser, "[data-nextjs-codeframe]"); +} + +async function getRedboxErrorCode(browser: BrowserInstance): Promise<string | null> { + return readRedboxField(browser, "[data-nextjs-error-code]"); +} + +async function getRedboxCallStack(browser: BrowserInstance): Promise<string[] | null> { + const frames = await readRedboxFieldAll( + browser, + "[data-nextjs-call-stack-frame] [data-nextjs-frame-expanded='true']", + ); + return frames.length > 0 ? frames : null; +} + +// ── Snapshot builder ─────────────────────────────────────────────────────────── + +type RedboxSnapshot = { + code?: string; + description?: string; + environmentLabel: string | null; + label: string | null; + source: string | null; + stack: string[]; +}; + +/** + * Normalise the source frame the same way Next.js does: strip surrounding + * context lines and keep only the header, the errored line (">"), and cursor. + */ +function focusSource(source: string | null): string | null { + if (source === null) return null; + let focused = ""; + const lines = source.split("\n"); + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trimEnd(); + if (line === "") continue; + if (line.startsWith(">")) { + focused += "\n" + line; + if (i + 1 < lines.length) focused += "\n" + lines[i + 1]; + break; + } + const isCodeFrameLine = /^ {2}\s*\d+ \|/.test(line); + if (!isCodeFrameLine) { + focused += "\n" + line; + } + } + return focused.trim() || null; +} + +async function buildRedboxSnapshot(browser: BrowserInstance): Promise<RedboxSnapshot> { + const [label, environmentLabel, description, rawSource, code, callStack] = await Promise.all([ + getRedboxLabel(browser), + getRedboxEnvironmentLabel(browser), + getRedboxDescription(browser), + getRedboxSource(browser), + getRedboxErrorCode(browser), + getRedboxCallStack(browser), + ]); + + const snapshot: RedboxSnapshot = { + environmentLabel, + label, + description: description ?? undefined, + source: focusSource(rawSource), + stack: callStack ?? [], + }; + + if (code !== null) { + snapshot.code = code; + } + + return snapshot; +} + +// ── Inline snapshot comparison (mirrors jest-snapshot toMatchInlineSnapshot) ─── +// +// Vitest ships its own inline-snapshot engine. We can reach it via the +// `expect` API: `expect(actual).toMatchInlineSnapshot(snapshot?)`. +// However custom matchers can't call other matchers directly via `this`. +// Instead we delegate to the public `expect(actual).toMatchInlineSnapshot()`. + +// ── Register matchers ───────────────────────────────────────────────────────── + +expect.extend({ + async toDisplayRedbox( + this: { isNot: boolean; promise: string }, + browser: BrowserInstance, + expectedSnapshot?: string, + ) { + // Capture a sync stack for better error reporting. + const syncError = new Error(); + + let snapshot: unknown; + try { + await waitForRedbox(browser); + snapshot = await buildRedboxSnapshot(browser); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + // Delegate to Vitest's own inline-snapshot matcher so the snapshot can + // be auto-written on first run. + try { + if (expectedSnapshot === undefined) { + expect(msg).toMatchInlineSnapshot(); + } else { + expect(msg).toMatchInlineSnapshot(expectedSnapshot); + } + } catch (matchErr) { + return { + pass: false, + message: () => + `${(matchErr as Error).message}\n\nOriginal error: ${(syncError as Error).stack}`, + }; + } + return { pass: false, message: () => msg }; + } + + try { + if (expectedSnapshot === undefined) { + expect(snapshot).toMatchInlineSnapshot(); + } else { + expect(snapshot).toMatchInlineSnapshot(expectedSnapshot); + } + } catch (matchErr) { + return { + pass: false, + message: () => + `${(matchErr as Error).message}\n\nSync callsite:\n${(syncError as Error).stack}`, + }; + } + + return { pass: true, message: () => "expected no redbox to be displayed" }; + }, + + async toDisplayCollapsedRedbox( + this: { isNot: boolean; promise: string }, + browser: BrowserInstance, + expectedSnapshot?: string, + ) { + const syncError = new Error(); + + let snapshot: unknown; + try { + await openRedbox(browser); + snapshot = await buildRedboxSnapshot(browser); + } catch (err: unknown) { + const msg = (err instanceof Error ? err.message : String(err)).replace( + "waitForRedbox", + "toDisplayRedbox", + ); + try { + if (expectedSnapshot === undefined) { + expect(msg).toMatchInlineSnapshot(); + } else { + expect(msg).toMatchInlineSnapshot(expectedSnapshot); + } + } catch (matchErr) { + return { + pass: false, + message: () => + `${(matchErr as Error).message}\n\nOriginal error: ${(syncError as Error).stack}`, + }; + } + return { pass: false, message: () => msg }; + } + + try { + if (expectedSnapshot === undefined) { + expect(snapshot).toMatchInlineSnapshot(); + } else { + expect(snapshot).toMatchInlineSnapshot(expectedSnapshot); + } + } catch (matchErr) { + return { + pass: false, + message: () => + `${(matchErr as Error).message}\n\nSync callsite:\n${(syncError as Error).stack}`, + }; + } + + return { pass: true, message: () => "expected no redbox to be displayed" }; + }, +}); + +// ─── jest-extended compat matchers ──────────────────────────────────────────── +// +// Some ported Next.js tests use jest-extended matchers that are not available in +// Vitest's built-in Chai assertions. We shim the subset actually used. + +expect.extend({ + toInclude(received: string, expected: string) { + const pass = typeof received === "string" && received.includes(expected); + return { + pass, + message: () => + pass + ? `expected string not to include "${expected}"` + : `expected string to include "${expected}" but got "${received}"`, + }; + }, + + toIncludeAllMembers(received: unknown[], expected: unknown[]) { + const pass = + Array.isArray(received) && + Array.isArray(expected) && + expected.every((item) => received.includes(item)); + return { + pass, + message: () => + pass + ? `expected array not to include all members ${JSON.stringify(expected)}` + : `expected ${JSON.stringify(received)} to include all members ${JSON.stringify(expected)}`, + }; + }, + + toStartWith(received: string, expected: string) { + const pass = typeof received === "string" && received.startsWith(expected); + return { + pass, + message: () => + pass + ? `expected string not to start with "${expected}"` + : `expected "${received}" to start with "${expected}"`, + }; + }, + + toEndWith(received: string, expected: string) { + const pass = typeof received === "string" && received.endsWith(expected); + return { + pass, + message: () => + pass + ? `expected string not to end with "${expected}"` + : `expected "${received}" to end with "${expected}"`, + }; + }, +}); + +// ─── Types ──────────────────────────────────────────────────────────────────── + +type ManifestLeaf = string[]; +type ManifestNode = { [key: string]: ManifestNode | ManifestLeaf }; +type ManifestRoot = ManifestNode; + +// ─── Load manifest ──────────────────────────────────────────────────────────── + +const manifest: ManifestRoot = JSON.parse( + readFileSync(join(import.meta.dirname, "skip-manifest.json"), "utf-8"), +); + +// ─── Runtime lookup with "*" fallback ──────────────────────────────────────── +// +// Walk the manifest tree one path segment at a time. At each node: +// 1. Try the exact segment key first. +// 2. If not found, fall back to the "*" key if present. +// 3. If neither exists, there are no matchers for this path → return null. +// +// When we reach a leaf (string[]) we return it as the matcher set. +// A flat key like "app-dir/app/index.test.ts" is split on "/" and treated as +// multiple segments, so flat and nested forms both work. + +function lookup(segments: string[]): Set<string> | null { + // oxlint-disable-next-line typescript/no-explicit-any + let node: any = manifest; + + for (const seg of segments) { + if (Array.isArray(node)) { + // We've hit a leaf before consuming all segments — no match. + return null; + } + + if (Object.prototype.hasOwnProperty.call(node, seg)) { + node = node[seg]; + } else if (Object.prototype.hasOwnProperty.call(node, "*")) { + const wildcard = node["*"]; + // If the wildcard is a leaf array, it applies to this entire subtree — + // return it immediately without consuming the remaining segments. + if (Array.isArray(wildcard)) return new Set(wildcard as string[]); + node = wildcard; + } else { + return null; + } + } + + if (Array.isArray(node)) { + return new Set(node as string[]); + } + + // Landed on an intermediate node (directory), not a leaf — check for "*". + // oxlint-disable-next-line typescript/no-explicit-any + if (Object.prototype.hasOwnProperty.call(node, "*")) { + const wildcard = (node as ManifestNode)["*"]; + if (Array.isArray(wildcard)) return new Set(wildcard as string[]); + } + + return null; +} + +// ─── Resolve skip set for the current test file ─────────────────────────────── + +// `expect.getState().testPath` is populated by Vitest before setupFiles run +// and refers to the test file being collected, not this setup file. +const relPath = normalize( + relative(join(import.meta.dirname, "clone"), expect.getState().testPath ?? ""), +).replace(/\\/g, "/"); + +// Split on "/" to get individual segments for the tree walk. +const skipMatchers = lookup(relPath.split("/")) ?? new Set<string>(); + +// ─── Matcher logic ──────────────────────────────────────────────────────────── + +const _testMode = process.env.NEXT_TEST_MODE ?? "dev"; + +/** Test whether a single leaf matcher string matches a test name. */ +function matchesMatcher(name: string, matcher: string): boolean { + if (matcher === "*") return true; + if (matcher.startsWith("$contains:")) return name.includes(matcher.slice("$contains:".length)); + return name === matcher; +} + +function shouldSkip(name: string): boolean { + for (const matcher of skipMatchers) { + let effective = matcher; + + // Mode-gated prefixes: only apply when the current mode matches. + if (matcher.startsWith("$mode_start:")) { + if (_testMode !== "start") continue; + effective = matcher.slice("$mode_start:".length); + } else if (matcher.startsWith("$mode_dev:")) { + if (_testMode !== "dev") continue; + effective = matcher.slice("$mode_dev:".length); + } + + if (matchesMatcher(name, effective)) return true; + } + return false; +} + +/** + * Expand an it.each template string for a given row object. + * Vitest replaces `$key` with `'stringValue'` (single-quoted) or the raw + * string representation for non-strings. + */ +function expandEachTemplate(template: string, row: unknown): string { + if (typeof row !== "object" || row === null || Array.isArray(row)) return template; + return template.replace(/\$([\w.]+)/g, (match, key) => { + const val = (row as Record<string, unknown>)[key]; + if (val === undefined) return match; + if (typeof val === "string") return `'${val}'`; + // oxlint-disable-next-line typescript/no-base-to-string + return String(val); + }); +} + +// ─── Wrap it / test ─────────────────────────────────────────────────────────── +// +// We use a Proxy rather than Object.assign so that every property access +// (including `it.each`, `it.skip`, `it.only`, etc.) is forwarded to the +// real runner with the correct `this`. Object.assign copies function +// references but loses the internal `this` binding that Vitest's `each` +// implementation relies on (`withContext`), causing a runtime crash. + +function wrapRunner(runner: typeof it): typeof it { + return new Proxy(runner, { + apply(_target, _thisArg, args: unknown[]) { + // oxlint-disable-next-line typescript/no-explicit-any + const [name, ...rest] = args as [string, ...any[]]; + if (shouldSkip(name)) return runner.skip(name, ...rest); + return runner(name, ...rest); + }, + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + // `it.each(table)` returns a registrar function that is later called + // with the generated test name. Wrap that registrar so the generated + // names also pass through shouldSkip. + if (prop === "each" && typeof value === "function") { + // oxlint-disable-next-line typescript/no-explicit-any + return (...tableArgs: any[]) => { + // For per-row skip matching, we need the raw table of objects. + const rawTable: unknown[] | null = + Array.isArray(tableArgs[0]) && tableArgs.length === 1 ? tableArgs[0] : null; + + // oxlint-disable-next-line typescript/no-explicit-any + const registrar = (value as any).call(target, ...tableArgs); + if (typeof registrar !== "function") return registrar; + // oxlint-disable-next-line typescript/no-explicit-any + const wrapped = (name: string, fn: unknown, ...rest: any[]) => { + // oxlint-disable-next-line typescript/no-explicit-any + if (shouldSkip(name)) return runner.skip(name, fn as unknown as any, ...rest); + + // For object-row tables, also check per-row expanded names so that + // individual it.each variants can be skipped without skipping the + // whole parameterised group. + if ( + rawTable && + rawTable.length > 0 && + typeof rawTable[0] === "object" && + !Array.isArray(rawTable[0]) + ) { + const keepRows: unknown[] = []; + const skipRows: unknown[] = []; + for (const row of rawTable) { + if (shouldSkip(expandEachTemplate(name, row))) skipRows.push(row); + else keepRows.push(row); + } + + if (skipRows.length > 0 && keepRows.length === 0) { + // All rows skipped — use the template-level skip. + // oxlint-disable-next-line typescript/no-explicit-any + return runner.skip(name, fn as unknown as any, ...rest); + } + + if (skipRows.length > 0) { + // Some rows skipped — register each skipped row individually, + // then run the remaining rows with a filtered table. + for (const row of skipRows) { + // oxlint-disable-next-line typescript/no-explicit-any + runner.skip(expandEachTemplate(name, row), fn as unknown as any); + } + // oxlint-disable-next-line typescript/no-explicit-any + const filteredRegistrar = (value as any).call(target, keepRows); + if (typeof filteredRegistrar === "function") { + return filteredRegistrar(name, fn, ...rest); + } + return; + } + } + + return registrar(name, fn, ...rest); + }; + // Preserve .skip/.only on the returned registrar as well. + return Object.assign(wrapped, registrar); + }; + } + return value; + }, + }); +} + +// Patch globalThis so upstream test files (which don't import these globals) +// pick up the wrapped versions. +(globalThis as Record<string, unknown>).it = wrapRunner(it); +(globalThis as Record<string, unknown>).test = wrapRunner(test); diff --git a/vite.config.ts b/vite.config.ts index 0b93255c8..5d89da371 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ semi: true, singleQuote: false, trailingComma: "all", - ignorePatterns: ["tests/fixtures/ecosystem/**", "examples/**"], + ignorePatterns: ["tests/fixtures/ecosystem/**", "examples/**", "tests/fixtures-repos/*/clone"], }, lint: { ignorePatterns: [ @@ -20,6 +20,7 @@ export default defineConfig({ "tests/fixtures/**", "tests/fixtures/ecosystem/**", "examples/**", + "tests/fixtures-repos/*/clone", ], options: { typeAware: true, @@ -78,6 +79,7 @@ export default defineConfig({ include: ["tests/**/*.test.ts"], exclude: [ "tests/fixtures/**/node_modules/**", + "tests/fixtures-repos/**/clone/**", // Integration tests: spin up Vite dev servers against shared fixture // dirs. Must run serially to avoid Vite deps optimizer cache races // (node_modules/.vite/*) that produce "outdated pre-bundle" 500s. @@ -134,4 +136,7 @@ export default defineConfig({ }, ], }, + run: { + enablePrePostScripts: true, + }, });