diff --git a/.changeset/bright-routes-run.md b/.changeset/bright-routes-run.md new file mode 100644 index 0000000..9055b0c --- /dev/null +++ b/.changeset/bright-routes-run.md @@ -0,0 +1,5 @@ +--- +'rsbuild-plugin-react-router': patch +--- + +Improve route analysis and route chunking performance for larger applications, with benchmark tooling to track build overhead. diff --git a/.changeset/coherent-dev-generations.md b/.changeset/coherent-dev-generations.md new file mode 100644 index 0000000..e6d97c8 --- /dev/null +++ b/.changeset/coherent-dev-generations.md @@ -0,0 +1,13 @@ +--- +'rsbuild-plugin-react-router': minor +--- + +Keep development SSR requests on the last successfully evaluated atomic set of +React Router server entries and their paired web manifests, and expose +`loadReactRouterServerBuild` so custom servers use the same last-good pair. +Expose `resolveReactRouterServerBuild` to normalize ESM and CommonJS production +server modules through the same validated build boundary. +Preserve `serverBundles` through config normalization and publish every +configured bundle atomically with its exact filtered manifest. +This does not snapshot deferred server chunks, make emitted client assets +atomic, or delay Rsbuild's WebSocket success notification. diff --git a/.changeset/lazy-entry-hydration.md b/.changeset/lazy-entry-hydration.md new file mode 100644 index 0000000..e833143 --- /dev/null +++ b/.changeset/lazy-entry-hydration.md @@ -0,0 +1,5 @@ +--- +'rsbuild-plugin-react-router': patch +--- + +Keep React Router hydration entries compatible with Rsbuild lazy compilation when `entries: true` is enabled. diff --git a/.changeset/quiet-topology-order.md b/.changeset/quiet-topology-order.md new file mode 100644 index 0000000..2e14bc3 --- /dev/null +++ b/.changeset/quiet-topology-order.md @@ -0,0 +1,6 @@ +--- +'rsbuild-plugin-react-router': patch +--- + +Preserve route topology declaration order during development so reordering route +entries is detected as a topology change. diff --git a/.changeset/sharp-routes-heal.md b/.changeset/sharp-routes-heal.md new file mode 100644 index 0000000..f4645f8 --- /dev/null +++ b/.changeset/sharp-routes-heal.md @@ -0,0 +1,6 @@ +--- +'rsbuild-plugin-react-router': patch +--- + +Harden route module transforms and development route watching so source maps, +server/client-only modules, and route topology restarts behave consistently. diff --git a/.changeset/warm-watchers-rest.md b/.changeset/warm-watchers-rest.md new file mode 100644 index 0000000..5ec061f --- /dev/null +++ b/.changeset/warm-watchers-rest.md @@ -0,0 +1,6 @@ +--- +'rsbuild-plugin-react-router': patch +--- + +Avoid duplicate startup route topology scans and tighten development watcher +lifecycle handling for route additions and removals. diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index bab0658..2987f83 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -21,7 +21,7 @@ jobs: env: BENCHMARK_PROFILE: large BENCHMARK_MODE: dev - BENCHMARK_ITERATIONS: '3' + BENCHMARK_ITERATIONS: '10' BENCHMARK_WARMUP: '0' BENCHMARK_DEV_ROUTES: auto BENCHMARK_HISTORY_BRANCH: benchmark-results diff --git a/README.md b/README.md index 65a0a18..d96dccf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ A Rsbuild plugin that provides seamless integration with React Router, supportin ## Features - - 🚀 Zero-config setup with sensible defaults - 🔄 Automatic route generation from file system - 🖥️ Server-Side Rendering (SSR) support @@ -58,11 +57,11 @@ export default defineConfig(() => { // Optional: Enable custom server mode customServer: false, // Optional: Specify server output format - serverOutput: "commonjs", + serverOutput: 'commonjs', // Optional: enable experimental support for module federation - federation: false - }), - pluginReact() + federation: false, + }), + pluginReact(), ], }; }); @@ -73,6 +72,7 @@ export default defineConfig(() => { The plugin uses a two-part configuration system: 1. **Plugin Options** (in `rsbuild.config.ts`): + ```ts pluginReactRouter({ /** @@ -87,7 +87,36 @@ pluginReactRouter({ * Options: "commonjs" | "module" * @default "module" */ - serverOutput?: "commonjs" | "module" + serverOutput?: "commonjs" | "module", + + /** + * Rsbuild dev-only lazy compilation behavior. + * The plugin guards React Router hydration-critical modules so + * `lazyCompilation: { entries: true }` remains enabled without replacing + * manifest route modules with lazy entry proxies. + * @default undefined + */ + lazyCompilation?: boolean | Rspack.LazyCompilationOptions, + + /** + * Emit structured React Router plugin timing logs. + * @default false + */ + logPerformance?: boolean, + + /** + * Run route transforms in a worker-thread pool. + * Pass `false` to disable or an integer to override the default worker count. + * @default true. The default uses available CPU cores minus 2, capped at 4. + */ + parallelTransforms?: false | number, + + /** + * Route-topology notification for programmatic/custom dev servers. + * Recreate the Rsbuild server when this fires. + */ + onRouteTopologyChange?: () => void | Promise, + /** * Enable experimental support for module federation * @default false @@ -106,6 +135,7 @@ passing the build to React Router's request handler. ``` 2. **React Router Configuration** (in `react-router.config.*`): + ```ts import type { Config } from '@react-router/dev/config'; @@ -120,19 +150,19 @@ export default { * The file name for the server build output. * @default "index.js" */ - serverBuildFile: "index.js", + serverBuildFile: 'index.js', /** * The output format for the server build. * Options: "esm" | "cjs" * @default "esm" */ - serverModuleFormat: "esm", + serverModuleFormat: 'esm', /** * Split server bundles by route branch (advanced). */ - serverBundles: async ({ branch }) => branch[0]?.id ?? "main", + serverBundles: async ({ branch }) => branch[0]?.id ?? 'main', /** * Hook called after the build completes. @@ -160,12 +190,10 @@ export default { basename: '/my-app', /** - * React Router future flags (optional). - * Example: split client route modules into separate chunks. + * Split client route module exports into separate chunks. + * @default true */ - future: { - v8_splitRouteModules: true, - }, + splitRouteModules: true, } satisfies Config; ``` @@ -256,13 +284,14 @@ export default { } satisfies Config; ``` -For large sites, you can tune prerender concurrency: +Prerendering defaults to one path at a time, matching React Router. You can opt +into concurrent prerendering for large sites: ```ts export default { ssr: false, prerender: { - paths: ['/','/about'], + paths: ['/', '/about'], unstable_concurrency: 4, }, } satisfies Config; @@ -275,7 +304,12 @@ If no configuration is provided, the following defaults will be used: ```ts // Plugin defaults (rsbuild.config.ts) { - customServer: false + customServer: false, + serverOutput: 'module', + federation: false, + lazyCompilation: undefined, // Rsbuild's dev defaults still apply + logPerformance: false, + parallelTransforms: undefined // workers enabled with the default worker count } // Router defaults (react-router.config.ts) @@ -287,6 +321,21 @@ If no configuration is provided, the following defaults will be used: } ``` +Route transforms use worker threads by default when spare CPU cores are +available. The automatic worker count uses available CPU cores minus 2, limits +3-4 core machines to 1 worker, and is capped at 4 workers on larger machines to +avoid oversubscribing parallel web and node dev compilers. On machines with no +spare cores, transforms run inline. Pass a positive integer to override that +count, or `false` to force inline transforms. + +For builds with 256+ routes, detailed file-size reporting is compacted to totals +by default to avoid gzipping and printing thousands of assets. Set +`performance.printFileSize` to an object to customize that output. + +Route transform source maps are generated in development only. If you enable +Rsbuild source maps for faster local debugging, prefer a cheap JS map: +`output.sourceMap: { js: 'cheap-module-source-map', css: false }`. + ### Route Configuration Routes can be defined in `app/routes.ts` using the helper functions from `@react-router/dev/routes`: @@ -326,6 +375,7 @@ export default [ ``` The plugin provides several helper functions for defining routes: + - `index()` - Creates an index route - `route()` - Creates a regular route with a path - `layout()` - Creates a layout route with nested children @@ -336,6 +386,7 @@ The plugin provides several helper functions for defining routes: Route components support the following exports: #### Client-side Exports + - `default` - The route component - `ErrorBoundary` - Error boundary component - `HydrateFallback` - Loading component during hydration @@ -349,6 +400,7 @@ Route components support the following exports: - `shouldRevalidate` - Revalidation control #### Server-side Exports + - `loader` - Server-side data loading - `action` - Server-side form actions - `middleware` - Server-side middleware @@ -387,34 +439,37 @@ export default defineConfig(() => { return { plugins: [ pluginReactRouter({ - customServer: true - }), - pluginReact() + customServer: true, + }), + pluginReact(), ], }; }); ``` -When using a custom server, you'll need to: +If the server is created programmatically with `createDevServer()`, pass +`onRouteTopologyChange` and use it to recreate that server. Rsbuild's +`reload-server` watcher is owned by the CLI and is not installed by the +programmatic API. The callback is a notification and is not awaited, so it can +safely start a serialized replacement task. Always `await` the active server's +`close()` before calling `createDevServer()` again; the plugin rejects overlapping +or out-of-order replacement instead of closing one server from inside another +server's startup hooks. If startup fails before returning a server, or if +`close()` rejects, restart the process before retrying unless you can externally +prove and force complete teardown; a fresh Rsbuild instance alone is not +sufficient. Do not launch concurrent `createDevServer()` calls. + +Create one server entry point (`server.js`) and let it own the React Router +request handler in both development and production. Only the build provider +changes between modes: -1. Create a server handler (`server/index.ts`): -```ts -import { createRequestHandler } from '@react-router/express'; - -export const app = createRequestHandler({ - build: () => import('virtual/react-router/server-build'), - getLoadContext() { - // Add custom context available to your loaders/actions - return { - // ... your custom context - }; - }, -}); -``` - -2. Set up your server entry point (`server.js`): ```js import { createRsbuild, loadConfig } from '@rsbuild/core'; +import { createRequestHandler } from '@react-router/express'; +import { + loadReactRouterServerBuild, + resolveReactRouterServerBuild, +} from 'rsbuild-plugin-react-router'; import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -426,61 +481,83 @@ const app = express(); const isDev = process.env.NODE_ENV !== 'production'; async function startServer() { + let devServer; + let build; + if (isDev) { const config = await loadConfig(); const rsbuild = await createRsbuild({ rsbuildConfig: config.content, }); - const devServer = await rsbuild.createDevServer(); - app.use(devServer.middlewares); - - app.use(async (req, res, next) => { - try { - const bundle = await devServer.environments.node.loadBundle('app'); - await bundle.app(req, res, next); - } catch (e) { - next(e); - } - }); - - const port = Number.parseInt(process.env.PORT || '3000', 10); - const server = app.listen(port, () => { - console.log(`Development server is running on http://localhost:${port}`); - devServer.afterListen(); - }); - devServer.connectWebSocket({ server }); + const currentDevServer = await rsbuild.createDevServer(); + devServer = currentDevServer; + app.use(currentDevServer.middlewares); + build = () => loadReactRouterServerBuild(currentDevServer); } else { - // Production mode - app.use(express.static(path.join(__dirname, 'build/client'), { - index: false - })); - - // Load the server bundle - const serverBundle = await import('./build/server/static/js/app.js'); - // Mount the server app after static file handling - app.use(async (req, res, next) => { - try { - await serverBundle.default.app(req, res, next); - } catch (e) { - next(e); - } - }); - - const port = Number.parseInt(process.env.PORT || '3000', 10); - app.listen(port, () => { - console.log(`Production server is running on http://localhost:${port}`); - }); + app.use( + express.static(path.join(__dirname, 'build/client'), { + index: false, + }) + ); + build = await resolveReactRouterServerBuild( + import('./build/server/static/js/app.js') + ); } + + app.use( + createRequestHandler({ + build, + mode: isDev ? 'development' : 'production', + getLoadContext() { + return { + // Add custom loader/action context here. + }; + }, + }) + ); + + const port = Number.parseInt(process.env.PORT || '3000', 10); + const server = app.listen(port, () => { + const mode = isDev ? 'Development' : 'Production'; + console.log(`${mode} server is running on http://localhost:${port}`); + devServer?.afterListen(); + }); + devServer?.connectWebSocket({ server }); } startServer().catch(console.error); ``` -3. Update your `package.json` scripts: +`loadReactRouterServerBuild` waits for a complete React Router development +generation. During rebuilds it returns the last successfully evaluated server +build, whose embedded manifest is paired with the selected web compilation. +A failed or incomplete candidate does not replace that last-good pair. The +built-in development middleware uses the same path. Calling +`devServer.environments.node.loadBundle()` directly bypasses this guarantee. + +When `serverBundles` is configured, pass its exact Rsbuild entry name as the +optional second argument (for example, `bundle-a/index`). The default build +and every configured bundle are +evaluated and published as one generation; one failing bundle keeps the whole +previous generation active. + +`resolveReactRouterServerBuild` accepts an imported production server module, +normalizes ESM and CommonJS namespace shapes, resolves supported asynchronous +build exports, and validates the result before it reaches React Router. + +This guarantee covers the eagerly evaluated server entry object and its +embedded manifest. It does not snapshot deferred server chunks, make emitted +client assets immutable, or delay Rsbuild's WebSocket success notification. +Same-path server or client chunks can change before the matching framework +generation commits. Closing that publication gap requires a supported Rsbuild +graph-settled hook plus immutable or staged outputs. + +Then update your `package.json` scripts: + ```json { "scripts": { - "dev": "node server.js", + "dev": "NODE_ENV=development NODE_OPTIONS=\"--experimental-vm-modules\" node server.js", "build": "rsbuild build", "start": "NODE_ENV=production node server.js" } @@ -488,6 +565,7 @@ startServer().catch(console.error); ``` The custom server setup allows you to: + - Add custom middleware - Handle API routes - Integrate with databases @@ -500,6 +578,7 @@ The custom server setup allows you to: To deploy your React Router app to Cloudflare Workers: 1. **Configure Rsbuild** (`rsbuild.config.ts`): + ```ts import { defineConfig } from '@rsbuild/core'; import { pluginReact } from '@rsbuild/plugin-react'; @@ -524,17 +603,24 @@ export default defineConfig({ module: true, }, resolve: { - conditionNames: ['workerd', 'worker', 'browser', 'import', 'require'], + conditionNames: [ + 'workerd', + 'worker', + 'browser', + 'import', + 'require', + ], }, }, }, }, }, - plugins: [pluginReactRouter({customServer: true}), pluginReact()], + plugins: [pluginReactRouter({ customServer: true }), pluginReact()], }); ``` 2. **Configure Wrangler** (`wrangler.toml`): + ```toml workers_dev = true name = "my-react-router-worker" @@ -552,6 +638,7 @@ VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare" ``` 3. **Create Worker Entry** (`server/index.ts`): + ```ts import { createRequestHandler } from 'react-router'; @@ -588,6 +675,7 @@ export default { ``` 4. **Update Package Dependencies**: + ```json { "dependencies": { @@ -605,6 +693,7 @@ export default { ``` 5. **Setup Deployment Scripts** (`package.json`): + ```json { "scripts": { @@ -630,6 +719,7 @@ export default { ### Development Workflow: 1. Local Development: + ```bash # Start local development server npm run dev @@ -646,6 +736,7 @@ export default { ## Development The plugin automatically: + - Runs type generation during development and build - Sets up development server with live reload - Handles route-based code splitting @@ -657,7 +748,7 @@ React Router "Framework Mode" wraps Data Mode using a Vite plugin. This Rsbuild plugin aims to match the important behaviors without depending on Vite: - Typegen + Route Module API types (`./+types/*`) -- Route module splitting (`future.v8_splitRouteModules`) +- Route module splitting (`splitRouteModules`) - SPA mode (`ssr: false`), SSR mode, and static prerendering (`prerender`) Some Vite-specific integrations (for example Vite's environment API + critical @@ -667,17 +758,17 @@ CSS endpoint) are not supported 1:1. The repository includes several examples demonstrating different use cases: -| Example | Description | Port | Command | -|---------|-------------|------|---------| -| [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` | -| [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` | -| [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` | -| [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` | -| [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` | -| [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` | -| [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` | -| [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` | -| [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` | +| Example | Description | Port | Command | +| ----------------------------------------------------------------------- | --------------------------------------- | ---- | ---------- | +| [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` | +| [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` | +| [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` | +| [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` | +| [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` | +| [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` | +| [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` | +| [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` | +| [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` | Each example has unique ports configured to allow running multiple examples simultaneously. diff --git a/benchmarks/README.md b/benchmarks/README.md index c5defff..1a15002 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -69,6 +69,20 @@ When `--rspack-trace-output` is provided, the benchmark writes one absolute trace file per run under that directory so Rsbuild does not resolve the path inside each generated `.rspack-profile-*` directory. +To capture Rspack tracing output for a benchmark, pass `--rspack-profile`: + +```sh +node scripts/bench-builds.mjs --profile=smoke --iterations=1 --warmup=0 --rspack-profile=OVERVIEW +node scripts/bench-builds.mjs --profile=full --filter=synthetic-1024 --iterations=1 --warmup=0 --rspack-profile=ALL +``` + +Trace directories are moved from fixture roots into +`.benchmark/results//rspack-profiles/` and referenced from the JSON +result. `ALL` can produce large traces; use it for targeted runs. +When `--rspack-trace-output` is provided, the benchmark writes one absolute +trace file per run under that directory so Rsbuild does not resolve the path +inside each generated `.rspack-profile-*` directory. + ## Baseline Shape The synthetic fixture keeps app behavior simple and scales route count/export diff --git a/config/package.json b/config/package.json index 1bcf48a..8a53e8c 100644 --- a/config/package.json +++ b/config/package.json @@ -3,7 +3,7 @@ "version": "1.0.1", "private": true, "devDependencies": { - "@rsbuild/core": "2.0.15", + "@rsbuild/core": "2.1.0", "@rslib/core": "0.22.1", "@types/node": "^25.0.10", "typescript": "^5.9.3" diff --git a/config/rslib.config.ts b/config/rslib.config.ts index b005996..9b76aba 100644 --- a/config/rslib.config.ts +++ b/config/rslib.config.ts @@ -13,7 +13,6 @@ export const nodeMinifyConfig: Minify = { css: false, jsOptions: { minimizerOptions: { - // preserve variable name and disable minify for easier debugging mangle: false, minify: false, compress: true, @@ -21,14 +20,13 @@ export const nodeMinifyConfig: Minify = { }, }; -// Clean tsc cache to ensure the dts files can be generated correctly export const pluginCleanTscCache: RsbuildPlugin = { name: 'plugin-clean-tsc-cache', setup(api) { api.onBeforeBuild(() => { const tsbuildinfo = path.join( - api.context.rootPath, - 'tsconfig.tsbuildinfo', + api.context.rootPath, + 'tsconfig.tsbuildinfo' ); if (fs.existsSync(tsbuildinfo)) { fs.rmSync(tsbuildinfo); @@ -42,8 +40,8 @@ export const esmConfig: LibConfig = { syntax: 'es2021', shims: { esm: { - __dirname: true - } + __dirname: true, + }, }, dts: { build: true, @@ -51,10 +49,6 @@ export const esmConfig: LibConfig = { plugins: [pluginCleanTscCache], output: { minify: nodeMinifyConfig, - externals: { - '@babel/traverse': 'commonjs @babel/traverse', - '@babel/generator': 'commonjs @babel/generator', - } }, }; diff --git a/docs/coherent-dev-generations.md b/docs/coherent-dev-generations.md new file mode 100644 index 0000000..5acc592 --- /dev/null +++ b/docs/coherent-dev-generations.md @@ -0,0 +1,70 @@ +# Coherent React Router development generations + +The plugin builds React Router applications with separate `web` and `node` +compilers. During development, those compilers can finish at different times. +Publishing each result independently can pair a new browser manifest with an +older server entry object. + +## Contract + +The plugin exposes only a committed React Router development generation: + +- every configured React Router server entry was evaluated successfully; +- each entry's embedded manifest came from the selected web compilation; +- failed, incomplete, and superseded candidates cannot replace the last-good + generation; and +- built-in middleware and `loadReactRouterServerBuild(devServer)` read the same + committed generation. + +Requests capture one committed server entry object for their lifetime. Calling +`loadReactRouterServerBuild(devServer, entryName)` selects a configured server +bundle by its exact Rsbuild entry name; omitting `entryName` selects the full +default build. All entries switch generations together. The public helper is +the supported build provider for custom development servers; calling +`devServer.environments.node.loadBundle()` directly bypasses this contract. + +## Lifecycle model + +A candidate records the exact web compilation used to produce its manifests and +the corresponding evaluated node builds. It becomes visible only after +Rsbuild's aggregate development callback supplies a complete, error-free pair. +One-sided callbacks are accepted only when their known changed files do not +intersect the unchanged compiler's dependencies. + +Each node compilation also records the latest completed web compilation when it +starts. If rapid edits produce a callback containing a node result paired with +a different web compilation, that mixed candidate is discarded. Fatal compiler +failures reject initial waiters promptly; later failures preserve last-good +output. + +The committed generation remains available while a later candidate builds. +Initial compilation failures are reported to requests; failures after a commit +leave the last-good generation available. Starting a replacement dev server +creates a new lifecycle session so callbacks from the old session cannot +publish into the new one. + +Programmatic replacement requires callers to await the active server's +`close()` before calling `createDevServer()` again. The plugin rejects an +overlapping or out-of-order replacement rather than closing one server from +inside another server's global startup-hook transaction. Callers must serialize +`createDevServer()` calls; concurrent server creation is outside this contract. +If startup fails before returning a server, or if closing the active server +rejects, restart the process before retrying unless the caller can externally +prove and force complete teardown. A fresh Rsbuild instance alone is not +sufficient because the prior compiler or watchers may still be active. + +## Deliberate limit + +This is an eagerly evaluated server-entry-set and manifest-pairing guarantee, not +byte-level output atomicity. Development outputs use stable paths and mutable +storage, so an old server-build object does not preserve older client assets or +server chunks that are imported lazily after entry evaluation. + +Rsbuild publishes compiler-derived WebSocket success before its supported +`onAfterDevCompile` plugin callback. The plugin therefore cannot promise that +browser success notification waits for framework publication. A supported +graph-settled, pre-success hook is required to close that gap. + +Strict old-or-new asset serving would additionally require immutable +generation filenames, staged output promotion, or request-pinned asset +snapshots with garbage collection. That is outside this contract. diff --git a/examples/client-only/package.json b/examples/client-only/package.json index c3e5acb..948fa29 100644 --- a/examples/client-only/package.json +++ b/examples/client-only/package.json @@ -20,8 +20,8 @@ "devDependencies": { "@playwright/test": "^1.58.0", "@react-router/dev": "^7.13.0", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "^2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "^2.1.0", "@types/node": "^25.0.10", "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", diff --git a/examples/cloudflare/package.json b/examples/cloudflare/package.json index 08d6de1..6877fa5 100644 --- a/examples/cloudflare/package.json +++ b/examples/cloudflare/package.json @@ -23,8 +23,8 @@ "@playwright/test": "^1.58.0", "@react-router/cloudflare": "^7.13.0", "@react-router/dev": "^7.13.0", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "^2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "^2.1.0", "@tailwindcss/postcss": "^4.1.18", "@types/node": "^25.0.10", "@types/react": "^19.2.10", diff --git a/examples/custom-node-server/app/load-context.d.ts b/examples/custom-node-server/app/load-context.d.ts new file mode 100644 index 0000000..5d62985 --- /dev/null +++ b/examples/custom-node-server/app/load-context.d.ts @@ -0,0 +1,7 @@ +import 'react-router'; + +declare module 'react-router' { + interface AppLoadContext { + VALUE_FROM_EXPRESS: string; + } +} diff --git a/examples/custom-node-server/app/routes/projects/edit.tsx b/examples/custom-node-server/app/routes/projects/edit.tsx index 2b2927b..dcbe0f2 100644 --- a/examples/custom-node-server/app/routes/projects/edit.tsx +++ b/examples/custom-node-server/app/routes/projects/edit.tsx @@ -1,9 +1,11 @@ import { Form, Link, useLoaderData, useNavigation } from 'react-router'; import type { Route } from './+types/edit'; +type LoaderData = Route.ComponentProps['loaderData']; + export function handle() { return { - breadcrumb: (data: Route.LoaderData) => `Edit ${data.project.name}`, + breadcrumb: (data: LoaderData) => `Edit ${data.project.name}`, }; } @@ -31,7 +33,7 @@ export async function action({ request, params }: Route.ActionArgs) { } export default function EditProject() { - const { project } = useLoaderData(); + const { project } = useLoaderData(); const navigation = useNavigation(); const isSubmitting = navigation.state === 'submitting'; diff --git a/examples/custom-node-server/app/routes/projects/index.tsx b/examples/custom-node-server/app/routes/projects/index.tsx index a5da7a3..1278ecc 100644 --- a/examples/custom-node-server/app/routes/projects/index.tsx +++ b/examples/custom-node-server/app/routes/projects/index.tsx @@ -1,6 +1,8 @@ import { Link, useLoaderData } from 'react-router'; import type { Route } from './+types/index'; +type LoaderData = Route.ComponentProps['loaderData']; + export function handle() { return { breadcrumb: () => 'All Projects', @@ -73,7 +75,7 @@ function StatCard({ } export default function ProjectsIndex() { - const { stats, recentActivity } = useLoaderData(); + const { stats, recentActivity } = useLoaderData(); return (
diff --git a/examples/custom-node-server/app/routes/projects/layout.tsx b/examples/custom-node-server/app/routes/projects/layout.tsx index 9fd9d5c..cd638ce 100644 --- a/examples/custom-node-server/app/routes/projects/layout.tsx +++ b/examples/custom-node-server/app/routes/projects/layout.tsx @@ -1,6 +1,8 @@ import { Link, NavLink, Outlet, useLoaderData } from 'react-router'; import type { Route } from './+types/layout'; +type LoaderData = Route.ComponentProps['loaderData']; + export function handle() { return { breadcrumb: () => 'Projects', @@ -20,7 +22,7 @@ export function loader() { } export default function ProjectsLayout() { - const { projects } = useLoaderData(); + const { projects } = useLoaderData(); return (
diff --git a/examples/custom-node-server/app/routes/projects/project.tsx b/examples/custom-node-server/app/routes/projects/project.tsx index d99dd02..5a30f57 100644 --- a/examples/custom-node-server/app/routes/projects/project.tsx +++ b/examples/custom-node-server/app/routes/projects/project.tsx @@ -1,9 +1,11 @@ -import { Link, useLoaderData, useParams } from 'react-router'; +import { Link, useLoaderData } from 'react-router'; import type { Route } from './+types/project'; +type LoaderData = Route.ComponentProps['loaderData']; + export function handle() { return { - breadcrumb: (data: Route.LoaderData) => data.project.name, + breadcrumb: (data: LoaderData) => data.project.name, }; } @@ -74,7 +76,7 @@ function Avatar({ name, initials }: { name: string; initials: string }) { } export default function Project() { - const { project } = useLoaderData(); + const { project } = useLoaderData(); return (
diff --git a/examples/custom-node-server/app/routes/projects/settings.tsx b/examples/custom-node-server/app/routes/projects/settings.tsx index 76e7318..6f9d124 100644 --- a/examples/custom-node-server/app/routes/projects/settings.tsx +++ b/examples/custom-node-server/app/routes/projects/settings.tsx @@ -1,9 +1,11 @@ import { Form, Link, useLoaderData, useNavigation } from 'react-router'; import type { Route } from './+types/settings'; +type LoaderData = Route.ComponentProps['loaderData']; + export function handle() { return { - breadcrumb: (data: Route.LoaderData) => `${data.project.name} Settings`, + breadcrumb: (data: LoaderData) => `${data.project.name} Settings`, }; } @@ -64,7 +66,7 @@ function SettingsSection({ } export default function ProjectSettings() { - const { project } = useLoaderData(); + const { project } = useLoaderData(); const navigation = useNavigation(); const isSubmitting = navigation.state === 'submitting'; diff --git a/examples/custom-node-server/package.json b/examples/custom-node-server/package.json index 7b5fcf4..76ba920 100644 --- a/examples/custom-node-server/package.json +++ b/examples/custom-node-server/package.json @@ -6,11 +6,14 @@ "type": "module", "main": "index.js", "scripts": { - "dev": "RSDOCTOR=false PORT=3003 NODE_OPTIONS=\"--experimental-vm-modules --experimental-global-webcrypto\" node server.js", + "dev": "NODE_ENV=development RSDOCTOR=false PORT=3003 NODE_OPTIONS=\"--experimental-vm-modules --experimental-global-webcrypto\" node server.js", "start": "NODE_ENV=production PORT=3003 node server.js", "build": "rsbuild build", - "typecheck": "react-router typegen && tsc", - "test:e2e": "playwright test" + "typecheck": "react-router typegen && tsc --noEmit", + "test:e2e": "RR_E2E_REUSE_EXISTING_SERVER=false playwright test && corepack pnpm run test:production", + "test:production": "corepack pnpm run test:production:module && corepack pnpm run test:production:commonjs", + "test:production:module": "RR_SERVER_OUTPUT=module corepack pnpm run build && node scripts/smoke-production.mjs", + "test:production:commonjs": "RR_SERVER_OUTPUT=commonjs corepack pnpm run build && node scripts/smoke-production.mjs" }, "keywords": [], "author": "", @@ -27,8 +30,8 @@ "devDependencies": { "@playwright/test": "^1.58.0", "@react-router/dev": "^7.13.0", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "^2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "^2.1.0", "@rsdoctor/rspack-plugin": "^1.5.13", "@tailwindcss/postcss": "^4.1.18", "@types/express": "^5.0.6", diff --git a/examples/custom-node-server/playwright.config.ts b/examples/custom-node-server/playwright.config.ts index fbeb1bd..b8ad8c0 100644 --- a/examples/custom-node-server/playwright.config.ts +++ b/examples/custom-node-server/playwright.config.ts @@ -42,9 +42,10 @@ export default defineConfig({ // Web server configuration - starts dev server for custom node server webServer: { - command: 'pnpm run dev', + command: 'corepack pnpm run dev', url: 'http://localhost:3003', - reuseExistingServer: !process.env.CI, + reuseExistingServer: + !process.env.CI && process.env.RR_E2E_REUSE_EXISTING_SERVER !== 'false', timeout: 120000, }, -}); \ No newline at end of file +}); diff --git a/examples/custom-node-server/rsbuild.config.ts b/examples/custom-node-server/rsbuild.config.ts index b0fd786..e277460 100644 --- a/examples/custom-node-server/rsbuild.config.ts +++ b/examples/custom-node-server/rsbuild.config.ts @@ -3,12 +3,15 @@ import { pluginReact } from '@rsbuild/plugin-react'; import { pluginReactRouter } from 'rsbuild-plugin-react-router'; export default defineConfig(() => { + const serverOutput = + process.env.RR_SERVER_OUTPUT === 'commonjs' ? 'commonjs' : 'module'; return { plugins: [ pluginReactRouter({ customServer: true, + serverOutput, }), - pluginReact() + pluginReact(), ], }; }); diff --git a/examples/custom-node-server/scripts/smoke-production.mjs b/examples/custom-node-server/scripts/smoke-production.mjs new file mode 100644 index 0000000..8de3cdf --- /dev/null +++ b/examples/custom-node-server/scripts/smoke-production.mjs @@ -0,0 +1,72 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import { createServer } from 'node:net'; +import { fileURLToPath } from 'node:url'; + +const root = fileURLToPath(new URL('..', import.meta.url)); +const probe = createServer(); +probe.listen(0, '127.0.0.1'); +await once(probe, 'listening'); +const address = probe.address(); +assert(address && typeof address !== 'string'); +const { port } = address; +await new Promise(resolve => probe.close(resolve)); + +const fetchWithTimeout = async url => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 2_000); + try { + return await fetch(url, { signal: controller.signal }); + } finally { + clearTimeout(timeout); + } +}; + +const child = spawn(process.execPath, ['server.js'], { + cwd: root, + env: { + ...process.env, + NODE_ENV: 'production', + PORT: String(port), + }, + stdio: ['ignore', 'pipe', 'pipe'], +}); +let output = ''; +child.stdout.on('data', chunk => { + output += chunk; +}); +child.stderr.on('data', chunk => { + output += chunk; +}); + +try { + const deadline = Date.now() + 30_000; + let response; + while (Date.now() < deadline) { + if (child.exitCode !== null) { + throw new Error(`Production server exited early.\n${output}`); + } + try { + response = await fetchWithTimeout(`http://127.0.0.1:${port}/`); + if (response.ok) { + break; + } + } catch { + // The server is still starting. + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + assert(response?.ok, `Production server did not become ready.\n${output}`); + assert.match(await response.text(), /React Router Demo/); + console.log('Production smoke request returned HTTP 200.'); +} finally { + child.kill('SIGTERM'); + await Promise.race([ + once(child, 'exit'), + new Promise(resolve => setTimeout(resolve, 5_000)), + ]); + if (child.exitCode === null) { + child.kill('SIGKILL'); + } +} diff --git a/examples/custom-node-server/server.js b/examples/custom-node-server/server.js index d500dff..a1b7c4b 100644 --- a/examples/custom-node-server/server.js +++ b/examples/custom-node-server/server.js @@ -1,4 +1,9 @@ import { createRsbuild, loadConfig } from '@rsbuild/core'; +import { createRequestHandler } from '@react-router/express'; +import { + loadReactRouterServerBuild, + resolveReactRouterServerBuild, +} from 'rsbuild-plugin-react-router'; import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; @@ -10,71 +15,49 @@ const app = express(); const isDev = process.env.NODE_ENV !== 'production'; async function startServer() { + /** @type {import('@rsbuild/core').RsbuildDevServer | undefined} */ + let devServer; + /** @type {import('react-router').ServerBuild | (() => Promise)} */ + let build; + if (isDev) { const config = await loadConfig(); const rsbuild = await createRsbuild({ rsbuildConfig: config.content, }); - const devServer = await rsbuild.createDevServer(); - app.use(devServer.middlewares); - - app.use(async (req, res, next) => { - try { - const tryLoadBundle = async (entryName) => { - try { - return await devServer.environments.node.loadBundle(entryName); - } catch (error) { - if (error instanceof Error && error.message.includes("Can't find entry")) { - return null; - } - throw error; - } - }; - const bundle = /** @type {import("./server/index.js")} */ ( - (await tryLoadBundle('static/js/app')) ?? (await tryLoadBundle('app')) - ); - await bundle.app(req, res, next); - } catch (e) { - next(e); - } - }); - - const port = Number.parseInt(process.env.PORT || '3000', 10); - const server = app.listen(port, () => { - console.log(`Development server is running on http://localhost:${port}`); - devServer.afterListen(); - }); - devServer.connectWebSocket({ server }); + const currentDevServer = await rsbuild.createDevServer(); + devServer = currentDevServer; + app.use(currentDevServer.middlewares); + build = () => loadReactRouterServerBuild(currentDevServer); } else { - // Production mode - - app.use(express.static(path.join(__dirname, 'build/client'), { - index: false - })); - - // Load the server bundle - const serverBundle = await import('./build/server/static/js/app.js'); - const serverApp = serverBundle.app ?? serverBundle.default?.app; - if (typeof serverApp !== 'function') { - throw new Error( - 'Invalid server bundle: expected an exported `app(req, res, next)` handler.' - ); - } - - // Mount the server app after static file handling - app.use(async (req, res, next) => { - try { - await serverApp(req, res, next); - } catch (e) { - next(e); - } - }); - - const port = Number.parseInt(process.env.PORT || '3000', 10); - app.listen(port, () => { - console.log(`Production server is running on http://localhost:${port}`); - }); + app.use( + express.static(path.join(__dirname, 'build/client'), { + index: false, + }) + ); + const productionBuildPath = './build/server/static/js/app.js'; + build = await resolveReactRouterServerBuild(import(productionBuildPath)); } + + app.use( + createRequestHandler({ + build, + mode: isDev ? 'development' : 'production', + getLoadContext() { + return { + VALUE_FROM_EXPRESS: 'Hello from Express', + }; + }, + }) + ); + + const port = Number.parseInt(process.env.PORT || '3000', 10); + const server = app.listen(port, () => { + const mode = isDev ? 'Development' : 'Production'; + console.log(`${mode} server is running on http://localhost:${port}`); + devServer?.afterListen(); + }); + devServer?.connectWebSocket({ server }); } startServer().catch(console.error); diff --git a/examples/custom-node-server/server/index.ts b/examples/custom-node-server/server/index.ts deleted file mode 100644 index 477feb4..0000000 --- a/examples/custom-node-server/server/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import 'react-router'; -import { createRequestHandler } from '@react-router/express'; - -declare module 'react-router' { - interface AppLoadContext { - VALUE_FROM_EXPRESS: string; - } -} - -export const app = createRequestHandler({ - // @ts-expect-error - virtual module provided by React Router at build time - build: () => import('virtual/react-router/server-build'), - getLoadContext() { - return { - VALUE_FROM_EXPRESS: 'Hello from Express', - }; - }, -}); diff --git a/examples/custom-node-server/tsconfig.json b/examples/custom-node-server/tsconfig.json index 0f91f7b..c50dc99 100644 --- a/examples/custom-node-server/tsconfig.json +++ b/examples/custom-node-server/tsconfig.json @@ -2,7 +2,7 @@ "include": [ "./.react-router/types/**/*", "./app/**/*", - "./server/**/*" + "./server.js" ], "exclude": ["./build"], "compilerOptions": { @@ -10,7 +10,7 @@ "strict": true, "checkJs": true, "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["@rsbuild/core/types"], + "types": ["@rsbuild/core/types", "node"], "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", diff --git a/examples/default-template/app/dev-routes.ts b/examples/default-template/app/dev-routes.ts new file mode 100644 index 0000000..42c5f72 --- /dev/null +++ b/examples/default-template/app/dev-routes.ts @@ -0,0 +1,5 @@ +import type { RouteConfig } from '@react-router/dev/routes'; + +// Kept separate so the dev-route-watch E2E covers route-config dependencies, +// not the direct reload-server watch on app/routes.ts. +export default [] satisfies RouteConfig; diff --git a/examples/default-template/app/routes.ts b/examples/default-template/app/routes.ts index 7ee17d8..463e452 100644 --- a/examples/default-template/app/routes.ts +++ b/examples/default-template/app/routes.ts @@ -5,6 +5,7 @@ import { prefix, route, } from '@react-router/dev/routes'; +import devRoutes from './dev-routes'; export default [ // Index route for the home page @@ -19,6 +20,8 @@ export default [ // Client loader/action example route('client-features', 'routes/client-features.tsx'), + ...devRoutes, + // Docs section with nested routes ...prefix('docs', [ layout('routes/docs/layout.tsx', [ diff --git a/examples/default-template/package.json b/examples/default-template/package.json index e83c36d..f66c857 100644 --- a/examples/default-template/package.json +++ b/examples/default-template/package.json @@ -8,7 +8,8 @@ "start:esm": "react-router-serve ./build/server/static/js/app.js", "start:cjs": "react-router-serve ./cjs-serve-patch.cjs", "typecheck": "react-router typegen && tsc", - "test:e2e": "playwright test" + "test:e2e": "playwright test", + "test:e2e:lazy": "cross-env RR_LAZY_COMPILATION=entries playwright test tests/e2e/lazy-compilation.test.ts" }, "dependencies": { "@react-router/express": "^7.13.0", @@ -22,9 +23,9 @@ "devDependencies": { "@playwright/test": "^1.58.0", "@react-router/dev": "^7.13.0", - "@rsbuild/core": "2.0.15", + "@rsbuild/core": "2.1.0", "@rsbuild/plugin-less": "^1.6.4", - "@rsbuild/plugin-react": "^2.0.1", + "@rsbuild/plugin-react": "^2.1.0", "@rsbuild/plugin-sass": "^1.5.3", "@tailwindcss/postcss": "^4.1.18", "@types/node": "^25.0.10", diff --git a/examples/default-template/playwright.config.ts b/examples/default-template/playwright.config.ts index 66c43d8..1029bdc 100644 --- a/examples/default-template/playwright.config.ts +++ b/examples/default-template/playwright.config.ts @@ -5,10 +5,12 @@ export default defineConfig({ // Maximum time one test can run for timeout: 30 * 1000, expect: { - timeout: 5000 + timeout: 5000, }, - // Run tests in files in parallel + // Keep this example serial because dev-route-watch mutates route config and + // restarts the shared dev server. fullyParallel: false, + workers: 1, // Fail the build on CI if you accidentally left test.only in the source code forbidOnly: !!process.env.CI, // Retry on CI only @@ -47,4 +49,4 @@ export default defineConfig({ reuseExistingServer: !process.env.CI, timeout: 120000, }, -}); \ No newline at end of file +}); diff --git a/examples/default-template/react-router.config.ts b/examples/default-template/react-router.config.ts index 9e79e74..1a463d0 100644 --- a/examples/default-template/react-router.config.ts +++ b/examples/default-template/react-router.config.ts @@ -4,7 +4,5 @@ export default { // Config options... // Server-side render by default, to enable SPA mode set this to `false` ssr: true, - future: { - v8_splitRouteModules: true, - }, + splitRouteModules: true, } satisfies Config; diff --git a/examples/default-template/rsbuild.config.ts b/examples/default-template/rsbuild.config.ts index 3f25416..71f3c70 100644 --- a/examples/default-template/rsbuild.config.ts +++ b/examples/default-template/rsbuild.config.ts @@ -13,7 +13,17 @@ declare module 'react-router' { } export default defineConfig(() => { + const lazyCompilation = + process.env.RR_LAZY_COMPILATION === 'entries' + ? { entries: true } + : undefined; + return { - plugins: [pluginReactRouter(), pluginReact(), pluginLess(), pluginSass()], + plugins: [ + pluginReactRouter({ lazyCompilation }), + pluginReact(), + pluginLess(), + pluginSass(), + ], }; }); diff --git a/examples/default-template/tests/e2e/dev-route-watch.test.ts b/examples/default-template/tests/e2e/dev-route-watch.test.ts new file mode 100644 index 0000000..56dceef --- /dev/null +++ b/examples/default-template/tests/e2e/dev-route-watch.test.ts @@ -0,0 +1,179 @@ +import { expect, test, type Page } from '@playwright/test'; +import { + existsSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const appDirectory = join(__dirname, '../../app'); +const restartMarkerPath = join( + __dirname, + '../../build/client/.react-router/route-watch' +); +const devRoutesConfigPath = join(appDirectory, 'dev-routes.ts'); +const addedRoutePath = join(appDirectory, 'routes/dev-added-route.tsx'); +const addedRouteUrl = '/dev-added-route'; +const addedRouteText = 'Route added while dev server is running'; +const editedAddedRouteText = 'Route edited without dev server restart'; +const emptyDevRoutesConfig = `import type { RouteConfig } from '@react-router/dev/routes'; + +// Kept separate so the dev-route-watch E2E covers route-config dependencies, +// not the direct reload-server watch on app/routes.ts. +export default [] satisfies RouteConfig; +`; +const populatedDevRoutesConfig = `import { route, type RouteConfig } from '@react-router/dev/routes'; + +export default [ + route('dev-added-route', 'routes/dev-added-route.tsx'), +] satisfies RouteConfig; +`; + +const removeAddedRouteConfig = (): boolean => { + const routesConfig = readFileSync(devRoutesConfigPath, 'utf8'); + if (routesConfig !== emptyDevRoutesConfig) { + writeFileSync(devRoutesConfigPath, emptyDevRoutesConfig); + return true; + } + return false; +}; + +const removeAddedRouteFile = (): boolean => { + if (existsSync(addedRoutePath)) { + rmSync(addedRoutePath, { force: true }); + return true; + } + return false; +}; + +const readRestartMarkerVersion = (): string | null => { + try { + if (!existsSync(restartMarkerPath)) { + return null; + } + const { mtimeNs } = statSync(restartMarkerPath, { bigint: true }); + return `${readFileSync(restartMarkerPath, 'utf8')}:${mtimeNs}`; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return null; + } + throw error; + } +}; + +const expectRestartMarkerStable = async ( + expectedMarker: string | null, + quietMs = 750 +) => { + const startedAt = Date.now(); + await expect + .poll( + () => { + const marker = readRestartMarkerVersion(); + if (marker !== expectedMarker) { + return `changed:${marker ?? 'missing'}`; + } + return Date.now() - startedAt >= quietMs ? 'stable' : 'waiting'; + }, + { intervals: [100], timeout: quietMs + 1000 } + ) + .toBe('stable'); +}; + +const waitForRouteText = async (page: Page, url: string, text: string) => { + await expect + .poll( + async () => { + try { + const response = await page.request.get(url, { + timeout: 2000, + }); + if (!response.ok()) { + return `status:${response.status()}`; + } + const body = await response.text(); + return body.includes(text) ? 'ready' : 'missing-text'; + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + }, + { timeout: 60000 } + ) + .toBe('ready'); +}; + +const waitForRouteToBeRemoved = async (page: Page, url: string) => { + await expect + .poll( + async () => { + try { + const response = await page.request.get(url, { timeout: 2000 }); + return response.status() === 404 ? 'removed' : response.status(); + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + }, + { timeout: 60000 } + ) + .toBe('removed'); +}; + +test.describe('dev route watch', () => { + test.setTimeout(90000); + + test.beforeEach(async ({ page }) => { + if (removeAddedRouteConfig()) { + await waitForRouteToBeRemoved(page, addedRouteUrl); + } + removeAddedRouteFile(); + }); + + test.afterEach(async ({ page }) => { + if (removeAddedRouteConfig()) { + await waitForRouteToBeRemoved(page, addedRouteUrl); + } + removeAddedRouteFile(); + }); + + test('serves a route added after the dev server starts without restarting on later edits', async ({ + page, + }) => { + await page.goto('/'); + await expect(page.locator('h1')).toContainText('Welcome to React Router'); + const restartMarkerBeforeAdd = readRestartMarkerVersion(); + + writeFileSync( + addedRoutePath, + `export default function DevAddedRoute() { + return

${addedRouteText}

; +} +` + ); + + writeFileSync(devRoutesConfigPath, populatedDevRoutesConfig); + + await waitForRouteText(page, addedRouteUrl, addedRouteText); + + await page.goto(addedRouteUrl); + await expect(page.locator('h1')).toHaveText(addedRouteText); + + await expect + .poll(readRestartMarkerVersion, { timeout: 10000 }) + .not.toBe(restartMarkerBeforeAdd); + const restartMarkerBefore = readRestartMarkerVersion(); + writeFileSync( + addedRoutePath, + `export default function DevAddedRoute() { + return

${editedAddedRouteText}

; +} +` + ); + + await waitForRouteText(page, addedRouteUrl, editedAddedRouteText); + await expectRestartMarkerStable(restartMarkerBefore); + }); +}); diff --git a/examples/default-template/tests/e2e/lazy-compilation.test.ts b/examples/default-template/tests/e2e/lazy-compilation.test.ts new file mode 100644 index 0000000..4d3c8fc --- /dev/null +++ b/examples/default-template/tests/e2e/lazy-compilation.test.ts @@ -0,0 +1,246 @@ +import { expect, test, type Page } from '@playwright/test'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const appDirectory = join(__dirname, '../../app'); +const aboutRoutePath = join(appDirectory, 'routes/about.tsx'); +const aboutCssPath = join(appDirectory, 'routes/about.css'); +const originalAboutRoute = readFileSync(aboutRoutePath, 'utf8'); +const originalAboutCss = readFileSync(aboutCssPath, 'utf8'); +const aboutRouteWithCssImport = `import './about.css'; + +export default function About() { + return ( +
+

About CSS HMR Probe

+
+ CSS HMR probe +
+
+ ); +} +`; +const aboutRouteWithoutCssImport = aboutRouteWithCssImport.replace( + "import './about.css';\n\n", + '' +); +const aboutCssProbe = `.css-hmr-probe { + color: rgb(255, 0, 0); +} +`; + +const writeFileIfChanged = (path: string, contents: string) => { + if (readFileSync(path, 'utf8') !== contents) { + writeFileSync(path, contents); + } +}; + +const restoreAboutRoute = () => { + writeFileIfChanged(aboutRoutePath, originalAboutRoute); + writeFileIfChanged(aboutCssPath, originalAboutCss); +}; + +const readProbeColor = async (page: Page) => { + try { + return await page.evaluate(() => { + const probe = document.querySelector('[data-testid="css-hmr-probe"]'); + return probe ? getComputedStyle(probe).color : 'missing'; + }); + } catch (cause) { + if ( + cause instanceof Error && + cause.message.includes('Execution context was destroyed') + ) { + return 'navigating'; + } + throw cause; + } +}; + +const readProbeState = async (page: Page) => { + try { + return await page.evaluate(() => { + const probe = document.querySelector('[data-testid="css-hmr-probe"]'); + const manifest = (window as any).__reactRouterManifest; + return { + color: probe ? getComputedStyle(probe).color : 'missing', + links: Array.from(document.querySelectorAll('link[rel="stylesheet"]')) + .map(link => (link as HTMLLinkElement).href) + .sort(), + manifestCss: manifest?.routes?.['routes/about']?.css ?? null, + manifestModule: manifest?.routes?.['routes/about']?.module ?? null, + manifestVersion: manifest?.version ?? null, + }; + }); + } catch (cause) { + if ( + cause instanceof Error && + cause.message.includes('Execution context was destroyed') + ) { + return { color: 'navigating' }; + } + throw cause; + } +}; + +test.describe('lazy compilation', () => { + test.setTimeout(90000); + + test.beforeEach(() => { + restoreAboutRoute(); + }); + + test.afterEach(() => { + restoreAboutRoute(); + }); + + test('hydrates with entries:true while manifest route modules stay synchronous', async ({ + page, + }) => { + const errors: string[] = []; + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()); + } + }); + page.on('pageerror', (error) => { + errors.push(error.message); + }); + + await page.goto('/'); + + await page.waitForFunction(() => { + return (window as any).__reactRouterRouteModules !== undefined; + }); + + const initialRouteModules = await page.evaluate(() => { + const modules = (window as any).__reactRouterRouteModules ?? {}; + return Object.fromEntries( + Object.entries(modules).map(([routeId, moduleValue]) => [ + routeId, + Object.keys(moduleValue as Record).sort(), + ]) + ); + }); + expect(initialRouteModules.root).toContain('default'); + expect(initialRouteModules['routes/home']).toContain('default'); + + const manifestRouteModules = await page.evaluate(() => { + const manifest = (window as any).__reactRouterManifest; + return Object.fromEntries( + Object.entries(manifest.routes).map(([routeId, route]) => [ + routeId, + (route as { module: string }).module, + ]) + ); + }); + const rootRouteAsset = await page.request.get(manifestRouteModules.root); + expect(await rootRouteAsset.text()).not.toContain( + 'lazy-compilation-proxy' + ); + + const documentRequests: string[] = []; + page.on('request', (request) => { + if ( + request.isNavigationRequest() && + request.frame() === page.mainFrame() + ) { + documentRequests.push(request.url()); + } + }); + + await page.locator('a[href="/about"]').first().click(); + + await expect(page).toHaveURL('/about'); + await expect( + page.locator('h1:has-text("About This Demo")') + ).toBeVisible(); + expect(documentRequests).toEqual([]); + expect(errors.join('\n')).not.toMatch(/hydration|Hydration|Component/); + }); + + test('full reloads when active lazy route CSS import is removed and re-added', async ({ + page, + }) => { + writeFileSync(aboutRoutePath, aboutRouteWithCssImport); + writeFileSync(aboutCssPath, aboutCssProbe); + + const documentRequests: string[] = []; + const stylesheetRequests: string[] = []; + const stylesheetResponses: string[] = []; + page.on('request', request => { + if ( + request.isNavigationRequest() && + request.frame() === page.mainFrame() + ) { + documentRequests.push(request.url()); + } + if ( + request.resourceType() === 'stylesheet' || + new URL(request.url()).pathname.endsWith('.css') + ) { + stylesheetRequests.push(request.url()); + } + }); + page.on('response', response => { + const url = response.url(); + if ( + response.request().resourceType() === 'stylesheet' || + new URL(url).pathname.endsWith('.css') + ) { + stylesheetResponses.push(`${response.status()} ${url}`); + } + }); + + await page.goto('/about'); + await expect( + page.getByRole('heading', { name: 'About CSS HMR Probe' }) + ).toBeVisible(); + await expect + .poll(() => readProbeColor(page), { timeout: 60000 }) + .toBe('rgb(255, 0, 0)'); + + const documentRequestsBeforeRemoval = documentRequests.length; + writeFileSync(aboutRoutePath, aboutRouteWithoutCssImport); + + await expect + .poll( + async () => { + const reloads = + documentRequests.length - documentRequestsBeforeRemoval; + const color = await readProbeColor(page); + return reloads > 0 && color !== 'rgb(255, 0, 0)' + ? 'cleared' + : `reloads:${reloads};color:${color}`; + }, + { timeout: 60000 } + ) + .toBe('cleared'); + + const stylesheetRequestsBeforeReAdd = stylesheetRequests.length; + const documentRequestsBeforeReAdd = documentRequests.length; + writeFileSync(aboutRoutePath, aboutRouteWithCssImport); + + await expect + .poll( + async () => { + const state = await readProbeState(page); + const reloads = documentRequests.length - documentRequestsBeforeReAdd; + return state.color === 'rgb(255, 0, 0)' && + stylesheetRequests.length > stylesheetRequestsBeforeReAdd && + reloads > 0 + ? 'loaded' + : JSON.stringify({ + ...state, + reloads, + stylesheetRequestCount: stylesheetRequests.length, + stylesheetResponses: stylesheetResponses.slice(-5), + }); + }, + { timeout: 60000 } + ) + .toBe('loaded'); + }); +}); diff --git a/examples/epic-stack/package.json b/examples/epic-stack/package.json index ae98ab1..91a86db 100644 --- a/examples/epic-stack/package.json +++ b/examples/epic-stack/package.json @@ -66,8 +66,8 @@ "@react-router/node": "^7.13.0", "@react-router/remix-routes-option-adapter": "7.13.0", "@remix-run/server-runtime": "2.17.4", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "2.1.0", "@sentry/node": "10.37.0", "@sentry/profiling-node": "10.37.0", "@sentry/react": "10.37.0", diff --git a/examples/federation/epic-stack-remote/package.json b/examples/federation/epic-stack-remote/package.json index 62f587b..c4bb8d6 100644 --- a/examples/federation/epic-stack-remote/package.json +++ b/examples/federation/epic-stack-remote/package.json @@ -70,8 +70,8 @@ "@react-router/node": "^7.13.0", "@react-router/remix-routes-option-adapter": "7.13.0", "@remix-run/server-runtime": "2.17.4", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "2.1.0", "@sentry/node": "10.37.0", "@sentry/profiling-node": "10.37.0", "@sentry/react": "10.37.0", diff --git a/examples/federation/epic-stack/package.json b/examples/federation/epic-stack/package.json index e237b13..817c90b 100644 --- a/examples/federation/epic-stack/package.json +++ b/examples/federation/epic-stack/package.json @@ -70,8 +70,8 @@ "@react-router/node": "^7.13.0", "@react-router/remix-routes-option-adapter": "7.13.0", "@remix-run/server-runtime": "2.17.4", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "2.1.0", "@sentry/node": "10.37.0", "@sentry/profiling-node": "10.37.0", "@sentry/react": "10.37.0", diff --git a/examples/prerender/package.json b/examples/prerender/package.json index 458967f..4f5a09a 100644 --- a/examples/prerender/package.json +++ b/examples/prerender/package.json @@ -21,8 +21,8 @@ "devDependencies": { "@playwright/test": "^1.58.0", "@react-router/dev": "^7.13.0", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "^2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "^2.1.0", "@tailwindcss/postcss": "^4.1.18", "@types/node": "^25.0.10", "@types/react": "^19.2.10", diff --git a/examples/spa-mode/package.json b/examples/spa-mode/package.json index b2bc006..b061a79 100644 --- a/examples/spa-mode/package.json +++ b/examples/spa-mode/package.json @@ -21,8 +21,8 @@ "devDependencies": { "@playwright/test": "^1.58.0", "@react-router/dev": "^7.13.0", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "^2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "^2.1.0", "@tailwindcss/postcss": "^4.1.18", "@types/node": "^25.0.10", "@types/react": "^19.2.10", diff --git a/package.json b/package.json index 4e8b647..3f54284 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "main": "./dist/index.cjs", "types": "./dist/index.d.ts", "files": [ - "dist" + "dist", + "src" ], "publint": { "ignoreMissingDts": true @@ -56,13 +57,14 @@ "bench:baseline": "node scripts/bench-builds.mjs --profile default --iterations 5 --warmup 1 --clean build --format both --out .benchmark/results/baseline", "bench:full": "node scripts/bench-builds.mjs --profile full --iterations 5 --warmup 1 --clean build --format both --out .benchmark/results/full", "bench:large": "node scripts/bench-builds.mjs --profile large --iterations 1 --warmup 0 --clean cold --format both --out .benchmark/results/large", - "e2e": "pnpm build && pnpm --filter './examples/{default-template,spa-mode,prerender,custom-node-server,cloudflare,client-only}' test:e2e", + "e2e": "pnpm build && pnpm test:package-interop && pnpm --filter './examples/{default-template,spa-mode,prerender,custom-node-server,cloudflare,client-only}' test:e2e", "dev": "rslib build --watch", "test": "rstest run", "test:watch": "rstest watch", "test:coverage": "rstest run --coverage", "test:core": "rstest run -c ./rstest.config.ts", "test:core:watch": "rstest watch -c ./rstest.config.ts", + "test:package-interop": "node scripts/test-package-interop.mjs", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", "changeset": "changeset", @@ -71,16 +73,9 @@ "release:local": "pnpm build && changeset version && changeset publish && git add . && git commit -m \"chore: version packages\" && git push && git push --tags" }, "dependencies": { - "@babel/core": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", "@react-router/node": "^7.13.0", "@remix-run/node-fetch-server": "^0.13.0", "@rspack/plugin-react-refresh": "^2.0.2", - "babel-dead-code-elimination": "^1.0.12", - "esbuild": "^0.27.2", "execa": "^9.6.1", "fs-extra": "11.3.3", "isbot": "5.1.34", @@ -88,22 +83,21 @@ "jsesc": "^3.1.0", "pathe": "^2.0.3", "react-refresh": "^0.18.0", - "rspack-plugin-virtual-module": "^1.0.1" + "yuku-analyzer": "0.5.39", + "yuku-codegen": "0.5.39", + "yuku-parser": "0.5.39" }, "devDependencies": { "@changesets/cli": "^2.29.8", "@react-router/dev": "^8.0.1", "@rsbuild/config": "workspace:*", - "@rsbuild/core": "2.0.15", - "@rsbuild/plugin-react": "2.0.1", + "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-react": "2.1.0", "@rslib/core": "^0.22.1", - "@rspack/core": "2.0.8", - "@swc/helpers": "^0.5.23", + "@rspack/core": "2.1.0", "@rstest/core": "^0.8.1", "@rstest/coverage-istanbul": "^0.2.0", - "@types/babel__core": "^7.20.5", - "@types/babel__generator": "^7.27.0", - "@types/babel__traverse": "^7.28.0", + "@swc/helpers": "^0.5.23", "@types/fs-extra": "11.0.4", "@types/jsesc": "^3.0.3", "@types/node": "^25.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 194a74b..d0eb21a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,36 +11,15 @@ importers: .: dependencies: - '@babel/core': - specifier: ^7.28.6 - version: 7.28.6 - '@babel/generator': - specifier: ^7.28.6 - version: 7.28.6 - '@babel/parser': - specifier: ^7.28.6 - version: 7.28.6 - '@babel/traverse': - specifier: ^7.28.6 - version: 7.28.6 - '@babel/types': - specifier: ^7.28.6 - version: 7.28.6 '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@remix-run/node-fetch-server': specifier: ^0.13.0 - version: 0.13.0 + version: 0.13.3 '@rspack/plugin-react-refresh': specifier: ^2.0.2 - version: 2.0.2(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(react-refresh@0.18.0) - babel-dead-code-elimination: - specifier: ^1.0.12 - version: 1.0.12 - esbuild: - specifier: ^0.27.2 - version: 0.27.2 + version: 2.0.2(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(react-refresh@0.18.0) execa: specifier: ^9.6.1 version: 9.6.1 @@ -52,7 +31,7 @@ importers: version: 5.1.34 jiti: specifier: ^2.6.1 - version: 2.6.1 + version: 2.7.0 jsesc: specifier: ^3.1.0 version: 3.1.0 @@ -62,49 +41,46 @@ importers: react-refresh: specifier: ^0.18.0 version: 0.18.0 - rspack-plugin-virtual-module: - specifier: ^1.0.1 - version: 1.0.1 + yuku-analyzer: + specifier: 0.5.39 + version: 0.5.39 + yuku-codegen: + specifier: 0.5.39 + version: 0.5.39 + yuku-parser: + specifier: 0.5.39 + version: 0.5.39 devDependencies: '@changesets/cli': specifier: ^2.29.8 - version: 2.29.8(@types/node@25.0.10) + version: 2.31.0(@types/node@25.0.10) '@react-router/dev': specifier: ^8.0.1 - version: 8.0.1(babel-plugin-macros@3.1.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0)) + version: 8.0.1(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/config': specifier: workspace:* version: link:config '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: 2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@rslib/core': specifier: ^0.22.1 version: 0.22.1(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)(typescript@5.9.3) '@rspack/core': - specifier: 2.0.8 - version: 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) '@rstest/core': specifier: ^0.8.1 - version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1)) + version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0)) '@rstest/coverage-istanbul': specifier: ^0.2.0 - version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1))) + version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0))) '@swc/helpers': specifier: ^0.5.23 version: 0.5.23 - '@types/babel__core': - specifier: ^7.20.5 - version: 7.20.5 - '@types/babel__generator': - specifier: ^7.27.0 - version: 7.27.0 - '@types/babel__traverse': - specifier: ^7.28.0 - version: 7.28.0 '@types/fs-extra': specifier: 11.0.4 version: 11.0.4 @@ -131,22 +107,22 @@ importers: version: 0.0.75 playwright: specifier: ^1.58.0 - version: 1.58.0 + version: 1.61.1 prettier: specifier: 3.8.1 version: 3.8.1 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react-router-dom: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -154,8 +130,8 @@ importers: config: devDependencies: '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rslib/core': specifier: 0.22.1 version: 0.22.1(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)(typescript@5.9.3) @@ -170,38 +146,38 @@ importers: dependencies: '@react-router/express': specifier: ^7.13.0 - version: 7.13.0(express@4.22.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@4.22.2)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/serve': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) isbot: specifier: ^5.1.34 version: 5.1.34 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) devDependencies: '@playwright/test': specifier: ^1.58.0 version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: ^2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: ^2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@types/node': specifier: ^25.0.10 version: 25.0.10 @@ -219,50 +195,50 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + version: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) examples/cloudflare: dependencies: '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/serve': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) isbot: specifier: ^5.1.34 version: 5.1.34 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) devDependencies: '@cloudflare/workers-types': specifier: ^4.20260127.0 - version: 4.20260127.0 + version: 4.20260628.1 '@playwright/test': specifier: ^1.58.0 version: 1.58.0 '@react-router/cloudflare': specifier: ^7.13.0 - version: 7.13.0(@cloudflare/workers-types@4.20260127.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(@cloudflare/workers-types@4.20260628.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: ^2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: ^2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@types/node': specifier: ^25.0.10 version: 25.0.10 @@ -283,16 +259,16 @@ importers: version: 5.9.3 wrangler: specifier: ^4.61.0 - version: 4.61.0(@cloudflare/workers-types@4.20260127.0) + version: 4.105.0(@cloudflare/workers-types@4.20260628.1) examples/custom-node-server: dependencies: '@react-router/express': specifier: ^7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) express: specifier: ^5.2.1 version: 5.2.1 @@ -301,32 +277,32 @@ importers: version: 5.1.34 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) devDependencies: '@playwright/test': specifier: ^1.58.0 version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.9.4)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: ^2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: ^2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@rsdoctor/rspack-plugin': specifier: ^1.5.13 - version: 1.5.13(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + version: 1.5.16(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@types/express': specifier: ^5.0.0 version: 5.0.6 @@ -353,47 +329,47 @@ importers: dependencies: '@react-router/express': specifier: ^7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/serve': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) isbot: specifier: ^5.1.34 version: 5.1.34 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) devDependencies: '@playwright/test': specifier: ^1.58.0 version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-less': specifier: ^1.6.4 - version: 1.6.4(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + version: 1.6.4(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) '@rsbuild/plugin-react': - specifier: ^2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: ^2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@rsbuild/plugin-sass': specifier: ^1.5.3 - version: 1.5.3(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)) + version: 1.5.3(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@types/node': specifier: ^25.0.10 version: 25.0.10 @@ -408,13 +384,13 @@ importers: version: 10.1.0 react-router-devtools: specifier: ^6.2.0 - version: 6.2.0(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 6.2.1(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.7(react@19.2.7))(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) rsbuild-plugin-react-router: specifier: workspace:* version: link:../.. string-replace-loader: specifier: ^3.3.0 - version: 3.3.0(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + version: 3.3.0(webpack@5.108.1(lightningcss@1.32.0)) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -426,16 +402,16 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + version: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) vite-tsconfig-paths: specifier: ^6.0.5 - version: 6.0.5(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 6.1.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) examples/epic-stack: dependencies: '@conform-to/react': specifier: 1.16.0 - version: 1.16.0(react@19.2.4) + version: 1.16.0(react@19.2.7) '@conform-to/zod': specifier: 1.16.0 version: 1.16.0(zod@3.25.76) @@ -459,7 +435,7 @@ importers: version: 0.9.1 '@nasa-gcn/remix-seo': specifier: 2.0.1 - version: 2.0.1(@remix-run/react@2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3)) + version: 2.0.1(@remix-run/react@2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3)) '@oslojs/crypto': specifier: 1.0.1 version: 1.0.1 @@ -474,46 +450,46 @@ importers: version: 6.0.0(prisma@6.0.0) '@prisma/instrumentation': specifier: 7.3.0 - version: 7.3.0(@opentelemetry/api@1.9.0) + version: 7.3.0(@opentelemetry/api@1.9.1) '@radix-ui/react-checkbox': specifier: 1.3.3 - version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-dropdown-menu': specifier: 2.1.16 - version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-label': specifier: 2.1.8 - version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': specifier: 1.2.4 - version: 1.2.4(@types/react@19.2.10)(react@19.2.4) + version: 1.2.4(@types/react@19.2.10)(react@19.2.7) '@radix-ui/react-toast': specifier: 1.2.15 - version: 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-tooltip': specifier: 1.2.8 - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@react-email/components': specifier: 1.0.6 - version: 1.0.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.0.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@react-router/express': specifier: 7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/remix-routes-option-adapter': specifier: 7.13.0 - version: 7.13.0(@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0))(typescript@5.9.3) + version: 7.13.0(@react-router/dev@7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)))(typescript@5.9.3) '@remix-run/server-runtime': specifier: 2.17.4 version: 2.17.4(typescript@5.9.3) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: 2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@sentry/node': specifier: 10.37.0 version: 10.37.0 @@ -522,7 +498,7 @@ importers: version: 10.37.0 '@sentry/react': specifier: 10.37.0 - version: 10.37.0(react@19.2.4) + version: 10.37.0(react@19.2.7) '@tusbar/cache-control': specifier: 2.0.0 version: 2.0.0 @@ -582,7 +558,7 @@ importers: version: 8.1.0 input-otp: specifier: 1.4.2 - version: 1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) intl-parse-accept-language: specifier: 1.0.0 version: 1.0.0 @@ -606,13 +582,13 @@ importers: version: 1.5.4 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) remix-auth: specifier: 4.2.0 version: 4.2.0 @@ -621,7 +597,7 @@ importers: version: 3.0.2(remix-auth@4.2.0) remix-utils: specifier: 9.0.0 - version: 9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(@standard-schema/spec@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) rsbuild-plugin-react-router: specifier: workspace:* version: link:../.. @@ -630,13 +606,13 @@ importers: version: 3.0.1 sonner: specifier: 2.0.7 - version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) source-map-support: specifier: 0.5.21 version: 0.5.21 spin-delay: specifier: 2.0.1 - version: 2.0.1(react@19.2.4) + version: 2.0.1(react@19.2.7) tailwind-merge: specifier: 3.4.0 version: 3.4.0 @@ -651,14 +627,14 @@ importers: version: 4.0.2(tailwindcss@4.1.18) vite-env-only: specifier: 3.0.3 - version: 3.0.3(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 3.0.3(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) zod: specifier: 3.25.76 version: 3.25.76 devDependencies: '@epic-web/config': specifier: 1.21.3 - version: 1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1)(typescript@5.9.3)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(prettier@3.8.1)(typescript@5.9.3) '@faker-js/faker': specifier: 10.2.0 version: 10.2.0 @@ -667,13 +643,13 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rstest/core': specifier: 0.8.1 - version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1)) + version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0)) '@rstest/coverage-istanbul': specifier: 0.2.0 - version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1))) + version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0))) '@sentry/vite-plugin': specifier: 4.8.0 version: 4.8.0(encoding@0.1.13) @@ -682,10 +658,10 @@ importers: version: 2.1.1(typescript@5.9.3) '@tailwindcss/nesting': specifier: 0.0.0-insiders.565cd3e - version: 0.0.0-insiders.565cd3e(postcss@8.5.15) + version: 0.0.0-insiders.565cd3e(postcss@8.5.16) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -694,7 +670,7 @@ importers: version: 6.9.1 '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@testing-library/user-event': specifier: 14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -745,10 +721,10 @@ importers: version: 0.5.10 '@vitejs/plugin-react': specifier: 5.1.2 - version: 5.1.2(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 5.1.2(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) autoprefixer: specifier: 10.4.23 - version: 10.4.23(postcss@8.5.15) + version: 10.4.23(postcss@8.5.16) enforce-unique: specifier: 1.3.0 version: 1.3.0 @@ -757,13 +733,13 @@ importers: version: 0.27.2 eslint: specifier: 9.39.2 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@2.7.0) fs-extra: specifier: 11.3.3 version: 11.3.3 jsdom: specifier: 27.4.0 - version: 27.4.0(@noble/hashes@2.0.1) + version: 27.4.0(@noble/hashes@2.2.0) msw: specifier: 2.12.7 version: 2.12.7(@types/node@25.0.10)(typescript@5.9.3) @@ -793,19 +769,19 @@ importers: version: 5.9.3 vite: specifier: 7.3.1 - version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + version: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) examples/federation: devDependencies: concurrently: specifier: ^9.2.1 - version: 9.2.1 + version: 9.2.3 examples/federation/epic-stack: dependencies: '@conform-to/react': specifier: 1.16.0 - version: 1.16.0(react@19.2.4) + version: 1.16.0(react@19.2.7) '@conform-to/zod': specifier: 1.16.0 version: 1.16.0(zod@3.25.76) @@ -832,16 +808,16 @@ importers: version: 0.6.1 '@module-federation/enhanced': specifier: 2.5.1 - version: 2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + version: 2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@module-federation/node': specifier: 2.7.44 - version: 2.7.44(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + version: 2.7.44(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@module-federation/rsbuild-plugin': specifier: 2.5.1 - version: 2.5.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + version: 2.5.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@nasa-gcn/remix-seo': specifier: 2.0.1 - version: 2.0.1(@remix-run/react@2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3)) + version: 2.0.1(@remix-run/react@2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3)) '@oslojs/crypto': specifier: 1.0.1 version: 1.0.1 @@ -856,46 +832,46 @@ importers: version: 6.0.0(prisma@6.0.0) '@prisma/instrumentation': specifier: ^6.0.0 - version: 6.19.2(@opentelemetry/api@1.9.0) + version: 6.19.3(@opentelemetry/api@1.9.1) '@radix-ui/react-checkbox': specifier: 1.3.3 - version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-dropdown-menu': specifier: 2.1.16 - version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-label': specifier: 2.1.8 - version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': specifier: 1.2.4 - version: 1.2.4(@types/react@19.2.10)(react@19.2.4) + version: 1.2.4(@types/react@19.2.10)(react@19.2.7) '@radix-ui/react-toast': specifier: 1.2.15 - version: 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-tooltip': specifier: 1.2.8 - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@react-email/components': specifier: 1.0.6 - version: 1.0.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.0.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@react-router/express': specifier: 7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/remix-routes-option-adapter': specifier: 7.13.0 - version: 7.13.0(@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0))(typescript@5.9.3) + version: 7.13.0(@react-router/dev@7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)))(typescript@5.9.3) '@remix-run/server-runtime': specifier: 2.17.4 version: 2.17.4(typescript@5.9.3) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: 2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@sentry/node': specifier: 10.37.0 version: 10.37.0 @@ -904,7 +880,7 @@ importers: version: 10.37.0 '@sentry/react': specifier: 10.37.0 - version: 10.37.0(react@19.2.4) + version: 10.37.0(react@19.2.7) '@tusbar/cache-control': specifier: 2.0.0 version: 2.0.0 @@ -964,7 +940,7 @@ importers: version: 8.1.0 input-otp: specifier: 1.4.2 - version: 1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) intl-parse-accept-language: specifier: 1.0.0 version: 1.0.0 @@ -988,13 +964,13 @@ importers: version: 1.5.4 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) remix-auth: specifier: 4.2.0 version: 4.2.0 @@ -1003,7 +979,7 @@ importers: version: 3.0.2(remix-auth@4.2.0) remix-utils: specifier: 9.0.0 - version: 9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(@standard-schema/spec@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) rsbuild-plugin-react-router: specifier: workspace:* version: link:../../.. @@ -1012,13 +988,13 @@ importers: version: 3.0.1 sonner: specifier: 2.0.7 - version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) source-map-support: specifier: 0.5.21 version: 0.5.21 spin-delay: specifier: 2.0.1 - version: 2.0.1(react@19.2.4) + version: 2.0.1(react@19.2.7) tailwind-merge: specifier: 3.4.0 version: 3.4.0 @@ -1037,7 +1013,7 @@ importers: devDependencies: '@epic-web/config': specifier: 1.21.3 - version: 1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1)(typescript@5.9.3)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(prettier@3.8.1)(typescript@5.9.3) '@faker-js/faker': specifier: 10.2.0 version: 10.2.0 @@ -1046,22 +1022,22 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rstest/core': specifier: 0.8.1 - version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1)) + version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0)) '@rstest/coverage-istanbul': specifier: 0.2.0 - version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1))) + version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0))) '@sly-cli/sly': specifier: 2.1.1 version: 2.1.1(typescript@5.9.3) '@tailwindcss/nesting': specifier: 0.0.0-insiders.565cd3e - version: 0.0.0-insiders.565cd3e(postcss@8.5.15) + version: 0.0.0-insiders.565cd3e(postcss@8.5.16) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -1070,7 +1046,7 @@ importers: version: 6.9.1 '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@testing-library/user-event': specifier: 14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -1121,7 +1097,7 @@ importers: version: 0.5.10 autoprefixer: specifier: 10.4.23 - version: 10.4.23(postcss@8.5.15) + version: 10.4.23(postcss@8.5.16) enforce-unique: specifier: 1.3.0 version: 1.3.0 @@ -1130,13 +1106,13 @@ importers: version: 0.27.2 eslint: specifier: 9.39.2 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@2.7.0) fs-extra: specifier: 11.3.3 version: 11.3.3 jsdom: specifier: 27.4.0 - version: 27.4.0(@noble/hashes@2.0.1) + version: 27.4.0(@noble/hashes@2.2.0) msw: specifier: 2.12.7 version: 2.12.7(@types/node@25.0.10)(typescript@5.9.3) @@ -1169,7 +1145,7 @@ importers: dependencies: '@conform-to/react': specifier: 1.16.0 - version: 1.16.0(react@19.2.4) + version: 1.16.0(react@19.2.7) '@conform-to/zod': specifier: 1.16.0 version: 1.16.0(zod@3.25.76) @@ -1196,16 +1172,16 @@ importers: version: 0.6.1 '@module-federation/enhanced': specifier: 2.5.1 - version: 2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + version: 2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@module-federation/node': specifier: 2.7.44 - version: 2.7.44(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + version: 2.7.44(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@module-federation/rsbuild-plugin': specifier: 2.5.1 - version: 2.5.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + version: 2.5.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@nasa-gcn/remix-seo': specifier: 2.0.1 - version: 2.0.1(@remix-run/react@2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3)) + version: 2.0.1(@remix-run/react@2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3)) '@oslojs/crypto': specifier: 1.0.1 version: 1.0.1 @@ -1220,46 +1196,46 @@ importers: version: 6.0.0(prisma@6.0.0) '@prisma/instrumentation': specifier: ^6.0.0 - version: 6.19.2(@opentelemetry/api@1.9.0) + version: 6.19.3(@opentelemetry/api@1.9.1) '@radix-ui/react-checkbox': specifier: 1.3.3 - version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-dropdown-menu': specifier: 2.1.16 - version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-label': specifier: 2.1.8 - version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': specifier: 1.2.4 - version: 1.2.4(@types/react@19.2.10)(react@19.2.4) + version: 1.2.4(@types/react@19.2.10)(react@19.2.7) '@radix-ui/react-toast': specifier: 1.2.15 - version: 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-tooltip': specifier: 1.2.8 - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@react-email/components': specifier: 1.0.6 - version: 1.0.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.0.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@react-router/express': specifier: 7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/remix-routes-option-adapter': specifier: 7.13.0 - version: 7.13.0(@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0))(typescript@5.9.3) + version: 7.13.0(@react-router/dev@7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)))(typescript@5.9.3) '@remix-run/server-runtime': specifier: 2.17.4 version: 2.17.4(typescript@5.9.3) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: 2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: 2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@sentry/node': specifier: 10.37.0 version: 10.37.0 @@ -1268,7 +1244,7 @@ importers: version: 10.37.0 '@sentry/react': specifier: 10.37.0 - version: 10.37.0(react@19.2.4) + version: 10.37.0(react@19.2.7) '@tusbar/cache-control': specifier: 2.0.0 version: 2.0.0 @@ -1328,7 +1304,7 @@ importers: version: 8.1.0 input-otp: specifier: 1.4.2 - version: 1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) intl-parse-accept-language: specifier: 1.0.0 version: 1.0.0 @@ -1352,13 +1328,13 @@ importers: version: 1.5.4 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) remix-auth: specifier: 4.2.0 version: 4.2.0 @@ -1367,7 +1343,7 @@ importers: version: 3.0.2(remix-auth@4.2.0) remix-utils: specifier: 9.0.0 - version: 9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(@standard-schema/spec@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + version: 9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) rsbuild-plugin-react-router: specifier: workspace:* version: link:../../.. @@ -1376,13 +1352,13 @@ importers: version: 3.0.1 sonner: specifier: 2.0.7 - version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) source-map-support: specifier: 0.5.21 version: 0.5.21 spin-delay: specifier: 2.0.1 - version: 2.0.1(react@19.2.4) + version: 2.0.1(react@19.2.7) tailwind-merge: specifier: 3.4.0 version: 3.4.0 @@ -1401,7 +1377,7 @@ importers: devDependencies: '@epic-web/config': specifier: 1.21.3 - version: 1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1)(typescript@5.9.3)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(prettier@3.8.1)(typescript@5.9.3) '@faker-js/faker': specifier: 10.2.0 version: 10.2.0 @@ -1410,22 +1386,22 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rstest/core': specifier: 0.8.1 - version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1)) + version: 0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0)) '@rstest/coverage-istanbul': specifier: 0.2.0 - version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1))) + version: 0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0))) '@sly-cli/sly': specifier: 2.1.1 version: 2.1.1(typescript@5.9.3) '@tailwindcss/nesting': specifier: 0.0.0-insiders.565cd3e - version: 0.0.0-insiders.565cd3e(postcss@8.5.15) + version: 0.0.0-insiders.565cd3e(postcss@8.5.16) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -1434,7 +1410,7 @@ importers: version: 6.9.1 '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@testing-library/user-event': specifier: 14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) @@ -1485,7 +1461,7 @@ importers: version: 0.5.10 autoprefixer: specifier: 10.4.23 - version: 10.4.23(postcss@8.5.15) + version: 10.4.23(postcss@8.5.16) enforce-unique: specifier: 1.3.0 version: 1.3.0 @@ -1494,13 +1470,13 @@ importers: version: 0.27.2 eslint: specifier: 9.39.2 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@2.7.0) fs-extra: specifier: 11.3.3 version: 11.3.3 jsdom: specifier: 27.4.0 - version: 27.4.0(@noble/hashes@2.0.1) + version: 27.4.0(@noble/hashes@2.2.0) msw: specifier: 2.12.7 version: 2.12.7(@types/node@25.0.10)(typescript@5.9.3) @@ -1533,41 +1509,41 @@ importers: dependencies: '@react-router/express': specifier: ^7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/serve': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) isbot: specifier: ^5.1.34 version: 5.1.34 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) devDependencies: '@playwright/test': specifier: ^1.58.0 version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: ^2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: ^2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@types/node': specifier: ^25.0.10 version: 25.0.10 @@ -1582,16 +1558,16 @@ importers: version: 10.1.0 react-router-devtools: specifier: ^6.2.0 - version: 6.2.0(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 6.2.1(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.7(react@19.2.7))(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) rsbuild-plugin-react-router: specifier: workspace:* version: link:../.. serve: specifier: ^14.2.4 - version: 14.2.5 + version: 14.2.6 string-replace-loader: specifier: ^3.3.0 - version: 3.3.0(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + version: 3.3.0(webpack@5.108.1(lightningcss@1.32.0)) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -1603,50 +1579,50 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + version: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) vite-tsconfig-paths: specifier: ^6.0.5 - version: 6.0.5(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 6.1.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) examples/spa-mode: dependencies: '@react-router/express': specifier: ^7.13.0 - version: 7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/node': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@react-router/serve': specifier: ^7.13.0 - version: 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + version: 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) isbot: specifier: ^5.1.34 version: 5.1.34 react: specifier: ^19.2.4 - version: 19.2.4 + version: 19.2.7 react-dom: specifier: ^19.2.4 - version: 19.2.4(react@19.2.4) + version: 19.2.7(react@19.2.7) react-router: specifier: ^7.13.0 - version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) devDependencies: '@playwright/test': specifier: ^1.58.0 version: 1.58.0 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + version: 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) '@rsbuild/core': - specifier: 2.0.15 - version: 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + specifier: 2.1.0 + version: 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) '@rsbuild/plugin-react': - specifier: ^2.0.1 - version: 2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) + specifier: ^2.1.0 + version: 2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)) '@tailwindcss/postcss': specifier: ^4.1.18 - version: 4.1.18 + version: 4.3.1 '@types/node': specifier: ^25.0.10 version: 25.0.10 @@ -1661,16 +1637,16 @@ importers: version: 10.1.0 react-router-devtools: specifier: ^6.2.0 - version: 6.2.0(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 6.2.1(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.7(react@19.2.7))(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) rsbuild-plugin-react-router: specifier: workspace:* version: link:../.. serve: specifier: ^14.2.4 - version: 14.2.5 + version: 14.2.6 string-replace-loader: specifier: ^3.3.0 - version: 3.3.0(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + version: 3.3.0(webpack@5.108.1(lightningcss@1.32.0)) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -1682,18 +1658,18 @@ importers: version: 5.9.3 vite: specifier: ^7.3.1 - version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + version: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) vite-tsconfig-paths: specifier: ^6.0.5 - version: 6.0.5(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) + version: 6.1.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) packages: '@acemir/cssom@0.9.31': resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - '@adobe/css-tools@4.4.4': - resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} @@ -1705,11 +1681,11 @@ packages: '@apm-js-collab/tracing-hooks@0.3.1': resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} - '@asamuzakjp/css-color@4.1.1': - resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==} + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} - '@asamuzakjp/dom-selector@6.7.6': - resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==} + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -1776,230 +1752,119 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/code-frame@7.28.6': - resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.6': - resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.7': resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.6': - resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} - engines: {node: '>=6.9.0'} - '@babel/core@7.29.7': resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.6': - resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.29.7': resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.29.7': resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-create-class-features-plugin@7.29.7': resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.29.7': resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.28.5': - resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} - engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.29.7': resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.29.7': resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.29.7': resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} - '@babel/helper-optimise-call-expression@7.29.7': resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.29.7': resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} engines: {node: '>=6.9.0'} - '@babel/helper-replace-supers@7.28.6': - resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.29.7': resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} - engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.29.7': resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.29.7': resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.6': - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.7': resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.29.7': resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.29.7': resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.28.6': - resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.29.7': resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.6': - resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2010,52 +1875,30 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.28.5': - resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.29.7': resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.6': - resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.6': - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@biomejs/cli-darwin-arm64@2.3.13': - resolution: {integrity: sha512-0OCwP0/BoKzyJHnFdaTk/i7hIP9JHH9oJJq6hrSCPmJPo8JWcJhprK4gQlhFzrwdTBAW4Bjt/RmCf3ZZe59gwQ==} + '@biomejs/cli-darwin-arm64@2.5.1': + resolution: {integrity: sha512-npqDzvqv7vFaWRiNN1Te71siRgPaqS9MpqgYCdP/CrUbkJ7ApezaeaKjueKHRN/JH/6lRjJQAHi8acQDCAz22w==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] @@ -2066,33 +1909,33 @@ packages: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@bufbuild/protobuf@2.11.0': - resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} + '@bufbuild/protobuf@2.12.1': + resolution: {integrity: sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg==} - '@changesets/apply-release-plan@7.0.14': - resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} + '@changesets/apply-release-plan@7.1.1': + resolution: {integrity: sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==} - '@changesets/assemble-release-plan@6.0.9': - resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + '@changesets/assemble-release-plan@6.0.10': + resolution: {integrity: sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==} '@changesets/changelog-git@0.2.1': resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@changesets/cli@2.29.8': - resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==} + '@changesets/cli@2.31.0': + resolution: {integrity: sha512-AhI4enNTgHu2IZr6K4WZyf0EPch4XVMn1yOMFmCD9gsfBGqMYaHXls5HyDv6/CL5axVQABz68eG30eCtbr2wFg==} hasBin: true - '@changesets/config@3.1.2': - resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==} + '@changesets/config@3.1.4': + resolution: {integrity: sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@changesets/get-dependents-graph@2.1.3': - resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + '@changesets/get-dependents-graph@2.1.4': + resolution: {integrity: sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==} - '@changesets/get-release-plan@4.0.14': - resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==} + '@changesets/get-release-plan@4.0.16': + resolution: {integrity: sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} @@ -2103,14 +1946,14 @@ packages: '@changesets/logger@0.1.1': resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} - '@changesets/parse@0.4.2': - resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} + '@changesets/parse@0.4.3': + resolution: {integrity: sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==} '@changesets/pre@2.0.2': resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} - '@changesets/read@0.6.6': - resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} + '@changesets/read@0.6.7': + resolution: {integrity: sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==} '@changesets/should-skip-package@0.1.2': resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} @@ -2124,51 +1967,51 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@cloudflare/kv-asset-handler@0.4.2': - resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} - engines: {node: '>=18.0.0'} + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} - '@cloudflare/unenv-preset@2.11.0': - resolution: {integrity: sha512-z3hxFajL765VniNPGV0JRStZolNz63gU3B3AktwoGdDlnQvz5nP+Ah4RL04PONlZQjwmDdGHowEStJ94+RsaJg==} + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} peerDependencies: unenv: 2.0.0-rc.24 - workerd: ^1.20260115.0 + workerd: '>1.20260305.0 <2.0.0-0' peerDependenciesMeta: workerd: optional: true - '@cloudflare/workerd-darwin-64@1.20260124.0': - resolution: {integrity: sha512-VuqscLhiiVIf7t/dcfkjtT0LKJH+a06KUFwFTHgdTcqyLbFZ44u1SLpOONu5fyva4A9MdaKh9a+Z/tBC1d76nw==} + '@cloudflare/workerd-darwin-64@1.20260625.1': + resolution: {integrity: sha512-naCfBv0WnnTQIQPTniqMoUlklOIFjrAcSn1X+IAOhY8aFLF/xGYtFjs1eEE8sFib3ZuChGGpU23FFORVczqr0A==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260124.0': - resolution: {integrity: sha512-PfnjoFooPgRKFUIZcEP9irnn5Y7OgXinjM+IMlKTdEyLWjMblLsbsqAgydf75+ii0715xAeUlWQjZrWdyOZjMw==} + '@cloudflare/workerd-darwin-arm64@1.20260625.1': + resolution: {integrity: sha512-jmH6zjp6Wrux46+qtFwDwrj+vd7s5bdwEqeGvdnwE0a4IEeAhKs0L42HQOyID+g5lkrHq9m55+AbhtmRAm63Pw==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260124.0': - resolution: {integrity: sha512-KSkZl4kwcWeFXI7qsaLlMnKwjgdZwI0OEARjyZpiHCxJCqAqla9XxQKNDscL2Z3qUflIo30i+uteGbFrhzuVGQ==} + '@cloudflare/workerd-linux-64@1.20260625.1': + resolution: {integrity: sha512-MiQkpA/dX8d83Zp64pzHUKfd6ca4cvwxnNobSP6CnXvfESvnNI9pfa+nfwnParla36sPmnYntNkjR7NjRuDeKQ==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260124.0': - resolution: {integrity: sha512-61xjSUNk745EVV4vXZP0KGyLCatcmamfBB+dcdQ8kDr6PrNU4IJ1kuQFSJdjybyDhJRm4TpGVywq+9hREuF7xA==} + '@cloudflare/workerd-linux-arm64@1.20260625.1': + resolution: {integrity: sha512-LxxW7Qv60Xvv37+w6gUSDpYZziyqMy+cZWd9IvSA5ehVgKAxmzEaYPMiSZlxk32nbIWL9u/tfjXYCOKJ4Lo+XQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260124.0': - resolution: {integrity: sha512-j9O11pwQQV6Vi3peNrJoyIas3SrZHlPj0Ah+z1hDW9o1v35euVBQJw/PuzjPOXxTFUlGQoMJdfzPsO9xP86g7A==} + '@cloudflare/workerd-windows-64@1.20260625.1': + resolution: {integrity: sha512-LH6iIX1HHaTwVKV5VokDxxUErXJzQoNZFRwVm7Vx/3fB/ApcTcRCUaMqcxI4as94jEUqg+pmX5czOndiveohow==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workers-types@4.20260127.0': - resolution: {integrity: sha512-4M1HLcWViSdT/pAeDGEB5x5P3sqW7UIi34QrBRnxXbqjAY9if8vBU/lWRWnM+UqKzxWGB2LYjEVOzZrp0jZL+w==} + '@cloudflare/workers-types@4.20260628.1': + resolution: {integrity: sha512-fMy5zBnNl/PxGqDzSOQb1TdqyR1sRT1Z7T4F8/cqtxpZ1w8VkejWi0qUH7GZE0I2gJyapdP0oW4pNZfxUbekmw==} '@conform-to/dom@1.16.0': resolution: {integrity: sha512-KkURoALYztq5kli8/Ojqe4PyTcAmc9pHOmIoU5RJzre8poWgmjCsHQt28xhEyDk5XBwo8bkvyKm8oKqwSAAucw==} @@ -2187,55 +2030,60 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.26': - resolution: {integrity: sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==} + '@csstools/css-syntax-patches-for-csstree@1.1.6': + resolution: {integrity: sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/core@1.8.1': - resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@epic-web/cachified@5.6.1': resolution: {integrity: sha512-+VKwMhqM43l2s+gX28Telcf6bUJk1Zaj0Ix2i8K4R2QW8WgPE0q3THCnr0xZg5chw35/B4SkHS43an2fqKOFnQ==} @@ -2255,23 +2103,17 @@ packages: resolution: {integrity: sha512-n4LJ9lJ5GjIEzba72RPaATqtWLrxm8cP7yRKbQvf/D8NHFSMF77hwXKwssFnsB7o9gEQLE4cYQpzqjlmsNMEVg==} engines: {node: '>=20'} - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [android] + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.27.2': resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} @@ -2279,10 +2121,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} - cpu: [arm] + cpu: [arm64] os: [android] '@esbuild/android-arm@0.27.2': @@ -2291,10 +2133,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm] os: [android] '@esbuild/android-x64@0.27.2': @@ -2303,11 +2145,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.27.2': resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} @@ -2315,10 +2157,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.27.2': @@ -2327,11 +2169,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.27.2': resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} @@ -2339,10 +2181,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.27.2': @@ -2351,11 +2193,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [linux] + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.27.2': resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} @@ -2363,10 +2205,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} - cpu: [arm] + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.27.2': @@ -2375,10 +2217,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.27.2': @@ -2387,10 +2229,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} - cpu: [loong64] + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.27.2': @@ -2399,10 +2241,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} - cpu: [mips64el] + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.27.2': @@ -2411,10 +2253,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} - cpu: [ppc64] + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.27.2': @@ -2423,10 +2265,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} - cpu: [riscv64] + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.27.2': @@ -2435,10 +2277,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} - cpu: [s390x] + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.27.2': @@ -2447,10 +2289,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} - cpu: [x64] + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.27.2': @@ -2459,11 +2301,11 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] + cpu: [x64] + os: [linux] '@esbuild/netbsd-arm64@0.27.2': resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} @@ -2471,10 +2313,10 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.27.2': @@ -2483,11 +2325,11 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] + cpu: [x64] + os: [netbsd] '@esbuild/openbsd-arm64@0.27.2': resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} @@ -2495,10 +2337,10 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.27.2': @@ -2507,11 +2349,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [openbsd] '@esbuild/openharmony-arm64@0.27.2': resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} @@ -2519,11 +2361,11 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} - cpu: [x64] - os: [sunos] + cpu: [arm64] + os: [openharmony] '@esbuild/sunos-x64@0.27.2': resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} @@ -2531,11 +2373,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [win32] + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.27.2': resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} @@ -2543,10 +2385,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.27.2': @@ -2555,10 +2397,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} - cpu: [x64] + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.27.2': @@ -2567,6 +2409,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2577,8 +2425,8 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/config-helpers@0.4.2': @@ -2589,8 +2437,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.3': - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.2': @@ -2605,8 +2453,8 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@exodus/bytes@1.10.0': - resolution: {integrity: sha512-tf8YdcbirXdPnJ+Nd4UN1EXnz+IP2DI45YVEr3vvzcVTOyrApkmIB4zvOQVd3XPr7RXnfBtAx+PXImXOIU0Ajg==} + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: '@noble/hashes': ^1.8.0 || ^2.0.0 @@ -2618,27 +2466,31 @@ packages: resolution: {integrity: sha512-rTXwAsIxpCqzUnZvrxVh3L0QA0NzToqWBLAhV+zDV3MIIwiQhAZHMdPCIaj5n/yADu/tyk12wIPgL6YHGXJP+g==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} - '@floating-ui/core@1.7.4': - resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - '@floating-ui/dom@1.7.5': - resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - '@floating-ui/react-dom@2.1.7': - resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -2649,8 +2501,8 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} '@img/sharp-darwin-arm64@0.34.5': @@ -2830,14 +2682,6 @@ packages: '@types/node': optional: true - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3008,9 +2852,6 @@ packages: resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} engines: {node: '>=18'} - '@napi-rs/wasm-runtime@0.2.12': - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} @@ -3026,14 +2867,20 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nasa-gcn/remix-seo@2.0.1': resolution: {integrity: sha512-g9biDdYfsdFBnOU7lM+7vPGEXSEMRnWmfVLDQ98pT0PnTT/O3pFuA+s3DA0Mj9IwnAq9IcLs2Wee/aL6fvEA+A==} peerDependencies: '@remix-run/react': ^1.0.0 || ^2.0.0 '@remix-run/server-runtime': ^1.0.0 || ^2.0.0 - '@noble/hashes@2.0.1': - resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + '@noble/hashes@2.2.0': + resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} engines: {node: '>= 20.19.0'} '@nodelib/fs.scandir@2.1.5': @@ -3065,12 +2912,16 @@ packages: resolution: {integrity: sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg==} engines: {node: '>=8.0.0'} - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + '@opentelemetry/api-logs@0.219.0': + resolution: {integrity: sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==} engines: {node: '>=8.0.0'} - '@opentelemetry/context-async-hooks@2.5.0': - resolution: {integrity: sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==} + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.8.0': + resolution: {integrity: sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -3081,6 +2932,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.8.0': + resolution: {integrity: sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/instrumentation-amqplib@0.58.0': resolution: {integrity: sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3225,24 +3082,30 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/redis-common@0.38.2': - resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + '@opentelemetry/instrumentation@0.219.0': + resolution: {integrity: sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/redis-common@0.38.3': + resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/resources@2.5.0': - resolution: {integrity: sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==} + '@opentelemetry/resources@2.8.0': + resolution: {integrity: sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.5.0': - resolution: {integrity: sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==} + '@opentelemetry/sdk-trace-base@2.8.0': + resolution: {integrity: sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.39.0': - resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} '@opentelemetry/sql-common@0.41.2': @@ -3397,8 +3260,8 @@ packages: '@prisma/get-platform@6.0.0': resolution: {integrity: sha512-PS6nYyIm9g8C03E4y7LknOfdCw/t2KyEJxntMPQHQZCOUgOpF82Ma60mdlOD08w90I3fjLiZZ0+MadenR3naDQ==} - '@prisma/instrumentation@6.19.2': - resolution: {integrity: sha512-5VXvzh/qOh7uBnVIx26IQJDBIXgpBhyh1+TPHDmw3pv/rue9x7NVAvip+bznUIYUREPPPTGWjabANUacRFttrA==} + '@prisma/instrumentation@6.19.3': + resolution: {integrity: sha512-gwU1D4pxrtBZXINZwSseEal+h8OghvIVZIH3VRv6+vPFAkZk1ykFUVcOhhboBc5WmhoD42NTJjla2i7GFNcZWA==} peerDependencies: '@opentelemetry/api': ^1.8 @@ -3415,8 +3278,11 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@radix-ui/react-accordion@1.2.12': - resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + '@radix-ui/primitive@1.1.4': + resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} + + '@radix-ui/react-accordion@1.2.14': + resolution: {integrity: sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3454,8 +3320,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.12': - resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + '@radix-ui/react-collapsible@1.1.14': + resolution: {integrity: sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.10': + resolution: {integrity: sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3489,6 +3368,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: @@ -3498,6 +3386,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.4': + resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: @@ -3507,6 +3404,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.2': + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.1.11': resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: @@ -3564,6 +3470,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-label@2.1.8': resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} peerDependencies: @@ -3629,8 +3544,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + '@radix-ui/react-presence@1.1.6': + resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3642,8 +3557,34 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.4': - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.6': + resolution: {integrity: sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3686,6 +3627,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-toast@1.2.15': resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} peerDependencies: @@ -3730,6 +3680,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-effect-event@0.0.2': resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: @@ -3739,6 +3698,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-escape-keydown@1.1.1': resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: @@ -3757,6 +3725,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-previous@1.1.1': resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: @@ -3979,28 +3956,28 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc - '@react-router/cloudflare@7.13.0': - resolution: {integrity: sha512-8K7gftcmFYe2kjx6RBWgJxqBgO9JplKZH3NzVSZsHXw/MwLVeHQWwIZJqm/mixEYLCV3DJVl2qfAGV4gIinFdA==} + '@react-router/cloudflare@7.18.0': + resolution: {integrity: sha512-im4t/oeTyT/oIoU/YoNtn4g9XA3NjeZoBM1wQafZPhXmCu6LfPF5TFaPwenj3U+70cqeuwXeVY/NfBG6yxodmQ==} engines: {node: '>=20.0.0'} peerDependencies: '@cloudflare/workers-types': ^4.0.0 - react-router: ^7.13.0 - typescript: ^5.1.0 + react-router: ^7.18.0 + typescript: ^5.1.0 || ^6.0.0 peerDependenciesMeta: typescript: optional: true - '@react-router/dev@7.13.0': - resolution: {integrity: sha512-0vRfTrS6wIXr9j0STu614Cv2ytMr21evnv1r+DXPv5cJ4q0V2x2kBAXC8TAqEXkpN5vdhbXBlbGQ821zwOfhvg==} + '@react-router/dev@7.18.0': + resolution: {integrity: sha512-GVTFvul0xlZHZyVXyRpiJv54Xfyj4eDOAlGYrzi7kDmN7n40rsrUqX+hvU0fy/41SCDMtckht59R3iGR94703g==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.13.0 - '@vitejs/plugin-rsc': ~0.5.7 - react-router: ^7.13.0 + '@react-router/serve': ^7.18.0 + '@vitejs/plugin-rsc': ~0.5.21 + react-router: ^7.18.0 react-server-dom-webpack: ^19.2.3 - typescript: ^5.1.0 - vite: ^5.1.0 || ^6.0.0 || ^7.0.0 + typescript: ^5.1.0 || ^6.0.0 + vite: ^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 wrangler: ^3.28.2 || ^4.0.0 peerDependenciesMeta: '@react-router/serve': @@ -4049,6 +4026,17 @@ packages: typescript: optional: true + '@react-router/express@7.18.0': + resolution: {integrity: sha512-7+6oZJYdJ7ha7ASV0XkYZkmnTUYtRkzjzjpZh3W9tPdaUY1zJoDFiMlUJZoAg0mHw8BNTAwVWCCf8NFV1xlLZA==} + engines: {node: '>=20.0.0'} + peerDependencies: + express: ^4.17.1 || ^5 + react-router: 7.18.0 + typescript: ^5.1.0 || ^6.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@react-router/node@7.13.0': resolution: {integrity: sha512-Mhr3fAou19oc/S93tKMIBHwCPfqLpWyWM/m0NWd3pJh/wZin8/9KhAdjwxhYbXw1TrTBZBLDENa35uZ+Y7oh3A==} engines: {node: '>=20.0.0'} @@ -4059,6 +4047,16 @@ packages: typescript: optional: true + '@react-router/node@7.18.0': + resolution: {integrity: sha512-pRXJahLrdVfuVbaTpWsZ89mBuGiYH3Z4y+y1UidwxmJFKk6NjMyUvkJl3FjDWdD+nSlgFPSESUZS0hF560MUUQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react-router: 7.18.0 + typescript: ^5.1.0 || ^6.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@react-router/node@8.0.1': resolution: {integrity: sha512-XUtOdjgOtFXe4XxkO28km51l++AYL7A3mk4Sozm7hr3ROY/9qE+9EoPHw0gEv4FQEgY7a/6XnzDL5dB+zNt7GA==} engines: {node: '>=22.22.0'} @@ -4079,21 +4077,18 @@ packages: typescript: optional: true - '@react-router/serve@7.13.0': - resolution: {integrity: sha512-bgpA3YdUvuSAQa0vRM9xeZaBsglgUvxsVCUqdJpxF87ZF9pT5uoAITrWYd1soDB8jSksnH3btJEXHasvG7cikA==} + '@react-router/serve@7.18.0': + resolution: {integrity: sha512-IrF0cLcJNGBBavnRBm3HxaEGwRrLrLF8E4EzQFuCpkgP1sRli1x2xEOOTJl4zBgUbyIn0ey4TAD6ytg45MAUBQ==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - react-router: 7.13.0 - - '@remix-run/node-fetch-server@0.13.0': - resolution: {integrity: sha512-1EsNo0ZpgXu/90AWoRZf/oE3RVTUS80tiTUpt+hv5pjtAkw7icN4WskDwz/KdAw5ARbJLMhZBrO1NqThmy/McA==} + react-router: 7.18.0 '@remix-run/node-fetch-server@0.13.3': resolution: {integrity: sha512-UfjOXed/DQteaM5VyTfqTeGpHwyL2J5aoRGY6cydip4tt1ehNNeSwuXCC7AEGE0RWBs/7bgKxYkL/B/+UDe4AA==} - '@remix-run/react@2.15.3': - resolution: {integrity: sha512-AynCltIk8KLlxV9a+4dORtEMNtF5wJAzBNBZLJMdw3FCJNQZRYQSen8rDnIovOOiz9UNZ2SmBTFERiFMKS16jw==} + '@remix-run/react@2.17.5': + resolution: {integrity: sha512-ya6OQ+T9yS4u8www75f4dH11NIYeK8K0eMrei+q4BTp6NrY2lqLTBfHqVlc23ZsUnWqdLUII4hzySVv+AJbTWA==} engines: {node: '>=18.0.0'} peerDependencies: react: ^18.0.0 @@ -4103,16 +4098,16 @@ packages: typescript: optional: true - '@remix-run/router@1.22.0': - resolution: {integrity: sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==} - engines: {node: '>=14.0.0'} - '@remix-run/router@1.23.2': resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} engines: {node: '>=14.0.0'} - '@remix-run/server-runtime@2.15.3': - resolution: {integrity: sha512-taHBe1DEqxZNjjj6OfkSYbup+sZPjbTgUhykaI+nHqrC2NDQuTiisBXhLwtx60GctONR/x0lWhF7R9ZGC5WsHw==} + '@remix-run/router@1.23.3': + resolution: {integrity: sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==} + engines: {node: '>=14.0.0'} + + '@remix-run/server-runtime@2.17.4': + resolution: {integrity: sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w==} engines: {node: '>=18.0.0'} peerDependencies: typescript: ^5.1.0 @@ -4120,8 +4115,8 @@ packages: typescript: optional: true - '@remix-run/server-runtime@2.17.4': - resolution: {integrity: sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w==} + '@remix-run/server-runtime@2.17.5': + resolution: {integrity: sha512-SyJ5n2pQyo4ERGvjjLvL2PpRWBzlC2qzO5KkkOE2ygOiNcgOu9WQnnom9GI8KgsTRCO8JnIeLHFRcmxZnVBjfw==} engines: {node: '>=18.0.0'} peerDependencies: typescript: ^5.1.0 @@ -4132,128 +4127,128 @@ packages: '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} - '@rollup/rollup-android-arm-eabi@4.57.0': - resolution: {integrity: sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==} + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.57.0': - resolution: {integrity: sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==} + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.57.0': - resolution: {integrity: sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==} + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.57.0': - resolution: {integrity: sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==} + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.57.0': - resolution: {integrity: sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==} + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.57.0': - resolution: {integrity: sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==} + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.57.0': - resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.57.0': - resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==} + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.57.0': - resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==} + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.57.0': - resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==} + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.57.0': - resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==} + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loong64-musl@4.57.0': - resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==} + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.57.0': - resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==} + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.57.0': - resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==} + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.57.0': - resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==} + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.57.0': - resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==} + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.57.0': - resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==} + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.57.0': - resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==} + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.57.0': - resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==} + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.57.0': - resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==} + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.57.0': - resolution: {integrity: sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==} + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.57.0': - resolution: {integrity: sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==} + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.57.0': - resolution: {integrity: sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==} + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.57.0': - resolution: {integrity: sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==} + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.57.0': - resolution: {integrity: sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==} + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} cpu: [x64] os: [win32] @@ -4272,6 +4267,16 @@ packages: core-js: optional: true + '@rsbuild/core@2.1.0': + resolution: {integrity: sha512-BNOp22xLGA+L8zvZ12Qg/3zhFhWFZCvZ7OcQRoGLSTw1pB9q/XDLK+WGey9SFPOtsRD3CRPXheXApxwvneH6uA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + core-js: '>= 3.0.0' + peerDependenciesMeta: + core-js: + optional: true + '@rsbuild/plugin-check-syntax@1.6.1': resolution: {integrity: sha512-26xtEYN0QjZYoyt0lWnvIztBWjEZJvcfw7MN4f5B4SpNggmnF7F7aNPrgkY3EccXVFx1VGQBhnCkBV//OoS07Q==} peerDependencies: @@ -4288,10 +4293,10 @@ packages: '@rsbuild/core': optional: true - '@rsbuild/plugin-react@2.0.1': - resolution: {integrity: sha512-n5m3VxEm6m3Dv1VkI0WnxsildySJ6M+QjGIzkZDy5UebRCIJ1Q/hlQVyhofBL6C+AcsF9fGjlHQkeiteXJSr3Q==} + '@rsbuild/plugin-react@2.1.0': + resolution: {integrity: sha512-RQTIAWB/CwPjoWt9iAl+8HixeQVgZ7kEIBrWPCixfITyHdiD84h0YpUTpEUuz6kGHw1KXT9mHZ3Rwy6WG7aRDA==} peerDependencies: - '@rsbuild/core': ^2.0.0-0 + '@rsbuild/core': ^2.0.0 peerDependenciesMeta: '@rsbuild/core': optional: true @@ -4304,28 +4309,28 @@ packages: '@rsbuild/core': optional: true - '@rsdoctor/client@1.5.13': - resolution: {integrity: sha512-pGc+Y6irGROjPBOAt7fMAiZwwtzS7dS5txcrioP8Q5MvJPGtcHxQ56IiUPl5i5faHBo0RSCwBESnslQPwjRGcg==} + '@rsdoctor/client@1.5.16': + resolution: {integrity: sha512-SskR2c06w+QTCrrhlN5wIjpVzJgqscQlMlYT3e/rSc2lH09lA1VBkKlgh2jy0OkE7FTZIO+9n6hdXM4qCAFOOA==} - '@rsdoctor/core@1.5.13': - resolution: {integrity: sha512-ZeJMUidUYAxA+Y48A+aAEyo0Pe1aIezv452y/z+eweI0V/MF4/uMcNTxaHuWYItKzP0XNr0/QStJTsfLSORzxQ==} + '@rsdoctor/core@1.5.16': + resolution: {integrity: sha512-Sj9bJO3yRYvTn111pNFzADLrE9WJ8ZgOJertzeST4ueOSw4yeUkM9oMgj21p7NWYsM44C2vDuBIcerAHuxIkBA==} - '@rsdoctor/graph@1.5.13': - resolution: {integrity: sha512-sImaFcLTg27m7PO5WOaH3ati3Vw64fvszBANae3ONXqb6F9EqpyLzyXTw7djqyvfVZsNObw+9LLmKJNevEmuyg==} + '@rsdoctor/graph@1.5.16': + resolution: {integrity: sha512-Uzq5n5Za30z2CW6V9j6Bl4x8Bbek1YHJyhKEXyF1P3UoQIQ2qpdYu1B//krlDV2YmwB7ENwqaV5D0D+LoB690g==} - '@rsdoctor/rspack-plugin@1.5.13': - resolution: {integrity: sha512-eYSrfW+8+A5BXynHryWizbwvTKXpItpANi3Mf8+XhCaxdi6zVDaND9RWt9vj4IBBaTMNH9M7j2TCsiP9TfcLCg==} + '@rsdoctor/rspack-plugin@1.5.16': + resolution: {integrity: sha512-CoFLPpJF+XU96sVZ8EWbbQh4ZjihmZAv3hlyQzrn+HO8VBa0BRI/k+oyRAPwg7fI0/Uc1OHDtgL7D8IST3vbTw==} peerDependencies: '@rspack/core': '*' peerDependenciesMeta: '@rspack/core': optional: true - '@rsdoctor/sdk@1.5.13': - resolution: {integrity: sha512-KCcKSR9B6cKUj3J0+EqzYf4up34WldD2MP2iUfUzGR+9OI/OSGzuirFMlGzV9cSFHWj0iJ5ydGSrXFvWDwmLkg==} + '@rsdoctor/sdk@1.5.16': + resolution: {integrity: sha512-t2ghEUCUKFwP12QeeLX3VvI5b+Z+SI9rwUiE8VHa+gOFi8oHkceEi0rlBf1k9BexMpQf3zP3RNTZtzo09mxpCw==} - '@rsdoctor/types@1.5.13': - resolution: {integrity: sha512-XBrxFQ45eB3QsF1B2P3AMowUis2cbDmFLOu8brtDovGTYypEiUlMAmVsdIhXKJrljkv47+j7GgTmUSUBarLM1g==} + '@rsdoctor/types@1.5.16': + resolution: {integrity: sha512-J9yqQu1VNFgLJpkgzAucjA1AkVnrg6kRyMhAzj05uYyPM6FQgFmi3NlQFDxRPLAfTuwSXC72xhSi7LYd6MuVvA==} peerDependencies: '@rspack/core': '*' webpack: 5.x @@ -4335,8 +4340,8 @@ packages: webpack: optional: true - '@rsdoctor/utils@1.5.13': - resolution: {integrity: sha512-Z3O+92uRl6XrYJBDt2s0YAgkDszgjut85+kmM5wx+Z10Ha2o3ZmGp3FVLYBPz7TbKsay5RYxb2OLuJ39lrUQKg==} + '@rsdoctor/utils@1.5.16': + resolution: {integrity: sha512-aFhv9gzDjJ5qyODO1Uno1NAfl/7WDhmJASJ5lQj1Qo8FV1VgtnYKFbbYdQYSpsowhxgklNISxVs905HdiBp6yg==} '@rslib/core@0.22.1': resolution: {integrity: sha512-RaqTITHFkpMDJG9fmD7Hu6FLE64hwctCo46asHOD2DipzQJWawg6K0pFGimTAyutYEZysIUfYgCwSYkbctDudg==} @@ -4351,8 +4356,8 @@ packages: typescript: optional: true - '@rspack/binding-darwin-arm64@1.7.4': - resolution: {integrity: sha512-d4FTW/TkqvU9R1PsaK2tbLG1uY0gAlxy3rEiQYrFRAOVTMOFkPasypmvhwD5iWrPIhkjIi79IkgrSzRJaP2ZwA==} + '@rspack/binding-darwin-arm64@1.7.12': + resolution: {integrity: sha512-rbFprJaJiqrmfy8SHth8EsoRS0wg4bXcucwj9NiMzpGFq14Opw8c04iQ6H9BECYzgmN0PKZ9rh41LdVvhdZe4A==} cpu: [arm64] os: [darwin] @@ -4361,8 +4366,13 @@ packages: cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-x64@1.7.4': - resolution: {integrity: sha512-Oq65S5szs3+In9hVWfPksdL6EUu1+SFZK3oQINP3kMJ5zPzrdyiue+L5ClpTU/VMKVxfQTdCBsI6OVJNnaLBiA==} + '@rspack/binding-darwin-arm64@2.1.0': + resolution: {integrity: sha512-1DdnXLCl4/7BydtxvFyJbqOyvo3dgeKIdukr5BrM7UUA5rJnpin0qZIq/C0Y+ZwTx7ML4zdYaJeR+WOujRQH1Q==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.7.12': + resolution: {integrity: sha512-jnOp+/UXOJa9xqUb8KXH03sysoO2e4Ij6tw6MqDdmdj8n/A8PQENRPUbW9AwXpPtVDJPus9r4fi7b3+6e4B8Hg==} cpu: [x64] os: [darwin] @@ -4371,8 +4381,13 @@ packages: cpu: [x64] os: [darwin] - '@rspack/binding-linux-arm64-gnu@1.7.4': - resolution: {integrity: sha512-sTpfCraAtYZBhdw9Xx5a19OgJ/mBELTi61utZzrO3bV6BFEulvOdmnNjpgb0xv1KATtNI8YxECohUzekk1WsOA==} + '@rspack/binding-darwin-x64@2.1.0': + resolution: {integrity: sha512-PJB6n/BaupvfLaErsfvC7q9W07WozkPe2Xw7sQqX6fblK+4tooBp0ZdAtKi76L+U2fR8t8/nQb0Jokco0co7Fg==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.7.12': + resolution: {integrity: sha512-C8owWG+yvo7X0oVLIXetkoJhIFBP1LYNcAQqtgLmJnQLQDklGuP83dKC+zISGQWpjawHfZ1ER96vLgoTrxKZdw==} cpu: [arm64] os: [linux] @@ -4381,8 +4396,13 @@ packages: cpu: [arm64] os: [linux] - '@rspack/binding-linux-arm64-musl@1.7.4': - resolution: {integrity: sha512-sw8jZbUe13Ry0/tnUt1pSdwkaPtSzKuveq+b6/CUT26I3DKfJQoG0uJbjj2quMe4ks3jDmoGlxuRe4D/fWUoSg==} + '@rspack/binding-linux-arm64-gnu@2.1.0': + resolution: {integrity: sha512-TCmWIeI03ZZi8GjpIS2yl9JpaazsaA4F84zbX6a4kdZnFkrmFKRdvczZrquTNQvmggAEaJiPxkSrS8OC1LSAwA==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.7.12': + resolution: {integrity: sha512-i51WWI64aRpsfSki6rN0aepPqXkVfS+vZM7+4bWDcmnhUmdMvhIPcYg0QRk3DtyJnu33jqNLM0WHY78k00NyfA==} cpu: [arm64] os: [linux] @@ -4391,8 +4411,23 @@ packages: cpu: [arm64] os: [linux] - '@rspack/binding-linux-x64-gnu@1.7.4': - resolution: {integrity: sha512-1W6LU0wR/TxB+8pogt0pn0WRwbQmKfu9839p/VBuSkNdWR4aljAhYO6RxsLQLCLrDAqEyrpeYWsWJBvAJ4T/pA==} + '@rspack/binding-linux-arm64-musl@2.1.0': + resolution: {integrity: sha512-HJzw5gG62qjj9fRQgj948naLucwE1Vg1bfcYHAxOr1/bVVIm4I4QvWGuqvd3XOu0MfLXPWvEyMAvJL+rtgamsw==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-riscv64-gnu@2.1.0': + resolution: {integrity: sha512-B3ENZHIBi5u1Apt6RJ62QSCabCijI5l86Sm2AEDYpQnqqBj3vIc+Br9HJHvNjK8PNWs1WfmD//UTUmQqZbYpKQ==} + cpu: [riscv64] + os: [linux] + + '@rspack/binding-linux-riscv64-musl@2.1.0': + resolution: {integrity: sha512-Qho1S8bW2BKRsJjl/f39GoyPRznF8ZarIgxZdVCIkn4k+3veggKWxqR1WWKoMj/LfykQd1uG3FF6n7zy5IfxWw==} + cpu: [riscv64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.7.12': + resolution: {integrity: sha512-MSos0FuPEefqo9V92ULd5hggKG29EkSNg1zDcypy0OkpsKh5pfjVxTLYFXgTcVyFoUQQbdG8zFBzYbwmJ8V4ew==} cpu: [x64] os: [linux] @@ -4401,8 +4436,13 @@ packages: cpu: [x64] os: [linux] - '@rspack/binding-linux-x64-musl@1.7.4': - resolution: {integrity: sha512-rkmu8qLnm/q8J14ZQZ04SnPNzdRNgzAoKJCTbnhCzcuL5k5e20LUFfGuS6j7Io1/UdVMOjz/u7R6b9h/qA1Scw==} + '@rspack/binding-linux-x64-gnu@2.1.0': + resolution: {integrity: sha512-oE2CMALLdV3QNiA3YYDZ46tDGf+WRlqu/tQ+B79JYKVwt3sI0fpzvgwPNpx/gfRKUyA0phaeYS4kyOEnpltjpA==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.7.12': + resolution: {integrity: sha512-JcAMVKXOnjfpC3coWjCFPWD3Yl8RBw6a+IXQQ8mfRlHaHMIiOv8IfZqx15XRxMUn49CtP7Z0Na8iiAg2aKrcfw==} cpu: [x64] os: [linux] @@ -4411,16 +4451,25 @@ packages: cpu: [x64] os: [linux] - '@rspack/binding-wasm32-wasi@1.7.4': - resolution: {integrity: sha512-6BQvLbDtUVkTN5o1QYLYKAYuXavC4ER5Vn/amJEoecbM9F25MNAv28inrXs7BQ4cHSU4WW/F4yZPGnA+jUZLyw==} + '@rspack/binding-linux-x64-musl@2.1.0': + resolution: {integrity: sha512-lxTFZgsfPPyyIt/DpOH5TK2u1ZROMB+gLp/LWvYBc8FSOtmR0Gl4L/AWmJdM2yqwPfy0hgSkVicf/7k80jHuVQ==} + cpu: [x64] + os: [linux] + + '@rspack/binding-wasm32-wasi@1.7.12': + resolution: {integrity: sha512-n+ZqP6ZMc0nhOgvadg5VhEs9ojtbES80AcWeFnmGkbzIszvGSO63GKNiRkXtjJ9KFuRzytbbmsCqkUVH+Tywxg==} cpu: [wasm32] '@rspack/binding-wasm32-wasi@2.0.8': resolution: {integrity: sha512-Yf4SiqTUroT5Ju+te0YAY2xxKOb35tECsO21v7hYyGa705wrgoAK/MmF7enOvs9GR1iZIqgiLD/wxsIxl8GjJw==} cpu: [wasm32] - '@rspack/binding-win32-arm64-msvc@1.7.4': - resolution: {integrity: sha512-kipggu7xVPhnAkAV7koSDVbBuuMDMA4hX60DNJKTS6fId3XNHcZqWKIsWGOt0yQ6KV7I3JRRBDotKLx6uYaRWw==} + '@rspack/binding-wasm32-wasi@2.1.0': + resolution: {integrity: sha512-ZsDDduXaEF1SpyGz2OFuEU9Tzm0pKtbtCYviymiNtQS+3lx6rXyv+FaK0oIWn+gWL+gVNamplxKnNNR2jZsp5w==} + cpu: [wasm32] + + '@rspack/binding-win32-arm64-msvc@1.7.12': + resolution: {integrity: sha512-8+h5fYDXYdmugbdfZ+D1y8IQ3rv2EhSfyGP7vBe+bjNyaMa4jWrpucmZbtxojUL1AzaeuHbvMdj9UO/gelk/+g==} cpu: [arm64] os: [win32] @@ -4429,8 +4478,13 @@ packages: cpu: [arm64] os: [win32] - '@rspack/binding-win32-ia32-msvc@1.7.4': - resolution: {integrity: sha512-9Zdozc13AUQHqagDDHxHml1FnZZWuSj/uP+SxtlTlQaiIE9GDH3n0cUio1GUq+cBKbcXeiE3dJMGJxhiFaUsxA==} + '@rspack/binding-win32-arm64-msvc@2.1.0': + resolution: {integrity: sha512-0uMWAZYgwdkk0ocE4X85w/0BNWT5GaKJTEZDVYxfSYcfVAxJvIsuM0VH/cjRjsQtEeE1rcY7JvJyd0rQ6j0DqA==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.7.12': + resolution: {integrity: sha512-cDMGwTRSa2p9fNBVe1wTRkF2AEXZ9ARWW36QeC5CkLaI0Ezz8lvhF2+CSOPnhaQ1O1qtn0L0SF+lFnrY+I7xGQ==} cpu: [ia32] os: [win32] @@ -4439,8 +4493,13 @@ packages: cpu: [ia32] os: [win32] - '@rspack/binding-win32-x64-msvc@1.7.4': - resolution: {integrity: sha512-3a/jZTUrvU340IuRcxul+ccsDtdrMaGq/vi4HNcWalL0H2xeOeuieBAV8AZqaRjmxMu8OyRcpcSrkHtN1ol/eA==} + '@rspack/binding-win32-ia32-msvc@2.1.0': + resolution: {integrity: sha512-MRuIZwF6w1tGyZgoJZ5dnpLaD/oMx8zAYSYQfNS7l0f7qjxbnp42625wkeNB8kPqrmDfqaWUWLwiOaRqFPmumA==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.7.12': + resolution: {integrity: sha512-wIqFvlgFqrgUyj/6S/FJcvShnkZOmIeXTfqvheLY67MGq8qd8jb1YimQVKAIrmWB3yuJKUFACI3Ag1UBtEedEA==} cpu: [x64] os: [win32] @@ -4449,14 +4508,22 @@ packages: cpu: [x64] os: [win32] - '@rspack/binding@1.7.4': - resolution: {integrity: sha512-BOACDXd9aTrdJgqa88KGxnTGdUdVLAClTCLhSvdNvQZIcaVLOB1qtW0TvqjZ19MxuQB/Cba5u/ILc5DNXxuDhg==} + '@rspack/binding-win32-x64-msvc@2.1.0': + resolution: {integrity: sha512-Fme2Ifa647CtD7N6we9xvK+COzfzVJREtUayxdG+VArPdijURZyRQVUKKlYSBW+2qMg+G+kF1xo7gf7svG3sNA==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.7.12': + resolution: {integrity: sha512-f4HHuLbvuld8Ba4iB/4ibse5XrKxFrgmM3S4P2AOKnPlekAFlBjmltCuaTL/W2ggYvILaVY+YcFXrEH1rrKeQA==} '@rspack/binding@2.0.8': resolution: {integrity: sha512-3uZ+y8aQxq33ty2srMxg2Nu0XuBI6vVrG50rkDaXqwWqOohfgGUSfFuQK7EnSUNy4aFUQlCG6NHialQHJov0wg==} - '@rspack/core@1.7.4': - resolution: {integrity: sha512-6QNqcsRSy1WbAGvjA2DAEx4yyAzwrvT6vd24Kv4xdZHdvF6FmcUbr5J+mLJ1jSOXvpNhZ+RzN37JQ8fSmytEtw==} + '@rspack/binding@2.1.0': + resolution: {integrity: sha512-LsXFIOOYDutHk44SAOcVQa5iA7lhYwEbD+nZhgmCiGJvKKh0UIpBj6EAsBsB6omEK5GEXvjDeLFieKgbYW08QQ==} + + '@rspack/core@1.7.12': + resolution: {integrity: sha512-6CwFIHlhRmXfZoMj3v9MZ1SMTPBn+cHVXeMIeaGp5sufqinKsISbsqHu6ZMJu2wDSmZLdmQJX6zLxkhcAUlhkQ==} engines: {node: '>=18.12.0'} peerDependencies: '@swc/helpers': '>=0.5.1' @@ -4476,18 +4543,24 @@ packages: '@swc/helpers': optional: true - '@rspack/lite-tapable@1.1.0': - resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} - - '@rspack/plugin-react-refresh@2.0.0': - resolution: {integrity: sha512-Cf6CxBStNDJbiXMc/GmsvG1G8PRlUpa0MSfWsMTI+e8npzuTN/p8nwLs3shriBZOLciqgkSZpBtPTd10BLpj1g==} + '@rspack/core@2.1.0': + resolution: {integrity: sha512-dlZRzWQi90HzLYErGh0/xnEWAEMEAtDKXvNxERCEj5uIVIOVu9+uYwpNyAkKc9cK5sPhOz05kk9MIb1EaUJ5gg==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - '@rspack/core': ^2.0.0-0 - react-refresh: '>=0.10.0 <1.0.0' + '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 + '@swc/helpers': ^0.5.23 peerDependenciesMeta: - '@rspack/core': + '@module-federation/runtime-tools': + optional: true + '@swc/helpers': optional: true + '@rspack/lite-tapable@1.1.0': + resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + + '@rspack/lite-tapable@1.1.2': + resolution: {integrity: sha512-1OnyWChLGE46YzWyjlmYJssOu/Y0STAnnr2ueKPqDCYTf63GJMs0mxNnCul4dNiVqHYPKv3/fxrTY3IpqoVwZQ==} + '@rspack/plugin-react-refresh@2.0.2': resolution: {integrity: sha512-dGNZiCxQxgAUI9sah7gd8u+O7OJZRCmqtEJNDOd8xW5RqcieC86F7p5qcShyw6onH5pKf57evpr2VjGbaFGkZg==} peerDependencies: @@ -4582,8 +4655,8 @@ packages: resolution: {integrity: sha512-P0PVlfrDvfvCYg2KPIS7YUG/4i6ZPf8z1MicXx09C9Cz9W9UhSBh/nii13eBdDtLav2BFMKhvaFMcghXHX03Hw==} engines: {node: '>=18'} - '@sentry-internal/node-cpu-profiler@2.2.0': - resolution: {integrity: sha512-oLHVYurqZfADPh5hvmQYS5qx8t0UZzT2u6+/68VXsFruQEOnYJTODKgU3BVLmemRs3WE6kCJjPeFdHVYOQGSzQ==} + '@sentry-internal/node-cpu-profiler@2.4.1': + resolution: {integrity: sha512-Wnkik+RLvRUZEB8Zx5ofgPdcC8bCSoiUUiyH6TorrLK6PBPerbjK48c2GsJf6l5Hc5GAhA3fitueVLATB0XhWQ==} engines: {node: '>=18'} '@sentry-internal/replay-canvas@10.37.0': @@ -4606,55 +4679,55 @@ packages: resolution: {integrity: sha512-QaXd/NzaZ2vmiA2FNu2nBkgQU+17N3fE+zVOTzG0YK54QDSJMd4n3AeJIEyPhSzkOob+GqtO22nbYf6AATFMAw==} engines: {node: '>= 14'} - '@sentry/cli-darwin@2.58.4': - resolution: {integrity: sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ==} + '@sentry/cli-darwin@2.58.6': + resolution: {integrity: sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==} engines: {node: '>=10'} os: [darwin] - '@sentry/cli-linux-arm64@2.58.4': - resolution: {integrity: sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig==} + '@sentry/cli-linux-arm64@2.58.6': + resolution: {integrity: sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==} engines: {node: '>=10'} cpu: [arm64] os: [linux, freebsd, android] - '@sentry/cli-linux-arm@2.58.4': - resolution: {integrity: sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA==} + '@sentry/cli-linux-arm@2.58.6': + resolution: {integrity: sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==} engines: {node: '>=10'} cpu: [arm] os: [linux, freebsd, android] - '@sentry/cli-linux-i686@2.58.4': - resolution: {integrity: sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA==} + '@sentry/cli-linux-i686@2.58.6': + resolution: {integrity: sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==} engines: {node: '>=10'} cpu: [x86, ia32] os: [linux, freebsd, android] - '@sentry/cli-linux-x64@2.58.4': - resolution: {integrity: sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q==} + '@sentry/cli-linux-x64@2.58.6': + resolution: {integrity: sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==} engines: {node: '>=10'} cpu: [x64] os: [linux, freebsd, android] - '@sentry/cli-win32-arm64@2.58.4': - resolution: {integrity: sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w==} + '@sentry/cli-win32-arm64@2.58.6': + resolution: {integrity: sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@sentry/cli-win32-i686@2.58.4': - resolution: {integrity: sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug==} + '@sentry/cli-win32-i686@2.58.6': + resolution: {integrity: sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==} engines: {node: '>=10'} cpu: [x86, ia32] os: [win32] - '@sentry/cli-win32-x64@2.58.4': - resolution: {integrity: sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w==} + '@sentry/cli-win32-x64@2.58.6': + resolution: {integrity: sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@sentry/cli@2.58.4': - resolution: {integrity: sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg==} + '@sentry/cli@2.58.6': + resolution: {integrity: sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==} engines: {node: '>= 10'} hasBin: true @@ -4718,41 +4791,38 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} - '@solid-primitives/event-listener@2.4.3': - resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==} + '@solid-primitives/event-listener@2.4.5': + resolution: {integrity: sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/keyboard@1.3.3': - resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==} + '@solid-primitives/keyboard@1.3.5': + resolution: {integrity: sha512-sav+l+PL+74z3yaftVs7qd8c2SXkqzuxPOVibUe5wYMt+U5Hxp3V3XCPgBPN2I6cANjvoFtz0NiU8uHVLdi9FQ==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/resize-observer@2.1.3': - resolution: {integrity: sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==} + '@solid-primitives/resize-observer@2.1.5': + resolution: {integrity: sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/rootless@1.5.2': - resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==} + '@solid-primitives/rootless@1.5.3': + resolution: {integrity: sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/static-store@0.1.2': - resolution: {integrity: sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==} + '@solid-primitives/static-store@0.1.3': + resolution: {integrity: sha512-uxez7SXnr5GiRnzqO2IEDjOJRIXaG+0LZLBizmUA1FwSi+hrpuMzVBwyk70m4prcl8X6FDDXUl9O8hSq8wHbBQ==} peerDependencies: solid-js: ^1.6.12 - '@solid-primitives/utils@6.3.2': - resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} + '@solid-primitives/utils@6.4.0': + resolution: {integrity: sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==} peerDependencies: solid-js: ^1.6.12 - '@speed-highlight/core@1.2.14': - resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@speed-highlight/core@1.2.17': + resolution: {integrity: sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==} '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -4762,65 +4832,65 @@ packages: peerDependencies: postcss: ^8.2.15 - '@tailwindcss/node@4.1.18': - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + '@tailwindcss/node@4.3.1': + resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==} - '@tailwindcss/oxide-android-arm64@4.1.18': - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-android-arm64@4.3.1': + resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.18': - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-arm64@4.3.1': + resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.18': - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-x64@4.3.1': + resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.18': - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-freebsd-x64@4.3.1': + resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': + resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': + resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': + resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': + resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.18': - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-musl@4.3.1': + resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.18': - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + '@tailwindcss/oxide-wasm32-wasi@4.3.1': + resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -4831,39 +4901,48 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': + resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': + resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.18': - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} - engines: {node: '>= 10'} + '@tailwindcss/oxide@4.3.1': + resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==} + engines: {node: '>= 20'} - '@tailwindcss/postcss@4.1.18': - resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@tailwindcss/postcss@4.3.1': + resolution: {integrity: sha512-dNJuNbdEJT/SWRuXTYP1WSamelsz3ztkUsdtWQPjrexysrTpaEPM40P/71knXiXLYEojqPOEGitVLLpPMS5T6A==} '@tanstack/devtools-client@0.0.5': resolution: {integrity: sha512-hsNDE3iu4frt9cC2ppn1mNRnLKo2uc1/1hXAyY9z4UYb+o40M2clFAhiFoo4HngjfGJDV3x18KVVIq7W4Un+zA==} engines: {node: '>=18'} + '@tanstack/devtools-client@0.0.6': + resolution: {integrity: sha512-f85ZJXJnDIFOoykG/BFIixuAevJovCvJF391LPs6YjBAPhGYC50NWlx1y4iF/UmK5/cCMx+/JqI5SBOz7FanQQ==} + engines: {node: '>=18'} + '@tanstack/devtools-event-bus@0.4.0': resolution: {integrity: sha512-1t+/csFuDzi+miDxAOh6Xv7VDE80gJEItkTcAZLjV5MRulbO/W8ocjHLI2Do/p2r2/FBU0eKCRTpdqvXaYoHpQ==} engines: {node: '>=18'} - '@tanstack/devtools-event-client@0.4.0': - resolution: {integrity: sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==} + '@tanstack/devtools-event-bus@0.4.1': + resolution: {integrity: sha512-cNnJ89Q021Zf883rlbBTfsaxTfi2r73/qejGtyTa7ksErF3hyDyAq1aTbo5crK9dAL7zSHh9viKY1BtMls1QOA==} engines: {node: '>=18'} - '@tanstack/devtools-ui@0.4.4': - resolution: {integrity: sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg==} + '@tanstack/devtools-event-client@0.4.4': + resolution: {integrity: sha512-6T5Yop/793YI+H+5J8Hsyj4kCih9sl4t3ElLgKioW5hk3ocn+ZdSJ94tT7vL7uabxSugWYBZlOTMPzEw2puvQw==} + engines: {node: '>=18'} + hasBin: true + + '@tanstack/devtools-ui@0.5.0': + resolution: {integrity: sha512-nNZ14054n31fWB61jtWhZYLRdQ3yceCE3G/RINoINUB0RqIGZAIm9DnEDwOTAOfqt4/a/D8vNk8pJu6RQUp74g==} engines: {node: '>=18'} peerDependencies: solid-js: '>=1.9.7' @@ -4874,14 +4953,15 @@ packages: peerDependencies: vite: ^6.0.0 || ^7.0.0 - '@tanstack/devtools@0.10.4': - resolution: {integrity: sha512-GR/HMWe+eAZgSm/mOeuWMs/cXy3pEcrdMBU+OH0c6Qv1IXYv/xqru4aCSJPe+2/eJXng5ioqCsoVt9MztyU1mg==} + '@tanstack/devtools@0.10.14': + resolution: {integrity: sha512-bg1e0PyjmMMsc9VSOGb9etu15CpFdAwlQ5DD2xS6N93iTPgCPWXiZQFZygrEDoKnnx1x7BM6QTaiukizaejgSA==} engines: {node: '>=18'} + hasBin: true peerDependencies: solid-js: '>=1.9.7' - '@tanstack/react-devtools@0.9.3': - resolution: {integrity: sha512-SJTYWXWZkbWznwUwZ11awinPGB5StVIVyJXT0BFM1zUgjuajRwT8xRHl1oXVzVqqjJP5kfj89jkbFrcQPpq7Ng==} + '@tanstack/react-devtools@0.9.13': + resolution: {integrity: sha512-O9YXTEe2dlnw2pPNKFZ4Wk7zC4qrDvc0SAALKfMVedeZ2Dyd0LEJUabYS6GPm+DmnrBhc7nJx6Zqc9aDjFrj4g==} engines: {node: '>=18'} peerDependencies: '@types/react': '>=16.8' @@ -4924,11 +5004,8 @@ packages: '@tusbar/cache-control@2.0.0': resolution: {integrity: sha512-9qYH+H1IQUseyJxhkDHLgtkMyi/OyY3GIFHkB3OBZkK8q4jXqCBzWdRB+7TYtWeEMDip/97bx8boB1FIHWNSiA==} - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -4976,18 +5053,12 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -5028,12 +5099,6 @@ packages: '@types/node@25.0.10': resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==} - '@types/node@25.9.4': - resolution: {integrity: sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==} - - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/pegjs@0.10.6': resolution: {integrity: sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==} @@ -5046,8 +5111,8 @@ packages: '@types/qrcode@1.5.6': resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - '@types/qs@6.14.0': - resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -5084,157 +5149,175 @@ packages: '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} - '@typescript-eslint/eslint-plugin@8.54.0': - resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@typescript-eslint/eslint-plugin@8.62.0': + resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.54.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser': ^8.62.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.54.0': - resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + '@typescript-eslint/parser@8.62.0': + resolution: {integrity: sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.54.0': - resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + '@typescript-eslint/project-service@8.62.0': + resolution: {integrity: sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.54.0': - resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + '@typescript-eslint/scope-manager@8.62.0': + resolution: {integrity: sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.54.0': - resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + '@typescript-eslint/tsconfig-utils@8.62.0': + resolution: {integrity: sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.54.0': - resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + '@typescript-eslint/type-utils@8.62.0': + resolution: {integrity: sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.54.0': - resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + '@typescript-eslint/types@8.62.0': + resolution: {integrity: sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.54.0': - resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + '@typescript-eslint/typescript-estree@8.62.0': + resolution: {integrity: sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.54.0': - resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + '@typescript-eslint/utils@8.62.0': + resolution: {integrity: sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.54.0': - resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + '@typescript-eslint/visitor-keys@8.62.0': + resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] os: [android] - '@unrs/resolver-binding-android-arm64@1.11.1': - resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} cpu: [arm64] os: [android] - '@unrs/resolver-binding-darwin-arm64@1.11.1': - resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} cpu: [arm64] os: [darwin] - '@unrs/resolver-binding-darwin-x64@1.11.1': - resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} cpu: [x64] os: [darwin] - '@unrs/resolver-binding-freebsd-x64@1.11.1': - resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} cpu: [x64] os: [freebsd] - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} cpu: [arm] os: [linux] - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} cpu: [arm64] os: [linux] - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} cpu: [arm64] os: [linux] - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} cpu: [ppc64] os: [linux] - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} cpu: [riscv64] os: [linux] - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} cpu: [riscv64] os: [linux] - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} cpu: [s390x] os: [linux] - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} cpu: [x64] os: [linux] - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} cpu: [x64] os: [linux] - '@unrs/resolver-binding-wasm32-wasi@1.11.1': - resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} cpu: [arm64] os: [win32] - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} cpu: [ia32] os: [win32] - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} cpu: [x64] os: [win32] @@ -5244,48 +5327,22 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/eslint-plugin@1.6.6': - resolution: {integrity: sha512-bwgQxQWRtnTVzsUHK824tBmHzjV0iTx3tZaiQIYDjX3SA7TsQS8CuDVqxXrRY3FaOUMgbGavesCxI9MOfFLm7Q==} + '@vitest/eslint-plugin@1.6.20': + resolution: {integrity: sha512-xRwWHFG0Utp6hXtbGiWk4VdKXCGdExD8kbWrrmFEiG5dk8anOJ+vbWbeOa8EbkocKQRTsx7JAWETccZiBgFp/Q==} engines: {node: '>=18'} peerDependencies: + '@typescript-eslint/eslint-plugin': '*' eslint: '>=8.57.0' typescript: '>=5.0.0' vitest: '*' peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true typescript: optional: true vitest: optional: true - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} - - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} - '@web3-storage/multipart-parser@1.0.0': resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} @@ -5340,6 +5397,174 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@yuku-analyzer/binding-darwin-arm64@0.5.39': + resolution: {integrity: sha512-+C9QJxMZ1ujKhhBf//NQTDYZ+es94VgekoULPBV4mHSUDNfmuVW2XB7FmV6RJv4NBrdZRQQ/WcbNiuz7M54VpA==} + cpu: [arm64] + os: [darwin] + + '@yuku-analyzer/binding-darwin-x64@0.5.39': + resolution: {integrity: sha512-GHO4pYMTm2W+tNxwM9bforEggO3qQ2V8qQ/+CxVLLJSFk2UwtwjDUmAzoCmydC2sxX6kS4B2C+UxR3IiGkvbIg==} + cpu: [x64] + os: [darwin] + + '@yuku-analyzer/binding-freebsd-x64@0.5.39': + resolution: {integrity: sha512-LWMPzuXL7QlSeiw43wgBZsBTEkcRs3mAshisQVztYCpeAhh9USznXmlo201rcE47X1V7hc8gc7FXRsBD9gs60Q==} + cpu: [x64] + os: [freebsd] + + '@yuku-analyzer/binding-linux-arm-gnu@0.5.39': + resolution: {integrity: sha512-W5jhcSUUf3iirM5qeZCQVHhnDfUUUSM2jqLvWhuWfnEbXlVECmr7PUZOX6k92FlKINrLpgkUgF+Qa3TJ7UV6pg==} + cpu: [arm] + os: [linux] + + '@yuku-analyzer/binding-linux-arm-musl@0.5.39': + resolution: {integrity: sha512-m0JlZRU9UMmHhgHJSAHzruc7/5RuxahLo1U9hrTEeePe3UtQfC6uNsJS+Oktd7K/HWhE5vJDfe2Jn8nn4Tc0PA==} + cpu: [arm] + os: [linux] + + '@yuku-analyzer/binding-linux-arm64-gnu@0.5.39': + resolution: {integrity: sha512-18tLRh0LUcwHqrXB31/xvxiiOZBCKQ4uTvAsltAfB9p+l1WDVYwogI3vkbvP/xkqqgWFlPYeV09wPmttdrelUw==} + cpu: [arm64] + os: [linux] + + '@yuku-analyzer/binding-linux-arm64-musl@0.5.39': + resolution: {integrity: sha512-v7bikc1uuy+Wqd7EHV9hAgQacsUMgLYmfm+QArs0RfsotNHOe7HRlEhQ1SUw6qgnOg2WU6QZDndoUio8CVr+uQ==} + cpu: [arm64] + os: [linux] + + '@yuku-analyzer/binding-linux-x64-gnu@0.5.39': + resolution: {integrity: sha512-jV97sJa+Hf7EcGAbj3NSvYP7VX0LlhpDkAO9vNVHjro0Aj9suwW20o4+fRPuzYHxZKUOJf0Hf3mmW7MUoCgb0A==} + cpu: [x64] + os: [linux] + + '@yuku-analyzer/binding-linux-x64-musl@0.5.39': + resolution: {integrity: sha512-r1PCUYBtcrnX5xwsEK1LY7QXty65Vh8y6JprItNqpMssRiOHCyLj8RW9omrV//9jGsEHco3PACXkZqAE94pvmw==} + cpu: [x64] + os: [linux] + + '@yuku-analyzer/binding-win32-arm64@0.5.39': + resolution: {integrity: sha512-WdFhMS8j8w5r85hD0hcieXF3le0EOMAv/iJYOVvvoVgupn2k+T//jyJjkuuzbAVIfq6sVv/IJPWa0vobhk3pfQ==} + cpu: [arm64] + os: [win32] + + '@yuku-analyzer/binding-win32-x64@0.5.39': + resolution: {integrity: sha512-qFH6vAxClP8BNpztHFt3r/Qkf5O/bMVlSPd9T+jyv+dSHlVgnbXgz5cbI3Ozd28FPbjmsxbGM9VCZq8GsMywHA==} + cpu: [x64] + os: [win32] + + '@yuku-codegen/binding-darwin-arm64@0.5.39': + resolution: {integrity: sha512-zBgmr0X0IQ5jr+lUsm0pK9EVcea4xOr/gTw/sFAZBkl5PrK6k+jhXCLcYTFnRZ7XCV+7XjZZeEBI3bOcTTb/Ug==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.5.39': + resolution: {integrity: sha512-DKKCXwLyRXquicGQODwt3oHUAl10Kcc9wFsuw9mqyR+vqLeHNsIPgO37zfzHGiTvDehwBcy6YfNIyS+bKxQhpA==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.5.39': + resolution: {integrity: sha512-IN+r1J1wrq8llBvvCx9O4/hZq6uMtYufDY1DyclnnVexuZ6oxTO+4Owg2PvHX1oeJWa0eWbvXyF75uscuX6FXw==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.5.39': + resolution: {integrity: sha512-CBmdviCmf/mw+AgU54OgA+oxgHiGgowYdz/AIPyQICKOo+F/bgeAGJcOdPvsGxAyLoylp8IdPXAqMzcmsaXdkA==} + cpu: [arm] + os: [linux] + + '@yuku-codegen/binding-linux-arm-musl@0.5.39': + resolution: {integrity: sha512-g34mpy2JG+rl5PaQmPARtf7BHHvHYhk3jMlwlfshUO+2frLLDCaSVTGzEb8jw5bqqiwHe/pMNveq9PYY1w/R+w==} + cpu: [arm] + os: [linux] + + '@yuku-codegen/binding-linux-arm64-gnu@0.5.39': + resolution: {integrity: sha512-EG6bv5an/4hx6Sy/ikDg7raRjtJgVoDLPfoDFULF9dJKlb7bVcGdhyAJUXy20HF5uQUMCtwtw+Zygduhvsajdw==} + cpu: [arm64] + os: [linux] + + '@yuku-codegen/binding-linux-arm64-musl@0.5.39': + resolution: {integrity: sha512-OTD73F2RMyxY9LTANkJZg+nFGq4VILqDQ/vtp8OdXxDls/cBIFGUHKHtWyZhXOBCtmtgVeJlWtiXsodmA2ZC9Q==} + cpu: [arm64] + os: [linux] + + '@yuku-codegen/binding-linux-x64-gnu@0.5.39': + resolution: {integrity: sha512-mZLLkl9wa+4V4poXBCjjMB4Qu2UXVvRQSgP1rLuzi7FUCkK5+Cy16Eawy8NgmWEtd82tpfe9Jmqt7rhl55LzJg==} + cpu: [x64] + os: [linux] + + '@yuku-codegen/binding-linux-x64-musl@0.5.39': + resolution: {integrity: sha512-KFkWzyUQaa7tRy5tmX5w//WAO58YiOh/GFsePeu7nuPfXueQFbKEa+9/5Jy0GoLCmaacg4quYwr3drGmY7PTew==} + cpu: [x64] + os: [linux] + + '@yuku-codegen/binding-win32-arm64@0.5.39': + resolution: {integrity: sha512-GAnElqWLv2tbmL/k0wYRrozqMozrEWhiWUSM6NpPOdQkEg4l9zfC+SqGJirNZQj7i+Oy+j3rIN0oMdz5hBsAow==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.5.39': + resolution: {integrity: sha512-uaF7BD+MkXj+9RFVih2Kv6UOg/Q0+jytSVYP5YRJfTrXcEFyEqNLMrZZav6dPGj9xOTSQUGgxvHNQS0XPf1R5Q==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-darwin-arm64@0.5.39': + resolution: {integrity: sha512-MYd/uTmmKRc9dyefCwprRYwqrXFbL97oW2+ANGqDpc5E/wezfsyF/aD+Xxb3pNrc/0bjhIPM76EO2a3ne7f0oQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.5.39': + resolution: {integrity: sha512-8qyPmKObZ9ro9gJWIhoUz3WZCDzf3OC1K9arLHb6zjvJWQPcQ64hxkuZXscGRX8p0elleZLx7sTy9HSbcuVS/A==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.5.39': + resolution: {integrity: sha512-ZP4Iksc54bBmVPVXg/8b9i+KzkQo+vGQE9BElnc+WRBQRos+xrH6pWLjWdm70AhJS+QLDDqEWpDValG22Mb4BA==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.5.39': + resolution: {integrity: sha512-T9T/QrDM4c7BKoJcLu2fDLF2ZaXIn+ZBPhXOVKM/EJvjvVe3TluIla1xXLlDdXjxU5M1G5q4ngcXz65qz1tvEA==} + cpu: [arm] + os: [linux] + + '@yuku-parser/binding-linux-arm-musl@0.5.39': + resolution: {integrity: sha512-/5FrXOu/M/m/91kdVxcZJWO8CYsvA93a9ihhRYlPEeTJ3iu8HWeZhRhlbCAVVulGpvpSifak/1UdIPjZDsumvg==} + cpu: [arm] + os: [linux] + + '@yuku-parser/binding-linux-arm64-gnu@0.5.39': + resolution: {integrity: sha512-hl6zf9VT6krdaM7kTjYmXElf9fbEtgjecLUJn/YmTc10IQYvxolwgSETKWNDouaq2mYPl3YG9xg4m/atZJXJYA==} + cpu: [arm64] + os: [linux] + + '@yuku-parser/binding-linux-arm64-musl@0.5.39': + resolution: {integrity: sha512-PVCU2JLondIFkTIuuZAaqnGIfL2H1AvugTQHMim1F+EWXqVvDQRMzlPXkvahLtB/qy5iegYmRSbfr5J8LUY45w==} + cpu: [arm64] + os: [linux] + + '@yuku-parser/binding-linux-x64-gnu@0.5.39': + resolution: {integrity: sha512-gVqZR/I4fKotvK3Wcg17Mvvm75wzB6ySW2UbIIbUKU9CPvEE66wGzu/Q3uHv08EKnM/k4KhB0NrmQR5/d37GYQ==} + cpu: [x64] + os: [linux] + + '@yuku-parser/binding-linux-x64-musl@0.5.39': + resolution: {integrity: sha512-quABxCeXY8Yu6Dljp08kNogcRQz3glGx4/voQ9Ee87kCZge9bzEo3Vfr+H2B/Z5xazR3Rsz65LEUTZNiuf6mcA==} + cpu: [x64] + os: [linux] + + '@yuku-parser/binding-win32-arm64@0.5.39': + resolution: {integrity: sha512-h9qTGty+oxY/4rb3R18IgNfOwh6D8MLKfoIyQ2UJU35AVKksqKjMGGPlenG9eqFm0ohvsxqgs6AMHEdfSyrxxg==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.5.39': + resolution: {integrity: sha512-geGQZsNoBnjJ3EGUphmy/39Ajm3jBkPqjY0qvQVlgpE8V5yUrDc7dVWdcJ5Pkv/UhOMS3FzSgJFQtRFgajbZsA==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.5.37': + resolution: {integrity: sha512-yaGadzsSgTqKXUFef9iUBP7tFXdkN+DWcZqU+MvixYajB3luC8HHCDfJZk/Dy/Hb8haAwJ3z0G9g7bjAG4nGJg==} + '@zeit/schemas@2.36.0': resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} @@ -5356,6 +5581,12 @@ packages: peerDependencies: acorn: ^8 + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -5365,11 +5596,6 @@ packages: resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.17.0: resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} @@ -5399,27 +5625,19 @@ packages: ajv: optional: true - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 - ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} peerDependencies: ajv: ^8.8.2 - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -5539,13 +5757,13 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - 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} + base32-decode@1.0.0: resolution: {integrity: sha512-KNWUX/R7wKenwE/G/qFMzGScOgVntOmbE27vvc6GrniDGYb6a5+qWcuoXl8WIOQL7q0TpK7nZDm1Y04Yi3Yn5g==} @@ -5565,10 +5783,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - baseline-browser-mapping@2.9.18: - resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} - hasBin: true - basic-auth@2.0.1: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} @@ -5614,12 +5828,12 @@ packages: blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.2: - resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} boolbase@1.0.0: @@ -5629,11 +5843,15 @@ packages: resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} engines: {node: '>=14.16'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -5642,13 +5860,8 @@ packages: browserslist-load-config@1.0.3: resolution: {integrity: sha512-boNaPS4KlW6AITZQ60G+1oDJLuxauljDd7QNQFOYpRtldzcTDknMZ8awbwI0BT/8h1/Y/CG4k/tDOLip9lAGcg==} - browserslist-to-es-version@1.4.1: - resolution: {integrity: sha512-1bYCrck5Qh5HUy7P+iDuK39v757/ry5PnQo20vf4sHGeUrYKL2N2OF05U9ARSGt06TpFDQiTv9MT+eitYgWWxA==} - hasBin: true - - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + browserslist-to-es-version@1.4.2: + resolution: {integrity: sha512-3NV13pCv0wmPxxZZcekHAG6vt8rQ94w2c4/UBe3ZU3NDUm5TP+QFK3rjS6XeKWSHWpnPYNfQzlhnljka0BrEOA==} hasBin: true browserslist@4.28.4: @@ -5681,8 +5894,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -5701,16 +5914,9 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-lite@1.0.30001766: - resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} - caniuse-lite@1.0.30001799: resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - chain-function@1.0.1: resolution: {integrity: sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==} @@ -5734,8 +5940,8 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -5756,10 +5962,6 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} @@ -5834,8 +6036,8 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - comment-parser@1.4.5: - resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==} + comment-parser@1.4.7: + resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} engines: {node: '>= 12.0.0'} compare-versions@6.1.1: @@ -5852,14 +6054,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} + concurrently@9.2.3: + resolution: {integrity: sha512-ihjs0E2SxvDgq/MK418hX6YycQgKhsqxpbZuZbHo0yKfqDWdymWMjWYIpCIzqDDLLKClHlXev8whW/8WXmJ0BA==} engines: {node: '>=18'} hasBin: true - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} @@ -5871,14 +6070,18 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.1: - resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -5889,10 +6092,6 @@ packages: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -5912,10 +6111,6 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -5945,8 +6140,8 @@ packages: css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} css-what@6.2.2: @@ -6034,6 +6229,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -6042,14 +6240,6 @@ packages: supports-color: optional: true - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -6079,14 +6269,6 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dedent@1.7.1: - resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - dedent@1.7.2: resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: @@ -6198,9 +6380,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.279: - resolution: {integrity: sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==} - electron-to-chromium@1.5.380: resolution: {integrity: sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==} @@ -6231,12 +6410,12 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.6.5: - resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} + engine.io@6.6.9: + resolution: {integrity: sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + enhanced-resolve@5.21.6: + resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} enhanced-resolve@5.24.1: @@ -6255,6 +6434,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + envinfo@7.21.0: resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} engines: {node: '>=4'} @@ -6273,8 +6456,12 @@ packages: error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -6285,8 +6472,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.2.2: - resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + es-iterator-helpers@1.3.3: + resolution: {integrity: sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==} engines: {node: '>= 0.4'} es-module-lexer@1.7.0: @@ -6295,8 +6482,8 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: @@ -6307,20 +6494,20 @@ packages: resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} engines: {node: '>= 0.4'} - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} - es-toolkit@1.47.1: - resolution: {integrity: sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==} + es-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true - esbuild@0.27.2: - resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -6348,15 +6535,12 @@ packages: unrs-resolver: optional: true - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-plugin-import-x@4.16.1: - resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==} + eslint-plugin-import-x@4.17.1: + resolution: {integrity: sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/utils': ^8.0.0 - eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/utils': ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint-import-resolver-node: '*' peerDependenciesMeta: '@typescript-eslint/utils': @@ -6374,8 +6558,8 @@ packages: '@testing-library/dom': optional: true - eslint-plugin-playwright@2.5.1: - resolution: {integrity: sha512-q7oqVQTTfa3VXJQ8E+ln0QttPGrs/XmSO1FjOMzQYBMYF3btih4FIrhEYh34JF184GYDmq3lJ/n7CMa49OHBvA==} + eslint-plugin-playwright@2.10.4: + resolution: {integrity: sha512-l0V/VxyqfFbtqCTxj5AdRn3Q6S/hIW4nKBnKZVleVbZ24N2My6Usj//ytX3dKKqAoSbvKck9YtSytfdZ5qjLuA==} engines: {node: '>=16.9.0'} peerDependencies: eslint: '>=8.40.0' @@ -6392,11 +6576,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-testing-library@7.15.4: - resolution: {integrity: sha512-qP0ZPWAvDrS3oxZJErUfn3SZiIzj5Zh2EWuyWxjR5Bsk84ntxpquh4D0USorfyw5MzECURQ8OcEeBQdspHatzQ==} + eslint-plugin-testing-library@7.16.2: + resolution: {integrity: sha512-8gleGnQXK2ZA3hHwjCwpYTZvM+9VsrJ+/9kDI8CjqAQGAdMQOdn/rJNu7ZySENuiWlGKQWyZJ4ZjEg2zamaRHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -6414,6 +6598,10 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6449,9 +6637,6 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -6492,27 +6677,20 @@ packages: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} - expect-type@1.4.0: - resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} - engines: {node: '>=12.0.0'} - express-rate-limit@8.2.1: resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} engines: {node: '>= 0.10.0'} express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - exsolve@1.1.0: resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} @@ -6532,8 +6710,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -6558,8 +6736,8 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - filesize@11.0.17: - resolution: {integrity: sha512-oHLTvMLw6imZUl1se/RBQrFlyy50nXce4sU7yGR6Qc0JgCwqnfiFsAnEwotdGmfKLD7SArGUk2/5STU0k8LOBQ==} + filesize@11.0.19: + resolution: {integrity: sha512-9Q2itINBvCHwFw9v5X7czZm855yAiFIYlnugrh7+8tYO4ITHZMzV91m35q2FngL9hoZwwjSTQACFF/W52OzJZQ==} engines: {node: '>= 10.8.0'} fill-range@7.1.1: @@ -6594,8 +6772,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} @@ -6615,8 +6793,8 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - framer-motion@12.29.2: - resolution: {integrity: sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==} + framer-motion@12.42.0: + resolution: {integrity: sha512-wp7EJnfWaaEScVygKv3e20udoRz+LbtxScsuTkakAxfXmt+ReC6WyPW2nINRAGvd+hG9odwcjBLyOTPjH5pBRA==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -6665,8 +6843,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -6719,8 +6897,8 @@ packages: get-them-args@1.3.2: resolution: {integrity: sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==} - get-tsconfig@4.13.0: - resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -6733,9 +6911,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} 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 @@ -6761,6 +6936,10 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -6772,8 +6951,8 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - goober@2.1.18: - resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + goober@2.1.19: + resolution: {integrity: sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==} peerDependencies: csstype: ^3.0.10 @@ -6784,8 +6963,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql@16.12.0: - resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} has-bigints@1.1.0: @@ -6815,10 +6994,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} @@ -6874,8 +7049,8 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - human-id@4.1.3: - resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + human-id@4.2.0: + resolution: {integrity: sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==} hasBin: true human-signals@2.1.0: @@ -6918,15 +7093,19 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - immutable@5.1.6: - resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==} + immutable@5.1.8: + resolution: {integrity: sha512-TM5YqrGeTsVIPPpILzeqZ8D2Zc2TvNgSDi88zPF2a4cyqQdWV/wVWBDRDbNzzrLeRWScrFcOX9lW2iX6GOtUDw==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@2.0.5: - resolution: {integrity: sha512-0InH9/4oDCBRzWXhpOqusspLBrVfK1vPvbn9Wxl8DAQ8yyx5fWJRETICSwkiAMaYntjJAMBP1R4B6cQnEUYVEA==} + import-in-the-middle@2.0.6: + resolution: {integrity: sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==} + + import-in-the-middle@3.2.0: + resolution: {integrity: sha512-vR2B6HKIhaBjcZr2bLpFiJ1VbzOlRQ7aby4/gw5WPIzToLjqpfWw3VJ4sk1uDchoOODEirvO2jyrSPtUSL5CrQ==} + engines: {node: '>=18'} imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} @@ -6991,10 +7170,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} @@ -7012,6 +7187,10 @@ packages: engines: {node: '>=8'} hasBin: true + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -7191,19 +7370,19 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + js-yaml@3.15.0: + resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true jsdom@27.4.0: @@ -7251,15 +7430,15 @@ packages: engines: {node: '>=6'} hasBin: true - jsonata@2.1.0: - resolution: {integrity: sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w==} + jsonata@2.2.1: + resolution: {integrity: sha512-xd1uwUrKeIcJbsWhaoS3qAX4Ea8m0Mw0G5nlnAQvPT7TbZ5qaPdzBVTQia9KfyuyQm+nenfyjvzUDTRYHsC2sw==} engines: {node: '>= 8'} jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} jsox@1.2.125: resolution: {integrity: sha512-HIf1uwublnXZsy7p3yHTrhzMzrLO6xKnqXytT9pEil5QxaXi8eyer7Is4luF5hYSV4kD3v03Y32FWoAeVYTghQ==} @@ -7284,9 +7463,6 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - launch-editor@2.12.0: - resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} - launch-editor@2.14.1: resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} @@ -7306,8 +7482,8 @@ packages: webpack: optional: true - less@4.6.6: - resolution: {integrity: sha512-ooPSwQGQ2sVe8Dh1jVsbKKsRR2gd8lFK72BDkeSzjnD1T5aIHL65hCMfO0GVmtriKgDKrQv6xp9UrihUsWuAzA==} + less@4.6.7: + resolution: {integrity: sha512-o3UxHBPPVY1HtCXx15/z1NlknQiWyafRNbtLEv+6xFaDRI2g2xPKIH43do9dSwt8bGLTsjNSaifa48N3d6odsQ==} engines: {node: '>=18'} hasBin: true @@ -7315,74 +7491,74 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.30.2: - resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.30.2: - resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.2: - resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.2: - resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.2: - resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.2: - resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.2: - resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.2: - resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.2: - resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.2: - resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.2: - resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.2: - resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} lines-and-columns@1.2.4: @@ -7421,9 +7597,6 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -7445,6 +7618,10 @@ packages: resolution: {integrity: sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==} engines: {node: 20 || >=22} + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -7480,8 +7657,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} @@ -7562,27 +7739,70 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - miniflare@4.20260124.0: - resolution: {integrity: sha512-Co8onUh+POwOuLty4myQg+Nzg9/xZ5eAJc1oqYBzRovHd/XIpb5WAnRVaubcfAQJ85awWtF3yXUHCDx6cIaN3w==} - engines: {node: '>=18.0.0'} + miniflare@4.20260625.0: + resolution: {integrity: sha512-3kKXwRUObJsnBYPBgR0NiNZYKF/yv8GFyha1cx2EeAEraxNODgRVcyeRo+F1ok1tg5Mg7iUpOWSkknQTHuFhwA==} + engines: {node: '>=22.0.0'} hasBin: true - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} - engines: {node: 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minimizer-webpack-plugin@5.6.1: + resolution: {integrity: sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true + uglify-js: + optional: true + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} mkdirp-classic@0.5.3: @@ -7591,18 +7811,18 @@ packages: module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} - moo@0.5.2: - resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + moo@0.5.3: + resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==} morgan@1.10.1: resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} - motion-dom@12.29.2: - resolution: {integrity: sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==} + motion-dom@12.42.0: + resolution: {integrity: sha512-M63h4n8R+quJdNhBwuLlgxM+OLYa9+I/T2pzDRboB9fLXRdbou+Gw7Zury+SkpaCyACP1JHSjHgZ1EgTkBr30w==} - motion-utils@12.29.2: - resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==} + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} @@ -7628,13 +7848,8 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -7676,13 +7891,17 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - node-abi@3.87.0: - resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} + node-abi@3.92.0: + resolution: {integrity: sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==} engines: {node: '>=10'} node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-exports-info@1.6.2: + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} + engines: {node: '>= 0.4'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -7695,9 +7914,6 @@ packages: node-html-parser@7.0.2: resolution: {integrity: sha512-DxodLVh7a6JMkYzWyc8nBX9MaF4M0lLFYkJHlWOiu7+9/I6mwNK9u5TbAMC7qfqDJEPX9OIoWA2A9t4C2l1mUQ==} - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - node-releases@2.0.50: resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} engines: {node: '>=18'} @@ -7765,10 +7981,6 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - obug@2.1.3: - resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} - engines: {node: '>=12.20.0'} - on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -7872,8 +8084,8 @@ packages: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} parseley@0.12.1: resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} @@ -7911,12 +8123,12 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.1: - resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} - engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} @@ -7924,8 +8136,8 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -7948,8 +8160,8 @@ packages: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-protocol@1.11.0: - resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + pg-protocol@1.15.0: + resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -7958,18 +8170,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - picomatch@2.3.2: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -7991,9 +8195,6 @@ packages: resolution: {integrity: sha512-u9mdErTewKSMsr+ceCt8VcNuNP0ro5AXiPXhUVApuEyqr2Zlvt+DdCFBcm+yGWN8mhOdZJ27meIDbnoZgfzpOw==} hasBin: true - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} @@ -8002,11 +8203,21 @@ packages: engines: {node: '>=18'} hasBin: true + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} + engines: {node: '>=18'} + hasBin: true + playwright@1.58.0: resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==} engines: {node: '>=18'} hasBin: true + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} + engines: {node: '>=18'} + hasBin: true + pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -8021,8 +8232,8 @@ packages: peerDependencies: postcss: ^8.2.14 - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + postcss-selector-parser@6.1.4: + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} engines: {node: '>=4'} postcss-value-parser@4.2.0: @@ -8032,8 +8243,8 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -8237,8 +8448,8 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -8249,8 +8460,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.14.1: - resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} quansync@0.2.11: @@ -8274,6 +8485,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + raw-body@2.5.3: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} @@ -8292,13 +8507,13 @@ packages: react: 16.x || 17.x || 18.x || 19.x react-dom: 16.x || 17.x || 18.x || 19.x - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: - react: ^19.2.4 + react: ^19.2.7 - react-hotkeys-hook@5.2.3: - resolution: {integrity: sha512-Q27F8EuImYJOVSXAjSQrQPj9cx4GSNY+WdSdk5tSNN085H8/a00W6LZp0PrytEDwF6iT0pGTJeVEDKPRpEK2Bg==} + react-hotkeys-hook@5.3.3: + resolution: {integrity: sha512-aswgyWUnE25hmhzHTfKDmKzsaSE5DJ4LKaU/o6rQSXkDd/1Bh9TfAFQbHkf6fLy11HvlYkp+cDDarGdhmCDhoQ==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -8340,8 +8555,8 @@ packages: '@types/react': optional: true - react-router-devtools@6.2.0: - resolution: {integrity: sha512-YzaFAyKZEtTmWzBF/moKuMtEa8Hd/xhTtUCKarrhAbZMyR8S0OpCpN0pyKrNGNz7ueOc4jvvKdE9S6Q3UTotDg==} + react-router-devtools@6.2.1: + resolution: {integrity: sha512-qhQg5bJvsiWQpCddehh7HUpNt/zGlxHygG3rseooky1ENDGmlNwB08w4gW4YZRzeZOPgVpgEXgw94ypBpGfpww==} peerDependencies: '@types/react': '>=17' '@types/react-dom': '>=17' @@ -8350,28 +8565,28 @@ packages: react-router: '>=7.0.0' vite: '>=5.0.0 || >=6.0.0' - react-router-dom@6.29.0: - resolution: {integrity: sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==} + react-router-dom@6.30.4: + resolution: {integrity: sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router-dom@7.13.0: - resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} + react-router-dom@7.18.0: + resolution: {integrity: sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@6.29.0: - resolution: {integrity: sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==} + react-router@6.30.4: + resolution: {integrity: sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' - react-router@7.13.0: - resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} + react-router@7.18.0: + resolution: {integrity: sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -8390,14 +8605,14 @@ packages: '@types/react': optional: true - react-tooltip@5.30.0: - resolution: {integrity: sha512-Yn8PfbgQ/wmqnL7oBpz1QiDaLKrzZMdSUUdk7nVeGTwzbxCAJiJzR4VSYW+eIO42F1INt57sPUmpgKv0KwJKtg==} + react-tooltip@5.30.1: + resolution: {integrity: sha512-1lSPLQXuVooePxadUpmcwLgOsF1mIty7UZTJ9XnyfX4drOzStYs4JMXnazcDLguQr41W5OUZddOp9kfvArdpEQ==} peerDependencies: react: '>=16.14.0' react-dom: '>=16.14.0' - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} read-pkg@3.0.0: @@ -8531,11 +8746,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -8545,8 +8755,9 @@ packages: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} hasBin: true restore-cursor@4.0.0: @@ -8568,8 +8779,8 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rollup@4.57.0: - resolution: {integrity: sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==} + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8597,17 +8808,14 @@ packages: resolution: {integrity: sha512-DCUkRKUBR1lSpHKRcxNvHaYwGrUVf9MsoE1u6gd0CF37I8vwwtWc4b+FA9OwYZ4QA/shslzAYorD3MMfd+Rs/Q==} engines: {node: ^20.19.0 || >=22.12.0} - rspack-plugin-virtual-module@1.0.1: - resolution: {integrity: sha512-NQJ3fXa1v0WayvfHMWbyqLUA3JIqgCkhIcIOnZscuisinxorQyIAo+bqcU5pCusMKSyPqVIWO3caQyl0s9VDAg==} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -8752,10 +8960,6 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} - schema-utils@4.3.0: resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} engines: {node: '>= 10.13.0'} @@ -8780,16 +8984,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - - semver@7.8.4: - resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} - engines: {node: '>=10'} - hasBin: true - semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -8813,8 +9007,8 @@ packages: resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} engines: {node: '>=10'} - serve-handler@6.1.6: - resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} + serve-handler@6.1.7: + resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} serve-static@1.16.3: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} @@ -8824,8 +9018,8 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} - serve@14.2.5: - resolution: {integrity: sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==} + serve@14.2.6: + resolution: {integrity: sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==} engines: {node: '>= 14'} hasBin: true @@ -8876,16 +9070,16 @@ packages: shell-exec@1.0.2: resolution: {integrity: sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==} - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} - engines: {node: '>= 0.4'} - shell-quote@1.8.4: resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + shell-quote@1.9.0: + resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -8896,13 +9090,10 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -8923,19 +9114,19 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - socket.io-adapter@2.5.6: - resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} + socket.io-adapter@2.5.8: + resolution: {integrity: sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==} - socket.io-parser@4.2.5: - resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} + socket.io-parser@4.2.6: + resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} engines: {node: '>=10.0.0'} socket.io@4.8.1: resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} engines: {node: '>=10.2.0'} - solid-js@1.9.11: - resolution: {integrity: sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==} + solid-js@1.9.13: + resolution: {integrity: sha512-6hJeJMOcEX8ktqjpDoJZEmld3ijvcvWBDtiXBm7f4332SiFN66QeAQI1REQshvyUoISsSeJ4PHDauKYbwao9JQ==} sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} @@ -8973,8 +9164,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} spin-delay@2.0.1: resolution: {integrity: sha512-ilggKXKqAMwk21PSYvxuF/KCnrsGFDrnO6mXa629mj8fvfo+dOQfubDViqsRjRX5U1jd3Xb8FTsV+m4Tg7YeUg==} @@ -8984,24 +9175,18 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sql-formatter@15.7.0: - resolution: {integrity: sha512-o2yiy7fYXK1HvzA8P6wwj8QSuwG3e/XcpWht/jIxkQX99c0SVPw0OXdLSV9fHASPiYB09HLA0uq8hokGydi/QA==} + sql-formatter@15.8.2: + resolution: {integrity: sha512-kTYRg5FIcvsDtYUG2Qn9pYT6xKwiLJN5TTIvc5Mur6hIg4pSfdpHu8Yyu5bqESLHnVM3mXzD446cb2+uEaKZXg==} hasBin: true stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -9038,12 +9223,12 @@ packages: string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: @@ -9057,8 +9242,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -9144,6 +9329,9 @@ packages: tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + tailwindcss@4.3.1: + resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==} + tapable@2.3.0: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} @@ -9152,8 +9340,8 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -9163,49 +9351,6 @@ packages: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} - terser-webpack-plugin@5.6.1: - resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@minify-html/node': '*' - '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' - esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@minify-html/node': - optional: true - '@swc/core': - optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true - uglify-js: - optional: true - terser@5.48.0: resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} engines: {node: '>=10'} @@ -9214,17 +9359,6 @@ packages: text-encoder-lite@2.0.0: resolution: {integrity: sha512-bo08ND8LlBwPeU23EluRUcO3p2Rsb/eN5EIfOVqfRmblNDEVKK5IzM9Qfidvo+odT0hhV8mpXQcP/M5MMzABXw==} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@1.2.4: - resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} - engines: {node: '>=18'} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -9233,15 +9367,11 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} - engines: {node: '>=14.0.0'} - - tldts-core@7.0.19: - resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + tldts-core@7.4.5: + resolution: {integrity: sha512-pGrwzZDvPwKe+7NNUqAunb6rqTfynr0VOUhCMdqbu5xlvNiszsAJygRzwvpVycdzejlbpY+SWJOn+s75Og7FEA==} - tldts@7.0.19: - resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + tldts@7.4.5: + resolution: {integrity: sha512-RfEzKWcq5fHUOFq7J3rl3Oz6ylKGtcHqUznzj4EcXsxLSIjJcvpbXAQtWGeJQ0xKnimR5e0Cn+cn9TssfMzm+g==} hasBin: true to-data-view@2.0.0: @@ -9256,8 +9386,8 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} tr46@0.0.3: @@ -9271,8 +9401,8 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@2.4.0: - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -9280,6 +9410,7 @@ packages: tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} + deprecated: unmaintained hasBin: true peerDependencies: typescript: ^5.0.0 @@ -9298,9 +9429,6 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - turbo-stream@2.4.0: - resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} - turbo-stream@2.4.1: resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} @@ -9320,17 +9448,17 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - type-fest@5.4.2: - resolution: {integrity: sha512-FLEenlVYf7Zcd34ISMLo3ZzRE1gRjY1nMDTp+bQRBiPsaKyIW8K3Zr99ioHDUgA9OGuGGJPyYpNcffGmBhJfGg==} + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} engines: {node: '>=20'} type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -9344,16 +9472,16 @@ packages: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.54.0: - resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + typescript-eslint@8.62.0: + resolution: {integrity: sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -9367,17 +9495,14 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - - undici@7.18.2: - resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} - engines: {node: '>=20.18.1'} - undici@7.24.7: resolution: {integrity: sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==} engines: {node: '>=20.18.1'} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} @@ -9400,8 +9525,8 @@ packages: unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} - unrs-resolver@1.11.1: - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} @@ -9454,14 +9579,6 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - valibot@1.4.2: resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} peerDependencies: @@ -9490,8 +9607,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite-tsconfig-paths@6.0.5: - resolution: {integrity: sha512-f/WvY6ekHykUF1rWJUAbCU7iS/5QYDIugwpqJA+ttwKbxSbzNlqlE8vZSrsnxNQciUW+z6lvhlXMaEyZn9MSig==} + vite-tsconfig-paths@6.1.1: + resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} peerDependencies: vite: '*' @@ -9535,40 +9652,6 @@ packages: yaml: optional: true - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -9590,10 +9673,6 @@ packages: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} - engines: {node: '>=10.13.0'} - webpack-sources@3.5.0: resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==} engines: {node: '>=10.13.0'} @@ -9601,8 +9680,8 @@ packages: webpack-virtual-modules@0.5.0: resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} - webpack@5.97.1: - resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} + webpack@5.108.1: + resolution: {integrity: sha512-UUCihHQK3O7483Woa0SulNLDeAiOhHI2PN2PAPU4fVWJqbzhv04EJ8FaWtB9WWh3i8fRt28543U7VfuJTOrpgQ==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -9641,8 +9720,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} engines: {node: '>= 0.4'} which@1.3.1: @@ -9654,11 +9733,6 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} @@ -9667,17 +9741,17 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - workerd@1.20260124.0: - resolution: {integrity: sha512-JN6voV/fUQK342a39Rl+20YVmtIXZVbpxc7V/m809lUnlTGPy4aa5MI7PMoc+9qExgAEOw9cojvN5zOfqmMWLg==} + workerd@1.20260625.1: + resolution: {integrity: sha512-GApQvFX52SDM6L4u0+RRnUDB1wJOnEwoXjinkmOPtIyofWBxrlZckdegJSYc1leg++lLZ3+DQ4zMVmBqYVtzfA==} engines: {node: '>=16'} hasBin: true - wrangler@4.61.0: - resolution: {integrity: sha512-Kb8NMe1B/HM7/ds3hU+fcV1U7T996vRKJ0UU/qqgNUMwdemTRA+sSaH3mQvQslIBbprHHU81s0huA6fDIcwiaQ==} - engines: {node: '>=20.0.0'} + wrangler@4.105.0: + resolution: {integrity: sha512-7dXFH6OLj1Fv0y6ZeRPUxFTkp+duWD7/xxVi/1c0vfOeEYwIFKWB7cdqnY05DvY1Ta3BnqAwRkXfLs8PDj538g==} + engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260124.0 + '@cloudflare/workers-types': ^4.20260625.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -9697,42 +9771,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -9766,15 +9804,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@1.10.3: - resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} - engines: {node: '>= 6'} - - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} - engines: {node: '>= 14'} - hasBin: true - yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -9791,6 +9820,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -9809,6 +9842,15 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + yuku-analyzer@0.5.39: + resolution: {integrity: sha512-ifxMHDDo3OM2LVdgN18yMurZYMbZkM51MBjR59rBLd/BOowqkAU3zvlLKWZoH2xW0shVWRjXXlDnfHPvhxG2oA==} + + yuku-codegen@0.5.39: + resolution: {integrity: sha512-SGvKDXn0I7MyQEYpDASCPGXIA6+hv6mhxoABicLa1EPhyKFp9vrfgKiXtrR0QHhlTYwlWPtCa1W2hHHTfysoDQ==} + + yuku-parser@0.5.39: + resolution: {integrity: sha512-uOddu+b5QhhH5+7gOb4mkNG8O4ExOyrG5q0UPuugy7I495eN4fuQlu20fp0F2lx6My/Th01XxtHjpHscIH9UFw==} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -9816,7 +9858,7 @@ snapshots: '@acemir/cssom@0.9.31': {} - '@adobe/css-tools@4.4.4': {} + '@adobe/css-tools@4.5.0': {} '@alloc/quick-lru@5.2.0': {} @@ -9830,21 +9872,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@asamuzakjp/css-color@4.1.1': + '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 lru-cache: 11.2.5 - '@asamuzakjp/dom-selector@6.7.6': + '@asamuzakjp/dom-selector@6.8.1': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 - css-tree: 3.1.0 + css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.5 + lru-cache: 11.5.1 '@asamuzakjp/nwsapi@2.3.9': {} @@ -9889,13 +9931,7 @@ snapshots: '@babel/code-frame@7.26.2': dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/code-frame@7.28.6': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 @@ -9905,30 +9941,8 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.6': {} - '@babel/compat-data@7.29.7': {} - '@babel/core@7.28.6': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/template': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -9949,14 +9963,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.6': - dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.7': dependencies: '@babel/parser': 7.29.7 @@ -9965,22 +9971,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.28.6 - '@babel/helper-annotate-as-pure@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-compilation-targets@7.29.7': dependencies: '@babel/compat-data': 7.29.7 @@ -9989,19 +9983,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.6 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -10015,17 +9996,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-globals@7.28.0': {} - '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.28.5': - dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - '@babel/helper-member-expression-to-functions@7.29.7': dependencies: '@babel/traverse': 7.29.7 @@ -10033,26 +10005,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.29.7': - dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.6 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -10065,27 +10021,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.27.1': - dependencies: - '@babel/types': 7.28.6 - '@babel/helper-optimise-call-expression@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.6 - transitivePeerDependencies: - - supports-color - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -10095,13 +10036,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - dependencies: - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - transitivePeerDependencies: - - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': dependencies: '@babel/traverse': 7.29.7 @@ -10109,64 +10043,31 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-validator-option@7.29.7': {} - '@babel/helpers@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 - '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 '@babel/types': 7.29.7 - '@babel/parser@7.28.6': - dependencies: - '@babel/types': 7.28.6 - '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -10175,26 +10076,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.6)': + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.6 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) - transitivePeerDependencies: - - supports-color + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': dependencies: @@ -10207,17 +10097,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.28.6)': - dependencies: - '@babel/core': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6) - transitivePeerDependencies: - - supports-color - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -10229,16 +10108,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/runtime@7.28.6': {} - - '@babel/runtime@7.29.7': - optional: true - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/runtime@7.29.7': {} '@babel/template@7.29.7': dependencies: @@ -10246,18 +10116,6 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.28.6': - dependencies: - '@babel/code-frame': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.6 - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -10270,35 +10128,30 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.28.6': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@biomejs/cli-darwin-arm64@2.3.13': + '@biomejs/cli-darwin-arm64@2.5.1': optional: true - '@bkrem/react-transition-group@1.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@bkrem/react-transition-group@1.3.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: chain-function: 1.0.1 dom-helpers: 3.4.0 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) react-lifecycles-compat: 3.0.4 warning: 3.0.0 - '@bufbuild/protobuf@2.11.0': {} + '@bufbuild/protobuf@2.12.1': {} - '@changesets/apply-release-plan@7.0.14': + '@changesets/apply-release-plan@7.1.1': dependencies: - '@changesets/config': 3.1.2 + '@changesets/config': 3.1.4 '@changesets/get-version-range-type': 0.4.0 '@changesets/git': 3.0.4 '@changesets/should-skip-package': 0.1.2 @@ -10310,59 +10163,58 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.7.3 + semver: 7.8.5 - '@changesets/assemble-release-plan@6.0.9': + '@changesets/assemble-release-plan@6.0.10': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-dependents-graph': 2.1.4 '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - semver: 7.7.3 + semver: 7.8.5 '@changesets/changelog-git@0.2.1': dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.29.8(@types/node@25.0.10)': + '@changesets/cli@2.31.0(@types/node@25.0.10)': dependencies: - '@changesets/apply-release-plan': 7.0.14 - '@changesets/assemble-release-plan': 6.0.9 + '@changesets/apply-release-plan': 7.1.1 + '@changesets/assemble-release-plan': 6.0.10 '@changesets/changelog-git': 0.2.1 - '@changesets/config': 3.1.2 + '@changesets/config': 3.1.4 '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 - '@changesets/get-release-plan': 4.0.14 + '@changesets/get-dependents-graph': 2.1.4 + '@changesets/get-release-plan': 4.0.16 '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.6 + '@changesets/read': 0.6.7 '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 '@inquirer/external-editor': 1.0.3(@types/node@25.0.10) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 - ci-info: 3.9.0 enquirer: 2.4.1 fs-extra: 7.0.1 mri: 1.2.0 - p-limit: 2.3.0 package-manager-detector: 0.2.11 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.7.3 + semver: 7.8.5 spawndamnit: 3.0.1 term-size: 2.2.1 transitivePeerDependencies: - '@types/node' - '@changesets/config@3.1.2': + '@changesets/config@3.1.4': dependencies: '@changesets/errors': 0.2.0 - '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-dependents-graph': 2.1.4 '@changesets/logger': 0.1.1 + '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 @@ -10372,19 +10224,19 @@ snapshots: dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@2.1.3': + '@changesets/get-dependents-graph@2.1.4': dependencies: '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.7.3 + semver: 7.8.5 - '@changesets/get-release-plan@4.0.14': + '@changesets/get-release-plan@4.0.16': dependencies: - '@changesets/assemble-release-plan': 6.0.9 - '@changesets/config': 3.1.2 + '@changesets/assemble-release-plan': 6.0.10 + '@changesets/config': 3.1.4 '@changesets/pre': 2.0.2 - '@changesets/read': 0.6.6 + '@changesets/read': 0.6.7 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 @@ -10402,10 +10254,10 @@ snapshots: dependencies: picocolors: 1.1.1 - '@changesets/parse@0.4.2': + '@changesets/parse@0.4.3': dependencies: '@changesets/types': 6.1.0 - js-yaml: 4.1.1 + js-yaml: 4.3.0 '@changesets/pre@2.0.2': dependencies: @@ -10414,11 +10266,11 @@ snapshots: '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.6.6': + '@changesets/read@0.6.7': dependencies: '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 - '@changesets/parse': 0.4.2 + '@changesets/parse': 0.4.3 '@changesets/types': 6.1.0 fs-extra: 7.0.1 p-filter: 2.1.0 @@ -10437,40 +10289,40 @@ snapshots: dependencies: '@changesets/types': 6.1.0 fs-extra: 7.0.1 - human-id: 4.1.3 + human-id: 4.2.0 prettier: 2.8.8 - '@cloudflare/kv-asset-handler@0.4.2': {} + '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/unenv-preset@2.11.0(unenv@2.0.0-rc.24)(workerd@1.20260124.0)': + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260625.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260124.0 + workerd: 1.20260625.1 - '@cloudflare/workerd-darwin-64@1.20260124.0': + '@cloudflare/workerd-darwin-64@1.20260625.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260124.0': + '@cloudflare/workerd-darwin-arm64@1.20260625.1': optional: true - '@cloudflare/workerd-linux-64@1.20260124.0': + '@cloudflare/workerd-linux-64@1.20260625.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260124.0': + '@cloudflare/workerd-linux-arm64@1.20260625.1': optional: true - '@cloudflare/workerd-windows-64@1.20260124.0': + '@cloudflare/workerd-windows-64@1.20260625.1': optional: true - '@cloudflare/workers-types@4.20260127.0': {} + '@cloudflare/workers-types@4.20260628.1': {} '@conform-to/dom@1.16.0': {} - '@conform-to/react@1.16.0(react@19.2.4)': + '@conform-to/react@1.16.0(react@19.2.7)': dependencies: '@conform-to/dom': 1.16.0 - react: 19.2.4 + react: 19.2.7 '@conform-to/zod@1.16.0(zod@3.25.76)': dependencies: @@ -10481,27 +10333,29 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.1.0': {} + '@csstools/color-helpers@6.1.0': {} - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.26': {} + '@csstools/css-syntax-patches-for-csstree@1.1.6(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 - '@csstools/css-tokenizer@3.0.4': {} + '@csstools/css-tokenizer@4.0.0': {} '@emnapi/core@1.10.0': dependencies: @@ -10509,9 +10363,9 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/core@1.8.1': + '@emnapi/core@1.11.1': dependencies: - '@emnapi/wasi-threads': 1.1.0 + '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true @@ -10520,17 +10374,17 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.8.1': + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 optional: true @@ -10539,20 +10393,20 @@ snapshots: '@epic-web/client-hints@1.3.8': {} - '@epic-web/config@1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1)(typescript@5.9.3)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))': + '@epic-web/config@1.21.3(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(prettier@3.8.1)(typescript@5.9.3)': dependencies: '@total-typescript/ts-reset': 0.6.1 - '@vitest/eslint-plugin': 1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-jest-dom: 5.5.0(@testing-library/dom@10.4.1)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-playwright: 2.5.1(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-testing-library: 7.15.4(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@vitest/eslint-plugin': 1.6.20(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-jest-dom: 5.5.0(@testing-library/dom@10.4.1)(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-playwright: 2.10.4(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-testing-library: 7.16.2(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) globals: 16.5.0 prettier-plugin-tailwindcss: 0.6.14(prettier@3.8.1) tslib: 2.8.1 - typescript-eslint: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - '@ianvs/prettier-plugin-sort-imports' - '@prettier/plugin-hermes' @@ -10561,6 +10415,7 @@ snapshots: - '@shopify/prettier-plugin-liquid' - '@testing-library/dom' - '@trivago/prettier-plugin-sort-imports' + - '@typescript-eslint/eslint-plugin' - '@typescript-eslint/utils' - '@zackad/prettier-plugin-twig' - eslint @@ -10590,174 +10445,174 @@ snapshots: base32-decode: 1.0.0 base32-encode: 2.0.0 - '@esbuild/aix-ppc64@0.27.0': - optional: true - '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/aix-ppc64@0.28.1': optional: true '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/android-arm64@0.28.1': optional: true '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/android-arm@0.28.1': optional: true '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/android-x64@0.28.1': optional: true '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/darwin-arm64@0.28.1': optional: true '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/darwin-x64@0.28.1': optional: true '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/freebsd-arm64@0.28.1': optional: true '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/freebsd-x64@0.28.1': optional: true '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/linux-arm64@0.28.1': optional: true '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/linux-arm@0.28.1': optional: true '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/linux-ia32@0.28.1': optional: true '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/linux-loong64@0.28.1': optional: true '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.27.0': + '@esbuild/linux-mips64el@0.28.1': optional: true '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.27.0': + '@esbuild/linux-ppc64@0.28.1': optional: true '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.27.0': + '@esbuild/linux-riscv64@0.28.1': optional: true '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.27.0': + '@esbuild/linux-s390x@0.28.1': optional: true '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.27.0': + '@esbuild/linux-x64@0.28.1': optional: true '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.27.0': + '@esbuild/netbsd-arm64@0.28.1': optional: true '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.27.0': + '@esbuild/netbsd-x64@0.28.1': optional: true '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.27.0': + '@esbuild/openbsd-arm64@0.28.1': optional: true '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.27.0': + '@esbuild/openbsd-x64@0.28.1': optional: true '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.27.0': + '@esbuild/openharmony-arm64@0.28.1': optional: true '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.27.0': + '@esbuild/sunos-x64@0.28.1': optional: true '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.27.0': + '@esbuild/win32-arm64@0.28.1': optional: true '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.27.0': + '@esbuild/win32-ia32@0.28.1': optional: true '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + '@esbuild/win32-x64@0.28.1': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.7.0))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.1': + '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -10769,16 +10624,16 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.3': + '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.12.6 + ajv: 6.15.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 + js-yaml: 4.3.0 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -10792,41 +10647,46 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@exodus/bytes@1.10.0(@noble/hashes@2.0.1)': + '@exodus/bytes@1.15.1(@noble/hashes@2.2.0)': optionalDependencies: - '@noble/hashes': 2.0.1 + '@noble/hashes': 2.2.0 '@faker-js/faker@10.2.0': {} - '@floating-ui/core@1.7.4': + '@floating-ui/core@1.7.5': dependencies: - '@floating-ui/utils': 0.2.10 + '@floating-ui/utils': 0.2.11 - '@floating-ui/dom@1.7.5': + '@floating-ui/dom@1.7.6': dependencies: - '@floating-ui/core': 1.7.4 - '@floating-ui/utils': 0.2.10 + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 - '@floating-ui/react-dom@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@floating-ui/dom': 1.7.5 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@floating-ui/dom': 1.7.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - '@floating-ui/utils@0.2.10': {} + '@floating-ui/utils@0.2.11': {} - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.0.0': {} + '@img/colour@1.1.0': {} '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: @@ -10910,7 +10770,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.8.1 + '@emnapi/runtime': 1.11.1 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -10946,7 +10806,7 @@ snapshots: '@inquirer/external-editor@1.0.3(@types/node@25.0.10)': dependencies: - chardet: 2.1.1 + chardet: 2.2.0 iconv-lite: 0.7.2 optionalDependencies: '@types/node': 25.0.10 @@ -10957,17 +10817,11 @@ snapshots: optionalDependencies: '@types/node': 25.0.10 - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -11003,14 +10857,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -11072,7 +10926,7 @@ snapshots: - node-fetch - utf-8-validate - '@module-federation/enhanced@2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15))': + '@module-federation/enhanced@2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16))': dependencies: '@module-federation/bridge-react-webpack-plugin': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/cli': 2.5.1(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) @@ -11081,7 +10935,7 @@ snapshots: '@module-federation/inject-external-runtime-core-plugin': 2.5.1(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13))) '@module-federation/managers': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/manifest': 2.5.1(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) - '@module-federation/rspack': 2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) + '@module-federation/rspack': 2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) '@module-federation/runtime-tools': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/sdk': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/webpack-bundler-runtime': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) @@ -11090,7 +10944,7 @@ snapshots: upath: 2.0.1 optionalDependencies: typescript: 5.9.3 - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15) + webpack: 5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -11125,16 +10979,16 @@ snapshots: - utf-8-validate - vue-tsc - '@module-federation/node@2.7.44(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15))': + '@module-federation/node@2.7.44(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16))': dependencies: - '@module-federation/enhanced': 2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + '@module-federation/enhanced': 2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@module-federation/runtime': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/sdk': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) encoding: 0.1.13 node-fetch: 2.7.0(encoding@0.1.13) tapable: 2.3.0 optionalDependencies: - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15) + webpack: 5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -11142,13 +10996,13 @@ snapshots: - utf-8-validate - vue-tsc - '@module-federation/rsbuild-plugin@2.5.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15))': + '@module-federation/rsbuild-plugin@2.5.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16))': dependencies: - '@module-federation/enhanced': 2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) - '@module-federation/node': 2.7.44(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) + '@module-federation/enhanced': 2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) + '@module-federation/node': 2.7.44(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(typescript@5.9.3)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) '@module-federation/sdk': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) optionalDependencies: - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -11158,7 +11012,7 @@ snapshots: - vue-tsc - webpack - '@module-federation/rspack@2.5.1(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)': + '@module-federation/rspack@2.5.1(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3)': dependencies: '@module-federation/bridge-react-webpack-plugin': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/dts-plugin': 2.5.1(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) @@ -11167,7 +11021,7 @@ snapshots: '@module-federation/manifest': 2.5.1(node-fetch@2.7.0(encoding@0.1.13))(typescript@5.9.3) '@module-federation/runtime-tools': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@module-federation/sdk': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + '@rspack/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -11246,41 +11100,48 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@0.2.12': - dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 - '@tybys/wasm-util': 0.10.1 - optional: true - '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 - '@tybys/wasm-util': 0.10.1 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@tybys/wasm-util': 0.10.3 optional: true - '@nasa-gcn/remix-seo@2.0.1(@remix-run/react@2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3))': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@remix-run/react': 2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@nasa-gcn/remix-seo@2.0.1(@remix-run/react@2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3))(@remix-run/server-runtime@2.17.4(typescript@5.9.3))': + dependencies: + '@remix-run/react': 2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3) '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) - lodash: 4.17.23 + lodash: 4.18.1 - '@noble/hashes@2.0.1': {} + '@noble/hashes@2.2.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -11305,253 +11166,271 @@ snapshots: '@opentelemetry/api-logs@0.207.0': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs@0.211.0': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 - '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api-logs@0.219.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/instrumentation-amqplib@0.58.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-amqplib@0.58.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-connect@0.54.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-connect@0.54.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@types/connect': 3.4.38 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-dataloader@0.28.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-dataloader@0.28.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.59.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-express@0.59.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fs@0.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-fs@0.30.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-generic-pool@0.54.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-generic-pool@0.54.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.58.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-graphql@0.58.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-hapi@0.57.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.211.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.211.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.59.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-ioredis@0.59.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.38.2 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-kafkajs@0.20.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-kafkajs@0.20.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-knex@0.55.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-knex@0.55.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.59.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-koa@0.59.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-lru-memoizer@0.55.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-lru-memoizer@0.55.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.64.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongodb@0.64.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongoose@0.57.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql2@0.57.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 - '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql@0.57.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql@0.57.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@types/mysql': 2.15.27 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.63.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-pg@0.63.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 - '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) '@types/pg': 8.15.6 '@types/pg-pool': 2.0.7 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis@0.59.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-redis@0.59.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/redis-common': 0.38.2 - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/redis-common': 0.38.3 + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-tedious@0.30.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-tedious@0.30.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-undici@0.21.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-undici@0.21.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.207.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.207.0 - import-in-the-middle: 2.0.5 + import-in-the-middle: 2.0.6 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.211.0 - import-in-the-middle: 2.0.5 + import-in-the-middle: 2.0.6 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/redis-common@0.38.2': {} + '@opentelemetry/instrumentation@0.219.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.219.0 + import-in-the-middle: 3.2.0 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color - '@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/redis-common@0.38.3': {} + + '@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/semantic-conventions@1.39.0': {} + '@opentelemetry/semantic-conventions@1.41.1': {} - '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@oslojs/asn1@1.0.0': dependencies: @@ -11574,7 +11453,7 @@ snapshots: '@paralleldrive/cuid2@3.3.0': dependencies: - '@noble/hashes': 2.0.1 + '@noble/hashes': 2.2.0 bignumber.js: 9.3.1 error-causes: 3.0.2 @@ -11683,614 +11562,648 @@ snapshots: dependencies: '@prisma/debug': 6.0.0 - '@prisma/instrumentation@6.19.2(@opentelemetry/api@1.9.0)': + '@prisma/instrumentation@6.19.3(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@prisma/instrumentation@7.2.0(@opentelemetry/api@1.9.0)': + '@prisma/instrumentation@7.2.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color - '@prisma/instrumentation@7.3.0(@opentelemetry/api@1.9.0)': + '@prisma/instrumentation@7.3.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/instrumentation': 0.207.0(@opentelemetry/api@1.9.1) transitivePeerDependencies: - supports-color '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/primitive@1.1.4': {} + + '@radix-ui/react-accordion@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collapsible': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-collection': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(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) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-collapsible@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collection@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.10)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.10)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-context@1.1.2(@types/react@19.2.10)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-context@1.1.4(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-context@1.1.2(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-direction@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-direction@1.1.2(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-id@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-id@1.1.2(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(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) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.4) + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.7) '@radix-ui/rect': 1.1.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.10 + '@types/react-dom': 19.2.3(@types/react@19.2.10) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-presence@1.1.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.6(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.10)(react@19.2.7)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-slot@1.3.0(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(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) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(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) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.10)(react@19.2.7)': + dependencies: + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.10)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.10)(react@19.2.7)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.10)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 + optionalDependencies: + '@types/react': 19.2.10 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.10)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.2.4 + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.10)(react@19.2.4)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.10)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.4) - react: 19.2.4 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.7) + react: 19.2.7 optionalDependencies: '@types/react': 19.2.10 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(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) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) '@radix-ui/rect@1.1.1': {} - '@react-email/body@0.2.1(react@19.2.4)': + '@react-email/body@0.2.1(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/button@0.2.1(react@19.2.4)': + '@react-email/button@0.2.1(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/code-block@0.2.1(react@19.2.4)': + '@react-email/code-block@0.2.1(react@19.2.7)': dependencies: prismjs: 1.30.0 - react: 19.2.4 - - '@react-email/code-inline@0.0.6(react@19.2.4)': - dependencies: - react: 19.2.4 - - '@react-email/column@0.0.14(react@19.2.4)': - dependencies: - react: 19.2.4 - - '@react-email/components@1.0.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@react-email/body': 0.2.1(react@19.2.4) - '@react-email/button': 0.2.1(react@19.2.4) - '@react-email/code-block': 0.2.1(react@19.2.4) - '@react-email/code-inline': 0.0.6(react@19.2.4) - '@react-email/column': 0.0.14(react@19.2.4) - '@react-email/container': 0.0.16(react@19.2.4) - '@react-email/font': 0.0.10(react@19.2.4) - '@react-email/head': 0.0.13(react@19.2.4) - '@react-email/heading': 0.0.16(react@19.2.4) - '@react-email/hr': 0.0.12(react@19.2.4) - '@react-email/html': 0.0.12(react@19.2.4) - '@react-email/img': 0.0.12(react@19.2.4) - '@react-email/link': 0.0.13(react@19.2.4) - '@react-email/markdown': 0.0.18(react@19.2.4) - '@react-email/preview': 0.0.14(react@19.2.4) - '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@react-email/row': 0.0.13(react@19.2.4) - '@react-email/section': 0.0.17(react@19.2.4) - '@react-email/tailwind': 2.0.3(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4) - '@react-email/text': 0.1.6(react@19.2.4) - react: 19.2.4 + react: 19.2.7 + + '@react-email/code-inline@0.0.6(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@react-email/column@0.0.14(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@react-email/components@1.0.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@react-email/body': 0.2.1(react@19.2.7) + '@react-email/button': 0.2.1(react@19.2.7) + '@react-email/code-block': 0.2.1(react@19.2.7) + '@react-email/code-inline': 0.0.6(react@19.2.7) + '@react-email/column': 0.0.14(react@19.2.7) + '@react-email/container': 0.0.16(react@19.2.7) + '@react-email/font': 0.0.10(react@19.2.7) + '@react-email/head': 0.0.13(react@19.2.7) + '@react-email/heading': 0.0.16(react@19.2.7) + '@react-email/hr': 0.0.12(react@19.2.7) + '@react-email/html': 0.0.12(react@19.2.7) + '@react-email/img': 0.0.12(react@19.2.7) + '@react-email/link': 0.0.13(react@19.2.7) + '@react-email/markdown': 0.0.18(react@19.2.7) + '@react-email/preview': 0.0.14(react@19.2.7) + '@react-email/render': 2.0.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@react-email/row': 0.0.13(react@19.2.7) + '@react-email/section': 0.0.17(react@19.2.7) + '@react-email/tailwind': 2.0.3(@react-email/body@0.2.1(react@19.2.7))(@react-email/button@0.2.1(react@19.2.7))(@react-email/code-block@0.2.1(react@19.2.7))(@react-email/code-inline@0.0.6(react@19.2.7))(@react-email/container@0.0.16(react@19.2.7))(@react-email/heading@0.0.16(react@19.2.7))(@react-email/hr@0.0.12(react@19.2.7))(@react-email/img@0.0.12(react@19.2.7))(@react-email/link@0.0.13(react@19.2.7))(@react-email/preview@0.0.14(react@19.2.7))(@react-email/text@0.1.6(react@19.2.7))(react@19.2.7) + '@react-email/text': 0.1.6(react@19.2.7) + react: 19.2.7 transitivePeerDependencies: - react-dom - '@react-email/container@0.0.16(react@19.2.4)': + '@react-email/container@0.0.16(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/font@0.0.10(react@19.2.4)': + '@react-email/font@0.0.10(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/head@0.0.13(react@19.2.4)': + '@react-email/head@0.0.13(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/heading@0.0.16(react@19.2.4)': + '@react-email/heading@0.0.16(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/hr@0.0.12(react@19.2.4)': + '@react-email/hr@0.0.12(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/html@0.0.12(react@19.2.4)': + '@react-email/html@0.0.12(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/img@0.0.12(react@19.2.4)': + '@react-email/img@0.0.12(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/link@0.0.13(react@19.2.4)': + '@react-email/link@0.0.13(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/markdown@0.0.18(react@19.2.4)': + '@react-email/markdown@0.0.18(react@19.2.7)': dependencies: marked: 15.0.12 - react: 19.2.4 + react: 19.2.7 - '@react-email/preview@0.0.14(react@19.2.4)': + '@react-email/preview@0.0.14(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@react-email/render@2.0.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: html-to-text: 9.0.5 prettier: 3.8.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - '@react-email/row@0.0.13(react@19.2.4)': + '@react-email/row@0.0.13(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/section@0.0.17(react@19.2.4)': + '@react-email/section@0.0.17(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-email/tailwind@2.0.3(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)': + '@react-email/tailwind@2.0.3(@react-email/body@0.2.1(react@19.2.7))(@react-email/button@0.2.1(react@19.2.7))(@react-email/code-block@0.2.1(react@19.2.7))(@react-email/code-inline@0.0.6(react@19.2.7))(@react-email/container@0.0.16(react@19.2.7))(@react-email/heading@0.0.16(react@19.2.7))(@react-email/hr@0.0.12(react@19.2.7))(@react-email/img@0.0.12(react@19.2.7))(@react-email/link@0.0.13(react@19.2.7))(@react-email/preview@0.0.14(react@19.2.7))(@react-email/text@0.1.6(react@19.2.7))(react@19.2.7)': dependencies: - '@react-email/text': 0.1.6(react@19.2.4) - react: 19.2.4 + '@react-email/text': 0.1.6(react@19.2.7) + react: 19.2.7 tailwindcss: 4.1.18 optionalDependencies: - '@react-email/body': 0.2.1(react@19.2.4) - '@react-email/button': 0.2.1(react@19.2.4) - '@react-email/code-block': 0.2.1(react@19.2.4) - '@react-email/code-inline': 0.0.6(react@19.2.4) - '@react-email/container': 0.0.16(react@19.2.4) - '@react-email/heading': 0.0.16(react@19.2.4) - '@react-email/hr': 0.0.12(react@19.2.4) - '@react-email/img': 0.0.12(react@19.2.4) - '@react-email/link': 0.0.13(react@19.2.4) - '@react-email/preview': 0.0.14(react@19.2.4) + '@react-email/body': 0.2.1(react@19.2.7) + '@react-email/button': 0.2.1(react@19.2.7) + '@react-email/code-block': 0.2.1(react@19.2.7) + '@react-email/code-inline': 0.0.6(react@19.2.7) + '@react-email/container': 0.0.16(react@19.2.7) + '@react-email/heading': 0.0.16(react@19.2.7) + '@react-email/hr': 0.0.12(react@19.2.7) + '@react-email/img': 0.0.12(react@19.2.7) + '@react-email/link': 0.0.13(react@19.2.7) + '@react-email/preview': 0.0.14(react@19.2.7) - '@react-email/text@0.1.6(react@19.2.4)': + '@react-email/text@0.1.6(react@19.2.7)': dependencies: - react: 19.2.4 + react: 19.2.7 - '@react-router/cloudflare@7.13.0(@cloudflare/workers-types@4.20260127.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/cloudflare@7.18.0(@cloudflare/workers-types@4.20260628.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: - '@cloudflare/workers-types': 4.20260127.0 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - optionalDependencies: - typescript: 5.9.3 - - '@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0)': - dependencies: - '@babel/core': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - '@react-router/node': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@remix-run/node-fetch-server': 0.13.0 - arg: 5.0.2 - babel-dead-code-elimination: 1.0.12 - chokidar: 4.0.3 - dedent: 1.7.1(babel-plugin-macros@3.1.0) - es-module-lexer: 1.7.0 - exit-hook: 2.2.1 - isbot: 5.1.34 - jsesc: 3.0.2 - lodash: 4.17.23 - p-map: 7.0.4 - pathe: 1.1.2 - picocolors: 1.1.1 - pkg-types: 2.3.0 - prettier: 3.8.1 - react-refresh: 0.14.2 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - semver: 7.7.3 - tinyglobby: 0.2.15 - valibot: 1.2.0(typescript@5.9.3) - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) - vite-node: 3.2.4(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + '@cloudflare/workers-types': 4.20260628.1 + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: - '@react-router/serve': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) typescript: 5.9.3 - wrangler: 4.61.0(@cloudflare/workers-types@4.20260127.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - '@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.9.4)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0)': - dependencies: - '@babel/core': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.6) - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - '@react-router/node': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@remix-run/node-fetch-server': 0.13.0 + '@react-router/dev@7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1))': + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@react-router/node': 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@remix-run/node-fetch-server': 0.13.3 arg: 5.0.2 babel-dead-code-elimination: 1.0.12 chokidar: 4.0.3 - dedent: 1.7.1(babel-plugin-macros@3.1.0) + dedent: 1.7.2 es-module-lexer: 1.7.0 exit-hook: 2.2.1 isbot: 5.1.34 jsesc: 3.0.2 - lodash: 4.17.23 + lodash: 4.18.1 p-map: 7.0.4 pathe: 1.1.2 picocolors: 1.1.1 - pkg-types: 2.3.0 + pkg-types: 2.3.1 prettier: 3.8.1 react-refresh: 0.14.2 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - semver: 7.7.3 - tinyglobby: 0.2.15 - valibot: 1.2.0(typescript@5.9.3) - vite: 7.3.1(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) - vite-node: 3.2.4(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + semver: 7.8.5 + tinyglobby: 0.2.17 + valibot: 1.4.2(typescript@5.9.3) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) + vite-node: 3.2.4(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) optionalDependencies: - '@react-router/serve': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/serve': 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) typescript: 5.9.3 - wrangler: 4.61.0(@cloudflare/workers-types@4.20260127.0) + wrangler: 4.105.0(@cloudflare/workers-types@4.20260628.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -12306,7 +12219,7 @@ snapshots: - tsx - yaml - '@react-router/dev@8.0.1(babel-plugin-macros@3.1.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))': + '@react-router/dev@8.0.1(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1))': dependencies: '@babel/core': 7.29.7 '@babel/generator': 7.29.7 @@ -12315,12 +12228,12 @@ snapshots: '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 - '@react-router/node': 8.0.1(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/node': 8.0.1(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) '@remix-run/node-fetch-server': 0.13.3 arg: 5.0.2 babel-dead-code-elimination: 1.0.12 chokidar: 5.0.0 - dedent: 1.7.2(babel-plugin-macros@3.1.0) + dedent: 1.7.2 es-module-lexer: 2.1.0 exit-hook: 5.1.0 isbot: 5.1.44 @@ -12332,104 +12245,117 @@ snapshots: pkg-types: 2.3.1 prettier: 3.9.1 react-refresh: 0.18.0 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) semver: 7.8.5 tinyglobby: 0.2.17 valibot: 1.4.2(typescript@5.9.3) - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) optionalDependencies: typescript: 5.9.3 - wrangler: 4.61.0(@cloudflare/workers-types@4.20260127.0) + wrangler: 4.105.0(@cloudflare/workers-types@4.20260628.1) transitivePeerDependencies: - babel-plugin-macros - supports-color - '@react-router/express@7.13.0(express@4.22.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/express@7.13.0(express@4.22.2)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: - '@react-router/node': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - express: 4.22.1 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@react-router/node': 7.13.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + express: 4.22.2 + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 5.9.3 - '@react-router/express@7.13.0(express@5.2.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/express@7.13.0(express@5.2.1)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: - '@react-router/node': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/node': 7.13.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) express: 5.2.1 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + typescript: 5.9.3 + + '@react-router/express@7.18.0(express@4.22.2)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': + dependencies: + '@react-router/node': 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + express: 4.22.2 + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + typescript: 5.9.3 + + '@react-router/node@7.13.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': + dependencies: + '@mjackson/node-fetch-server': 0.2.0 + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 5.9.3 - '@react-router/node@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/node@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 5.9.3 - '@react-router/node@8.0.1(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/node@8.0.1(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: '@remix-run/node-fetch-server': 0.13.3 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 5.9.3 - '@react-router/remix-routes-option-adapter@7.13.0(@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0))(typescript@5.9.3)': + '@react-router/remix-routes-option-adapter@7.13.0(@react-router/dev@7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)))(typescript@5.9.3)': dependencies: - '@react-router/dev': 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@25.0.10)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))(wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0))(yaml@2.7.0) + '@react-router/dev': 7.18.0(@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))(wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1)) optionalDependencies: typescript: 5.9.3 - '@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)': + '@react-router/serve@7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - '@react-router/express': 7.13.0(express@4.22.1)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) - '@react-router/node': 7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3) + '@react-router/express': 7.18.0(express@4.22.2)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@react-router/node': 7.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) compression: 1.8.1 - express: 4.22.1 + express: 4.22.2 get-port: 5.1.1 morgan: 1.10.1 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) source-map-support: 0.5.21 transitivePeerDependencies: - supports-color - typescript - '@remix-run/node-fetch-server@0.13.0': {} - '@remix-run/node-fetch-server@0.13.3': {} - '@remix-run/react@2.15.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)': + '@remix-run/react@2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3)': dependencies: - '@remix-run/router': 1.22.0 - '@remix-run/server-runtime': 2.15.3(typescript@5.9.3) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-router: 6.29.0(react@19.2.4) - react-router-dom: 6.29.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - turbo-stream: 2.4.0 + '@remix-run/router': 1.23.3 + '@remix-run/server-runtime': 2.17.5(typescript@5.9.3) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 6.30.4(react@19.2.7) + react-router-dom: 6.30.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + turbo-stream: 2.4.1 optionalDependencies: typescript: 5.9.3 - '@remix-run/router@1.22.0': {} - '@remix-run/router@1.23.2': {} - '@remix-run/server-runtime@2.15.3(typescript@5.9.3)': + '@remix-run/router@1.23.3': {} + + '@remix-run/server-runtime@2.17.4(typescript@5.9.3)': dependencies: - '@remix-run/router': 1.22.0 + '@remix-run/router': 1.23.2 '@types/cookie': 0.6.0 '@web3-storage/multipart-parser': 1.0.0 - cookie: 0.6.0 + cookie: 0.7.2 set-cookie-parser: 2.7.2 source-map: 0.7.6 - turbo-stream: 2.4.0 + turbo-stream: 2.4.1 optionalDependencies: typescript: 5.9.3 - '@remix-run/server-runtime@2.17.4(typescript@5.9.3)': + '@remix-run/server-runtime@2.17.5(typescript@5.9.3)': dependencies: - '@remix-run/router': 1.23.2 + '@remix-run/router': 1.23.3 '@types/cookie': 0.6.0 '@web3-storage/multipart-parser': 1.0.0 cookie: 0.7.2 @@ -12441,154 +12367,163 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.53': {} - '@rollup/rollup-android-arm-eabi@4.57.0': + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true - '@rollup/rollup-android-arm64@4.57.0': + '@rollup/rollup-android-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-arm64@4.57.0': + '@rollup/rollup-darwin-arm64@4.62.2': optional: true - '@rollup/rollup-darwin-x64@4.57.0': + '@rollup/rollup-darwin-x64@4.62.2': optional: true - '@rollup/rollup-freebsd-arm64@4.57.0': + '@rollup/rollup-freebsd-arm64@4.62.2': optional: true - '@rollup/rollup-freebsd-x64@4.57.0': + '@rollup/rollup-freebsd-x64@4.62.2': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.57.0': + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.57.0': + '@rollup/rollup-linux-arm-musleabihf@4.62.2': optional: true - '@rollup/rollup-linux-arm64-gnu@4.57.0': + '@rollup/rollup-linux-arm64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-arm64-musl@4.57.0': + '@rollup/rollup-linux-arm64-musl@4.62.2': optional: true - '@rollup/rollup-linux-loong64-gnu@4.57.0': + '@rollup/rollup-linux-loong64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-loong64-musl@4.57.0': + '@rollup/rollup-linux-loong64-musl@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.57.0': + '@rollup/rollup-linux-ppc64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-ppc64-musl@4.57.0': + '@rollup/rollup-linux-ppc64-musl@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.57.0': + '@rollup/rollup-linux-riscv64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-riscv64-musl@4.57.0': + '@rollup/rollup-linux-riscv64-musl@4.62.2': optional: true - '@rollup/rollup-linux-s390x-gnu@4.57.0': + '@rollup/rollup-linux-s390x-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-gnu@4.57.0': + '@rollup/rollup-linux-x64-gnu@4.62.2': optional: true - '@rollup/rollup-linux-x64-musl@4.57.0': + '@rollup/rollup-linux-x64-musl@4.62.2': optional: true - '@rollup/rollup-openbsd-x64@4.57.0': + '@rollup/rollup-openbsd-x64@4.62.2': optional: true - '@rollup/rollup-openharmony-arm64@4.57.0': + '@rollup/rollup-openharmony-arm64@4.62.2': optional: true - '@rollup/rollup-win32-arm64-msvc@4.57.0': + '@rollup/rollup-win32-arm64-msvc@4.62.2': optional: true - '@rollup/rollup-win32-ia32-msvc@4.57.0': + '@rollup/rollup-win32-ia32-msvc@4.62.2': optional: true - '@rollup/rollup-win32-x64-gnu@4.57.0': + '@rollup/rollup-win32-x64-gnu@4.62.2': optional: true - '@rollup/rollup-win32-x64-msvc@4.57.0': + '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true '@rsbuild/core@1.7.2': dependencies: - '@rspack/core': 1.7.4(@swc/helpers@0.5.23) - '@rspack/lite-tapable': 1.1.0 + '@rspack/core': 1.7.12(@swc/helpers@0.5.23) + '@rspack/lite-tapable': 1.1.2 + '@swc/helpers': 0.5.23 + core-js: 3.47.0 + jiti: 2.7.0 + + '@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)': + dependencies: + '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23) '@swc/helpers': 0.5.23 + optionalDependencies: core-js: 3.47.0 - jiti: 2.6.1 + transitivePeerDependencies: + - '@module-federation/runtime-tools' - '@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0)': + '@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0)': dependencies: - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + '@rspack/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) '@swc/helpers': 0.5.23 optionalDependencies: core-js: 3.47.0 transitivePeerDependencies: - '@module-federation/runtime-tools' - '@rsbuild/plugin-check-syntax@1.6.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))': + '@rsbuild/plugin-check-syntax@1.6.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))': dependencies: - acorn: 8.15.0 - browserslist-to-es-version: 1.4.1 + acorn: 8.17.0 + browserslist-to-es-version: 1.4.2 htmlparser2: 10.0.0 picocolors: 1.1.1 source-map: 0.7.6 optionalDependencies: - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) - '@rsbuild/plugin-less@1.6.4(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsbuild/plugin-less@1.6.4(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: deepmerge: 4.3.1 - less: 4.6.6 - less-loader: 12.3.3(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(less@4.6.6)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + less: 4.6.7 + less-loader: 12.3.3(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(less@4.6.7)(webpack@5.108.1(lightningcss@1.32.0)) reduce-configs: 1.1.2 optionalDependencies: - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) transitivePeerDependencies: - '@rspack/core' - webpack - '@rsbuild/plugin-react@2.0.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))': + '@rsbuild/plugin-react@2.1.0(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))': dependencies: - '@rspack/plugin-react-refresh': 2.0.0(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(react-refresh@0.18.0) + '@rspack/plugin-react-refresh': 2.0.2(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(react-refresh@0.18.0) react-refresh: 0.18.0 optionalDependencies: - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) transitivePeerDependencies: - '@rspack/core' - '@rsbuild/plugin-sass@1.5.3(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))': + '@rsbuild/plugin-sass@1.5.3(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))': dependencies: deepmerge: 4.3.1 loader-utils: 2.0.4 - postcss: 8.5.15 + postcss: 8.5.16 reduce-configs: 1.1.2 sass-embedded: 1.100.0 optionalDependencies: - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) - '@rsdoctor/client@1.5.13': {} + '@rsdoctor/client@1.5.16': {} - '@rsdoctor/core@1.5.13(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsdoctor/core@1.5.16(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: - '@rsbuild/plugin-check-syntax': 1.6.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)) - '@rsdoctor/graph': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/sdk': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/types': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/utils': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rspack/resolver': 0.2.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@rsbuild/plugin-check-syntax': 1.6.1(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)) + '@rsdoctor/graph': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/sdk': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/types': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/utils': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rspack/resolver': 0.2.8(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) browserslist-load-config: 1.0.3 - es-toolkit: 1.47.1 - filesize: 11.0.17 + es-toolkit: 1.49.0 + filesize: 11.0.19 fs-extra: 11.3.3 - semver: 7.8.4 + semver: 7.8.5 source-map: 0.7.6 transitivePeerDependencies: - '@emnapi/core' @@ -12600,26 +12535,26 @@ snapshots: - utf-8-validate - webpack - '@rsdoctor/graph@1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsdoctor/graph@1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: - '@rsdoctor/types': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/utils': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - es-toolkit: 1.47.1 + '@rsdoctor/types': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/utils': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + es-toolkit: 1.49.0 path-browserify: 1.0.1 source-map: 0.7.6 transitivePeerDependencies: - '@rspack/core' - webpack - '@rsdoctor/rspack-plugin@1.5.13(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsdoctor/rspack-plugin@1.5.16(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: - '@rsdoctor/core': 1.5.13(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/graph': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/sdk': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/types': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/utils': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + '@rsdoctor/core': 1.5.16(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@rsbuild/core@2.1.0(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/graph': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/sdk': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/types': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/utils': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) optionalDependencies: - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + '@rspack/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -12629,12 +12564,12 @@ snapshots: - utf-8-validate - webpack - '@rsdoctor/sdk@1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsdoctor/sdk@1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: - '@rsdoctor/client': 1.5.13 - '@rsdoctor/graph': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/types': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) - '@rsdoctor/utils': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + '@rsdoctor/client': 1.5.16 + '@rsdoctor/graph': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/types': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) + '@rsdoctor/utils': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) launch-editor: 2.14.1 safer-buffer: 2.1.2 socket.io: 4.8.1 @@ -12646,23 +12581,23 @@ snapshots: - utf-8-validate - webpack - '@rsdoctor/types@1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsdoctor/types@1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: '@types/connect': 3.4.38 '@types/estree': 1.0.5 '@types/tapable': 2.3.0 source-map: 0.7.6 optionalDependencies: - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2) + '@rspack/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + webpack: 5.108.1(lightningcss@1.32.0) - '@rsdoctor/utils@1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2))': + '@rsdoctor/utils@1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0))': dependencies: '@babel/code-frame': 7.26.2 - '@rsdoctor/types': 1.5.13(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) + '@rsdoctor/types': 1.5.16(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(webpack@5.108.1(lightningcss@1.32.0)) '@types/estree': 1.0.5 - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) acorn-walk: 8.3.5 deep-eql: 4.1.4 envinfo: 7.21.0 @@ -12679,7 +12614,7 @@ snapshots: '@rslib/core@0.22.1(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0)(typescript@5.9.3)': dependencies: - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0) rsbuild-plugin-dts: 0.22.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -12688,43 +12623,67 @@ snapshots: - '@typescript/native-preview' - core-js - '@rspack/binding-darwin-arm64@1.7.4': + '@rspack/binding-darwin-arm64@1.7.12': optional: true '@rspack/binding-darwin-arm64@2.0.8': optional: true - '@rspack/binding-darwin-x64@1.7.4': + '@rspack/binding-darwin-arm64@2.1.0': + optional: true + + '@rspack/binding-darwin-x64@1.7.12': optional: true '@rspack/binding-darwin-x64@2.0.8': optional: true - '@rspack/binding-linux-arm64-gnu@1.7.4': + '@rspack/binding-darwin-x64@2.1.0': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.7.12': optional: true '@rspack/binding-linux-arm64-gnu@2.0.8': optional: true - '@rspack/binding-linux-arm64-musl@1.7.4': + '@rspack/binding-linux-arm64-gnu@2.1.0': + optional: true + + '@rspack/binding-linux-arm64-musl@1.7.12': optional: true '@rspack/binding-linux-arm64-musl@2.0.8': optional: true - '@rspack/binding-linux-x64-gnu@1.7.4': + '@rspack/binding-linux-arm64-musl@2.1.0': + optional: true + + '@rspack/binding-linux-riscv64-gnu@2.1.0': + optional: true + + '@rspack/binding-linux-riscv64-musl@2.1.0': + optional: true + + '@rspack/binding-linux-x64-gnu@1.7.12': optional: true '@rspack/binding-linux-x64-gnu@2.0.8': optional: true - '@rspack/binding-linux-x64-musl@1.7.4': + '@rspack/binding-linux-x64-gnu@2.1.0': + optional: true + + '@rspack/binding-linux-x64-musl@1.7.12': optional: true '@rspack/binding-linux-x64-musl@2.0.8': optional: true - '@rspack/binding-wasm32-wasi@1.7.4': + '@rspack/binding-linux-x64-musl@2.1.0': + optional: true + + '@rspack/binding-wasm32-wasi@1.7.12': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true @@ -12736,36 +12695,52 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rspack/binding-win32-arm64-msvc@1.7.4': + '@rspack/binding-wasm32-wasi@2.1.0': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@rspack/binding-win32-arm64-msvc@1.7.12': optional: true '@rspack/binding-win32-arm64-msvc@2.0.8': optional: true - '@rspack/binding-win32-ia32-msvc@1.7.4': + '@rspack/binding-win32-arm64-msvc@2.1.0': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.7.12': optional: true '@rspack/binding-win32-ia32-msvc@2.0.8': optional: true - '@rspack/binding-win32-x64-msvc@1.7.4': + '@rspack/binding-win32-ia32-msvc@2.1.0': + optional: true + + '@rspack/binding-win32-x64-msvc@1.7.12': optional: true '@rspack/binding-win32-x64-msvc@2.0.8': optional: true - '@rspack/binding@1.7.4': + '@rspack/binding-win32-x64-msvc@2.1.0': + optional: true + + '@rspack/binding@1.7.12': optionalDependencies: - '@rspack/binding-darwin-arm64': 1.7.4 - '@rspack/binding-darwin-x64': 1.7.4 - '@rspack/binding-linux-arm64-gnu': 1.7.4 - '@rspack/binding-linux-arm64-musl': 1.7.4 - '@rspack/binding-linux-x64-gnu': 1.7.4 - '@rspack/binding-linux-x64-musl': 1.7.4 - '@rspack/binding-wasm32-wasi': 1.7.4 - '@rspack/binding-win32-arm64-msvc': 1.7.4 - '@rspack/binding-win32-ia32-msvc': 1.7.4 - '@rspack/binding-win32-x64-msvc': 1.7.4 + '@rspack/binding-darwin-arm64': 1.7.12 + '@rspack/binding-darwin-x64': 1.7.12 + '@rspack/binding-linux-arm64-gnu': 1.7.12 + '@rspack/binding-linux-arm64-musl': 1.7.12 + '@rspack/binding-linux-x64-gnu': 1.7.12 + '@rspack/binding-linux-x64-musl': 1.7.12 + '@rspack/binding-wasm32-wasi': 1.7.12 + '@rspack/binding-win32-arm64-msvc': 1.7.12 + '@rspack/binding-win32-ia32-msvc': 1.7.12 + '@rspack/binding-win32-x64-msvc': 1.7.12 '@rspack/binding@2.0.8': optionalDependencies: @@ -12780,34 +12755,52 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 2.0.8 '@rspack/binding-win32-x64-msvc': 2.0.8 - '@rspack/core@1.7.4(@swc/helpers@0.5.23)': + '@rspack/binding@2.1.0': + optionalDependencies: + '@rspack/binding-darwin-arm64': 2.1.0 + '@rspack/binding-darwin-x64': 2.1.0 + '@rspack/binding-linux-arm64-gnu': 2.1.0 + '@rspack/binding-linux-arm64-musl': 2.1.0 + '@rspack/binding-linux-riscv64-gnu': 2.1.0 + '@rspack/binding-linux-riscv64-musl': 2.1.0 + '@rspack/binding-linux-x64-gnu': 2.1.0 + '@rspack/binding-linux-x64-musl': 2.1.0 + '@rspack/binding-wasm32-wasi': 2.1.0 + '@rspack/binding-win32-arm64-msvc': 2.1.0 + '@rspack/binding-win32-ia32-msvc': 2.1.0 + '@rspack/binding-win32-x64-msvc': 2.1.0 + + '@rspack/core@1.7.12(@swc/helpers@0.5.23)': dependencies: '@module-federation/runtime-tools': 0.22.0 - '@rspack/binding': 1.7.4 + '@rspack/binding': 1.7.12 '@rspack/lite-tapable': 1.1.0 optionalDependencies: '@swc/helpers': 0.5.23 - '@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)': + '@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23)': dependencies: '@rspack/binding': 2.0.8 optionalDependencies: '@module-federation/runtime-tools': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) '@swc/helpers': 0.5.23 - '@rspack/lite-tapable@1.1.0': {} - - '@rspack/plugin-react-refresh@2.0.0(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(react-refresh@0.18.0)': + '@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23)': dependencies: - react-refresh: 0.18.0 + '@rspack/binding': 2.1.0 optionalDependencies: - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + '@module-federation/runtime-tools': 2.5.1(node-fetch@2.7.0(encoding@0.1.13)) + '@swc/helpers': 0.5.23 + + '@rspack/lite-tapable@1.1.0': {} + + '@rspack/lite-tapable@1.1.2': {} - '@rspack/plugin-react-refresh@2.0.2(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(react-refresh@0.18.0)': + '@rspack/plugin-react-refresh@2.0.2(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23))(react-refresh@0.18.0)': dependencies: react-refresh: 0.18.0 optionalDependencies: - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + '@rspack/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) '@rspack/resolver-binding-darwin-arm64@0.2.8': optional: true @@ -12827,9 +12820,9 @@ snapshots: '@rspack/resolver-binding-linux-x64-musl@0.2.8': optional: true - '@rspack/resolver-binding-wasm32-wasi@0.2.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@rspack/resolver-binding-wasm32-wasi@0.2.8(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -12844,7 +12837,7 @@ snapshots: '@rspack/resolver-binding-win32-x64-msvc@0.2.8': optional: true - '@rspack/resolver@0.2.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@rspack/resolver@0.2.8(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': optionalDependencies: '@rspack/resolver-binding-darwin-arm64': 0.2.8 '@rspack/resolver-binding-darwin-x64': 0.2.8 @@ -12852,7 +12845,7 @@ snapshots: '@rspack/resolver-binding-linux-arm64-musl': 0.2.8 '@rspack/resolver-binding-linux-x64-gnu': 0.2.8 '@rspack/resolver-binding-linux-x64-musl': 0.2.8 - '@rspack/resolver-binding-wasm32-wasi': 0.2.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@rspack/resolver-binding-wasm32-wasi': 0.2.8(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) '@rspack/resolver-binding-win32-arm64-msvc': 0.2.8 '@rspack/resolver-binding-win32-ia32-msvc': 0.2.8 '@rspack/resolver-binding-win32-x64-msvc': 0.2.8 @@ -12860,17 +12853,17 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - '@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1))': + '@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0))': dependencies: '@rsbuild/core': 1.7.2 '@types/chai': 5.2.3 tinypool: 1.1.1 optionalDependencies: - jsdom: 27.4.0(@noble/hashes@2.0.1) + jsdom: 27.4.0(@noble/hashes@2.2.0) - '@rstest/coverage-istanbul@0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1)))': + '@rstest/coverage-istanbul@0.2.0(@rstest/core@0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0)))': dependencies: - '@rstest/core': 0.8.1(jsdom@27.4.0(@noble/hashes@2.0.1)) + '@rstest/core': 0.8.1(jsdom@27.4.0(@noble/hashes@2.2.0)) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -12894,10 +12887,10 @@ snapshots: dependencies: '@sentry/core': 10.37.0 - '@sentry-internal/node-cpu-profiler@2.2.0': + '@sentry-internal/node-cpu-profiler@2.4.1': dependencies: detect-libc: 2.1.2 - node-abi: 3.87.0 + node-abi: 3.92.0 '@sentry-internal/replay-canvas@10.37.0': dependencies: @@ -12921,9 +12914,9 @@ snapshots: '@sentry/bundler-plugin-core@4.8.0(encoding@0.1.13)': dependencies: - '@babel/core': 7.28.6 + '@babel/core': 7.29.7 '@sentry/babel-plugin-component-annotate': 4.8.0 - '@sentry/cli': 2.58.4(encoding@0.1.13) + '@sentry/cli': 2.58.6(encoding@0.1.13) dotenv: 16.6.1 find-up: 5.0.0 glob: 10.5.0 @@ -12933,31 +12926,31 @@ snapshots: - encoding - supports-color - '@sentry/cli-darwin@2.58.4': + '@sentry/cli-darwin@2.58.6': optional: true - '@sentry/cli-linux-arm64@2.58.4': + '@sentry/cli-linux-arm64@2.58.6': optional: true - '@sentry/cli-linux-arm@2.58.4': + '@sentry/cli-linux-arm@2.58.6': optional: true - '@sentry/cli-linux-i686@2.58.4': + '@sentry/cli-linux-i686@2.58.6': optional: true - '@sentry/cli-linux-x64@2.58.4': + '@sentry/cli-linux-x64@2.58.6': optional: true - '@sentry/cli-win32-arm64@2.58.4': + '@sentry/cli-win32-arm64@2.58.6': optional: true - '@sentry/cli-win32-i686@2.58.4': + '@sentry/cli-win32-i686@2.58.6': optional: true - '@sentry/cli-win32-x64@2.58.4': + '@sentry/cli-win32-x64@2.58.6': optional: true - '@sentry/cli@2.58.4(encoding@0.1.13)': + '@sentry/cli@2.58.6(encoding@0.1.13)': dependencies: https-proxy-agent: 5.0.1 node-fetch: 2.7.0(encoding@0.1.13) @@ -12965,98 +12958,98 @@ snapshots: proxy-from-env: 1.1.0 which: 2.0.2 optionalDependencies: - '@sentry/cli-darwin': 2.58.4 - '@sentry/cli-linux-arm': 2.58.4 - '@sentry/cli-linux-arm64': 2.58.4 - '@sentry/cli-linux-i686': 2.58.4 - '@sentry/cli-linux-x64': 2.58.4 - '@sentry/cli-win32-arm64': 2.58.4 - '@sentry/cli-win32-i686': 2.58.4 - '@sentry/cli-win32-x64': 2.58.4 + '@sentry/cli-darwin': 2.58.6 + '@sentry/cli-linux-arm': 2.58.6 + '@sentry/cli-linux-arm64': 2.58.6 + '@sentry/cli-linux-i686': 2.58.6 + '@sentry/cli-linux-x64': 2.58.6 + '@sentry/cli-win32-arm64': 2.58.6 + '@sentry/cli-win32-i686': 2.58.6 + '@sentry/cli-win32-x64': 2.58.6 transitivePeerDependencies: - encoding - supports-color '@sentry/core@10.37.0': {} - '@sentry/node-core@10.37.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + '@sentry/node-core@10.37.0(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': dependencies: '@apm-js-collab/tracing-hooks': 0.3.1 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@sentry/core': 10.37.0 - '@sentry/opentelemetry': 10.37.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - import-in-the-middle: 2.0.5 + '@sentry/opentelemetry': 10.37.0(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + import-in-the-middle: 2.0.6 transitivePeerDependencies: - supports-color '@sentry/node@10.37.0': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-amqplib': 0.58.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-connect': 0.54.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-dataloader': 0.28.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-express': 0.59.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fs': 0.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-generic-pool': 0.54.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-graphql': 0.58.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-hapi': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-http': 0.211.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-ioredis': 0.59.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-kafkajs': 0.20.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-knex': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-koa': 0.59.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-lru-memoizer': 0.55.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongodb': 0.64.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongoose': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql2': 0.57.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-pg': 0.63.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-redis': 0.59.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-tedious': 0.30.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-undici': 0.21.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 - '@prisma/instrumentation': 7.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-amqplib': 0.58.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-connect': 0.54.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-dataloader': 0.28.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-express': 0.59.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-fs': 0.30.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-generic-pool': 0.54.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-graphql': 0.58.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-hapi': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': 0.211.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-ioredis': 0.59.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-kafkajs': 0.20.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-knex': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-koa': 0.59.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-lru-memoizer': 0.55.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongodb': 0.64.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mongoose': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-mysql2': 0.57.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-pg': 0.63.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-redis': 0.59.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-tedious': 0.30.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-undici': 0.21.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@prisma/instrumentation': 7.2.0(@opentelemetry/api@1.9.1) '@sentry/core': 10.37.0 - '@sentry/node-core': 10.37.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - '@sentry/opentelemetry': 10.37.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) - import-in-the-middle: 2.0.5 - minimatch: 9.0.5 + '@sentry/node-core': 10.37.0(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + '@sentry/opentelemetry': 10.37.0(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + import-in-the-middle: 2.0.6 + minimatch: 9.0.9 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@10.37.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + '@sentry/opentelemetry@10.37.0(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 '@sentry/core': 10.37.0 '@sentry/profiling-node@10.37.0': dependencies: - '@sentry-internal/node-cpu-profiler': 2.2.0 + '@sentry-internal/node-cpu-profiler': 2.4.1 '@sentry/core': 10.37.0 '@sentry/node': 10.37.0 transitivePeerDependencies: - supports-color - '@sentry/react@10.37.0(react@19.2.4)': + '@sentry/react@10.37.0(react@19.2.7)': dependencies: '@sentry/browser': 10.37.0 '@sentry/core': 10.37.0 - react: 19.2.4 + react: 19.2.7 '@sentry/vite-plugin@4.8.0(encoding@0.1.13)': dependencies: @@ -13076,7 +13069,7 @@ snapshots: compare-versions: 6.1.1 cosmiconfig: 8.3.6(typescript@5.9.3) execa: 7.2.0 - jsonata: 2.1.0 + jsonata: 2.2.1 lru-cache: 10.4.3 ora: 6.3.1 prompts: 2.4.2 @@ -13086,185 +13079,194 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@solid-primitives/event-listener@2.4.3(solid-js@1.9.11)': + '@solid-primitives/event-listener@2.4.5(solid-js@1.9.13)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.11) - solid-js: 1.9.11 + '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) + solid-js: 1.9.13 - '@solid-primitives/keyboard@1.3.3(solid-js@1.9.11)': + '@solid-primitives/keyboard@1.3.5(solid-js@1.9.13)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.11) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.11) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.11) - solid-js: 1.9.11 + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.13) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.13) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) + solid-js: 1.9.13 - '@solid-primitives/resize-observer@2.1.3(solid-js@1.9.11)': + '@solid-primitives/resize-observer@2.1.5(solid-js@1.9.13)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.11) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.11) - '@solid-primitives/static-store': 0.1.2(solid-js@1.9.11) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.11) - solid-js: 1.9.11 + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.13) + '@solid-primitives/rootless': 1.5.3(solid-js@1.9.13) + '@solid-primitives/static-store': 0.1.3(solid-js@1.9.13) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) + solid-js: 1.9.13 - '@solid-primitives/rootless@1.5.2(solid-js@1.9.11)': + '@solid-primitives/rootless@1.5.3(solid-js@1.9.13)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.11) - solid-js: 1.9.11 + '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) + solid-js: 1.9.13 - '@solid-primitives/static-store@0.1.2(solid-js@1.9.11)': + '@solid-primitives/static-store@0.1.3(solid-js@1.9.13)': dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.11) - solid-js: 1.9.11 + '@solid-primitives/utils': 6.4.0(solid-js@1.9.13) + solid-js: 1.9.13 - '@solid-primitives/utils@6.3.2(solid-js@1.9.11)': + '@solid-primitives/utils@6.4.0(solid-js@1.9.13)': dependencies: - solid-js: 1.9.11 - - '@speed-highlight/core@1.2.14': {} + solid-js: 1.9.13 - '@standard-schema/spec@1.1.0': - optional: true + '@speed-highlight/core@1.2.17': {} '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 - '@tailwindcss/nesting@0.0.0-insiders.565cd3e(postcss@8.5.15)': + '@tailwindcss/nesting@0.0.0-insiders.565cd3e(postcss@8.5.16)': dependencies: - postcss: 8.5.15 - postcss-nested: 5.0.6(postcss@8.5.15) + postcss: 8.5.16 + postcss-nested: 5.0.6(postcss@8.5.16) - '@tailwindcss/node@4.1.18': + '@tailwindcss/node@4.3.1': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.4 - jiti: 2.6.1 - lightningcss: 1.30.2 + enhanced-resolve: 5.21.6 + jiti: 2.7.0 + lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.1.18 + tailwindcss: 4.3.1 - '@tailwindcss/oxide-android-arm64@4.1.18': + '@tailwindcss/oxide-android-arm64@4.3.1': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.18': + '@tailwindcss/oxide-darwin-arm64@4.3.1': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.18': + '@tailwindcss/oxide-darwin-x64@4.3.1': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.18': + '@tailwindcss/oxide-freebsd-x64@4.3.1': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.1': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + '@tailwindcss/oxide-linux-arm64-musl@4.3.1': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + '@tailwindcss/oxide-linux-x64-gnu@4.3.1': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.18': + '@tailwindcss/oxide-linux-x64-musl@4.3.1': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.18': + '@tailwindcss/oxide-wasm32-wasi@4.3.1': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.1': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + '@tailwindcss/oxide-win32-x64-msvc@4.3.1': optional: true - '@tailwindcss/oxide@4.1.18': + '@tailwindcss/oxide@4.3.1': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-x64': 4.1.18 - '@tailwindcss/oxide-freebsd-x64': 4.1.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 - - '@tailwindcss/postcss@4.1.18': + '@tailwindcss/oxide-android-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-arm64': 4.3.1 + '@tailwindcss/oxide-darwin-x64': 4.3.1 + '@tailwindcss/oxide-freebsd-x64': 4.3.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.1 + '@tailwindcss/oxide-linux-x64-musl': 4.3.1 + '@tailwindcss/oxide-wasm32-wasi': 4.3.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 + + '@tailwindcss/postcss@4.3.1': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.18 - '@tailwindcss/oxide': 4.1.18 - postcss: 8.5.6 - tailwindcss: 4.1.18 + '@tailwindcss/node': 4.3.1 + '@tailwindcss/oxide': 4.3.1 + postcss: 8.5.15 + tailwindcss: 4.3.1 '@tanstack/devtools-client@0.0.5': dependencies: - '@tanstack/devtools-event-client': 0.4.0 + '@tanstack/devtools-event-client': 0.4.4 + + '@tanstack/devtools-client@0.0.6': + dependencies: + '@tanstack/devtools-event-client': 0.4.4 '@tanstack/devtools-event-bus@0.4.0': dependencies: - ws: 8.19.0 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tanstack/devtools-event-bus@0.4.1': + dependencies: + ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@tanstack/devtools-event-client@0.4.0': {} + '@tanstack/devtools-event-client@0.4.4': {} - '@tanstack/devtools-ui@0.4.4(csstype@3.2.3)(solid-js@1.9.11)': + '@tanstack/devtools-ui@0.5.0(csstype@3.2.3)(solid-js@1.9.13)': dependencies: clsx: 2.1.1 - goober: 2.1.18(csstype@3.2.3) - solid-js: 1.9.11 + dayjs: 1.11.21 + goober: 2.1.19(csstype@3.2.3) + solid-js: 1.9.13 transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.4.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))': + '@tanstack/devtools-vite@0.4.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))': dependencies: - '@babel/core': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@tanstack/devtools-client': 0.0.5 '@tanstack/devtools-event-bus': 0.4.0 chalk: 5.6.2 - launch-editor: 2.12.0 - picomatch: 4.0.3 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + launch-editor: 2.14.1 + picomatch: 4.0.4 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@tanstack/devtools@0.10.4(csstype@3.2.3)(solid-js@1.9.11)': + '@tanstack/devtools@0.10.14(csstype@3.2.3)(solid-js@1.9.13)': dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.11) - '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.11) - '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.11) - '@tanstack/devtools-client': 0.0.5 - '@tanstack/devtools-event-bus': 0.4.0 - '@tanstack/devtools-ui': 0.4.4(csstype@3.2.3)(solid-js@1.9.11) + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.13) + '@solid-primitives/keyboard': 1.3.5(solid-js@1.9.13) + '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.13) + '@tanstack/devtools-client': 0.0.6 + '@tanstack/devtools-event-bus': 0.4.1 + '@tanstack/devtools-ui': 0.5.0(csstype@3.2.3)(solid-js@1.9.13) clsx: 2.1.1 - goober: 2.1.18(csstype@3.2.3) - solid-js: 1.9.11 + goober: 2.1.19(csstype@3.2.3) + solid-js: 1.9.13 transitivePeerDependencies: - bufferutil - csstype - utf-8-validate - '@tanstack/react-devtools@0.9.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)': + '@tanstack/react-devtools@0.9.13(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)': dependencies: - '@tanstack/devtools': 0.10.4(csstype@3.2.3)(solid-js@1.9.11) + '@tanstack/devtools': 0.10.14(csstype@3.2.3)(solid-js@1.9.13) '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) transitivePeerDependencies: - bufferutil - csstype @@ -13273,8 +13275,8 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: - '@babel/code-frame': 7.28.6 - '@babel/runtime': 7.28.6 + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -13284,19 +13286,19 @@ snapshots: '@testing-library/jest-dom@6.9.1': dependencies: - '@adobe/css-tools': 4.4.4 + '@adobe/css-tools': 4.5.0 aria-query: 5.3.2 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 '@testing-library/dom': 10.4.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) @@ -13309,12 +13311,7 @@ snapshots: '@tusbar/cache-control@2.0.0': {} - '@tybys/wasm-util@0.10.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@tybys/wasm-util@0.10.2': + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true @@ -13323,24 +13320,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.7 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.7 '@types/bcryptjs@3.0.0': dependencies: @@ -13379,26 +13376,19 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/eslint-scope@3.7.7': - dependencies: - '@types/eslint': 9.6.1 - '@types/estree': 1.0.9 - '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@types/estree@1.0.5': {} - '@types/estree@1.0.8': {} - '@types/estree@1.0.9': {} '@types/express-serve-static-core@5.1.1': dependencies: '@types/node': 25.0.10 - '@types/qs': 6.14.0 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -13441,14 +13431,6 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.9.4': - dependencies: - undici-types: 7.24.6 - optional: true - - '@types/parse-json@4.0.2': - optional: true - '@types/pegjs@0.10.6': {} '@types/pg-pool@2.0.7': @@ -13458,14 +13440,14 @@ snapshots: '@types/pg@8.15.6': dependencies: '@types/node': 25.0.10 - pg-protocol: 1.11.0 + pg-protocol: 1.15.0 pg-types: 2.2.0 '@types/qrcode@1.5.6': dependencies: '@types/node': 25.0.10 - '@types/qs@6.14.0': {} + '@types/qs@6.15.1': {} '@types/range-parser@1.2.7': {} @@ -13500,232 +13482,200 @@ snapshots: '@types/tapable@2.3.0': dependencies: - tapable: 2.3.0 + tapable: 2.3.3 '@types/tedious@4.0.14': dependencies: '@types/node': 25.0.10 - '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.0.10 + + '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/parser': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/type-utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.62.0 + eslint: 9.39.2(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.62.0 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.62.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@5.9.3) + '@typescript-eslint/types': 8.62.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.54.0': + '@typescript-eslint/scope-manager@8.62.0': dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/visitor-keys': 8.62.0 - '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.62.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.3) + eslint: 9.39.2(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.54.0': {} + '@typescript-eslint/types@8.62.0': {} - '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.62.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/project-service': 8.62.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@5.9.3) + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/visitor-keys': 8.62.0 debug: 4.4.3 - minimatch: 9.0.5 + minimatch: 10.2.5 semver: 7.8.5 tinyglobby: 0.2.17 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/types': 8.62.0 + '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.9.3) + eslint: 9.39.2(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.54.0': + '@typescript-eslint/visitor-keys@8.62.0': dependencies: - '@typescript-eslint/types': 8.54.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.62.0 + eslint-visitor-keys: 5.0.1 - '@unrs/resolver-binding-android-arm-eabi@1.11.1': + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true - '@unrs/resolver-binding-android-arm64@1.11.1': + '@unrs/resolver-binding-android-arm64@1.12.2': optional: true - '@unrs/resolver-binding-darwin-arm64@1.11.1': + '@unrs/resolver-binding-darwin-arm64@1.12.2': optional: true - '@unrs/resolver-binding-darwin-x64@1.11.1': + '@unrs/resolver-binding-darwin-x64@1.12.2': optional: true - '@unrs/resolver-binding-freebsd-x64@1.11.1': + '@unrs/resolver-binding-freebsd-x64@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': optional: true - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': optional: true - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': optional: true - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': optional: true - '@unrs/resolver-binding-linux-x64-musl@1.11.1': + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': optional: true - '@unrs/resolver-binding-wasm32-wasi@1.11.1': + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': dependencies: - '@napi-rs/wasm-runtime': 0.2.12 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': optional: true - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': optional: true - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))': + '@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0))': dependencies: - '@babel/core': 7.28.6 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) '@rolldown/pluginutils': 1.0.0-beta.53 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) transitivePeerDependencies: - supports-color - '@vitest/eslint-plugin@1.6.6(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))': + '@vitest/eslint-plugin@1.6.20(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.7.0) optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) typescript: 5.9.3 - vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.18': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - chai: 6.2.2 - tinyrainbow: 3.1.0 - optional: true - - '@vitest/mocker@4.0.18(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0))': - dependencies: - '@vitest/spy': 4.0.18 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - msw: 2.12.7(@types/node@25.0.10)(typescript@5.9.3) - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) - optional: true - - '@vitest/pretty-format@4.0.18': - dependencies: - tinyrainbow: 3.1.0 - optional: true - - '@vitest/runner@4.0.18': - dependencies: - '@vitest/utils': 4.0.18 - pathe: 2.0.3 - optional: true - - '@vitest/snapshot@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - magic-string: 0.30.21 - pathe: 2.0.3 - optional: true - - '@vitest/spy@4.0.18': - optional: true - - '@vitest/utils@4.0.18': - dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.1.0 - optional: true - '@web3-storage/multipart-parser@1.0.0': {} '@webassemblyjs/ast@1.14.1': @@ -13808,6 +13758,107 @@ snapshots: '@xtuc/long@4.2.2': {} + '@yuku-analyzer/binding-darwin-arm64@0.5.39': + optional: true + + '@yuku-analyzer/binding-darwin-x64@0.5.39': + optional: true + + '@yuku-analyzer/binding-freebsd-x64@0.5.39': + optional: true + + '@yuku-analyzer/binding-linux-arm-gnu@0.5.39': + optional: true + + '@yuku-analyzer/binding-linux-arm-musl@0.5.39': + optional: true + + '@yuku-analyzer/binding-linux-arm64-gnu@0.5.39': + optional: true + + '@yuku-analyzer/binding-linux-arm64-musl@0.5.39': + optional: true + + '@yuku-analyzer/binding-linux-x64-gnu@0.5.39': + optional: true + + '@yuku-analyzer/binding-linux-x64-musl@0.5.39': + optional: true + + '@yuku-analyzer/binding-win32-arm64@0.5.39': + optional: true + + '@yuku-analyzer/binding-win32-x64@0.5.39': + optional: true + + '@yuku-codegen/binding-darwin-arm64@0.5.39': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.5.39': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.5.39': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.5.39': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.5.39': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.5.39': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.5.39': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.5.39': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.5.39': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.5.39': + optional: true + + '@yuku-codegen/binding-win32-x64@0.5.39': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.5.39': + optional: true + + '@yuku-parser/binding-darwin-x64@0.5.39': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.5.39': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.5.39': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.5.39': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.5.39': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.5.39': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.5.39': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.5.39': + optional: true + + '@yuku-parser/binding-win32-arm64@0.5.39': + optional: true + + '@yuku-parser/binding-win32-x64@0.5.39': + optional: true + + '@yuku-toolchain/types@0.5.37': {} + '@zeit/schemas@2.36.0': {} accepts@1.3.8: @@ -13820,19 +13871,21 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-import-attributes@1.9.5(acorn@8.15.0): + acorn-import-attributes@1.9.5(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn-import-phases@1.0.4(acorn@8.17.0): dependencies: - acorn: 8.15.0 + acorn: 8.17.0 - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-jsx@5.3.2(acorn@8.17.0): dependencies: - acorn: 8.15.0 + acorn: 8.17.0 acorn-walk@8.3.5: dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} + acorn: 8.17.0 acorn@8.17.0: {} @@ -13848,26 +13901,15 @@ snapshots: agent-base@7.1.4: {} - ajv-formats@2.1.1(ajv@8.17.1): + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: - ajv: 8.17.1 - - ajv-keywords@3.5.2(ajv@6.15.0): - dependencies: - ajv: 6.15.0 + ajv: 8.20.0 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.20.0): dependencies: - ajv: 8.17.1 + ajv: 8.20.0 fast-deep-equal: 3.1.3 - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -13875,17 +13917,17 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.12.0: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -13951,11 +13993,11 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 is-string: 1.1.1 math-intrinsics: 1.1.0 @@ -13964,41 +14006,41 @@ snapshots: array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -14007,13 +14049,13 @@ snapshots: async-function@1.0.0: {} - autoprefixer@10.4.23(postcss@8.5.15): + autoprefixer@10.4.23(postcss@8.5.16): dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001766 + browserslist: 4.28.4 + caniuse-lite: 1.0.30001799 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.15 + postcss: 8.5.16 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -14022,22 +14064,17 @@ snapshots: babel-dead-code-elimination@1.0.12: dependencies: - '@babel/core': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - babel-plugin-macros@3.1.0: - dependencies: - '@babel/runtime': 7.29.7 - cosmiconfig: 7.1.0 - resolve: 1.22.12 - optional: true - balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base32-decode@1.0.0: {} base32-encode@2.0.0: @@ -14050,8 +14087,6 @@ snapshots: baseline-browser-mapping@2.10.40: {} - baseline-browser-mapping@2.9.18: {} - basic-auth@2.0.1: dependencies: safe-buffer: 5.1.2 @@ -14097,7 +14132,7 @@ snapshots: blake3-wasm@2.1.5: {} - body-parser@1.20.4: + body-parser@1.20.5: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -14107,24 +14142,24 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.3 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - body-parser@2.2.2: + body-parser@2.3.0: dependencies: bytes: 3.1.2 - content-type: 1.0.5 + content-type: 2.0.0 debug: 4.4.3 http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.1 + qs: 6.15.3 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -14141,32 +14176,28 @@ snapshots: widest-line: 4.0.1 wrap-ansi: 8.1.0 - brace-expansion@1.1.12: + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.1: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 browserslist-load-config@1.0.3: {} - browserslist-to-es-version@1.4.1: + browserslist-to-es-version@1.4.2: dependencies: - browserslist: 4.28.1 - - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.9.18 - caniuse-lite: 1.0.30001766 - electron-to-chromium: 1.5.279 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) + browserslist: 4.28.4 browserslist@4.28.4: dependencies: @@ -14199,7 +14230,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -14217,13 +14248,8 @@ snapshots: camelcase@7.0.1: {} - caniuse-lite@1.0.30001766: {} - caniuse-lite@1.0.30001799: {} - chai@6.2.2: - optional: true - chain-function@1.0.1: {} chalk-template@0.4.0: @@ -14245,7 +14271,7 @@ snapshots: chalk@5.6.2: {} - chardet@2.1.1: {} + chardet@2.2.0: {} chokidar@3.6.0: dependencies: @@ -14271,8 +14297,6 @@ snapshots: chrome-trace-event@1.0.4: {} - ci-info@3.9.0: {} - cjs-module-lexer@2.2.0: {} class-variance-authority@0.7.1: @@ -14335,7 +14359,7 @@ snapshots: commander@2.20.3: {} - comment-parser@1.4.5: {} + comment-parser@1.4.7: {} compare-versions@6.1.1: {} @@ -14357,17 +14381,15 @@ snapshots: concat-map@0.0.1: {} - concurrently@9.2.1: + concurrently@9.2.3: dependencies: chalk: 4.1.2 rxjs: 7.8.2 - shell-quote: 1.8.3 + shell-quote: 1.8.4 supports-color: 8.1.1 tree-kill: 1.2.2 yargs: 17.7.2 - confbox@0.2.2: {} - confbox@0.2.4: {} content-disposition@0.5.2: {} @@ -14376,18 +14398,18 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.1: {} + content-disposition@1.1.0: {} content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.0.7: {} cookie-signature@1.2.2: {} - cookie@0.6.0: {} - cookie@0.7.2: {} cookie@1.1.1: {} @@ -14403,19 +14425,10 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.3 - optional: true - cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -14452,9 +14465,9 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 - css-tree@3.1.0: + css-tree@3.2.1: dependencies: - mdn-data: 2.12.2 + mdn-data: 2.27.1 source-map-js: 1.2.1 css-what@6.2.2: {} @@ -14465,9 +14478,9 @@ snapshots: cssstyle@5.3.7: dependencies: - '@asamuzakjp/css-color': 4.1.1 - '@csstools/css-syntax-patches-for-csstree': 1.0.26 - css-tree: 3.1.0 + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.6(css-tree@3.2.1) + css-tree: 3.2.1 lru-cache: 11.2.5 csstype@3.2.3: {} @@ -14541,15 +14554,12 @@ snapshots: date-fns@4.1.0: {} + dayjs@1.11.21: {} + debug@2.6.9: dependencies: ms: 2.0.0 - debug@3.2.7: - dependencies: - ms: 2.1.3 - optional: true - debug@4.3.7: dependencies: ms: 2.1.3 @@ -14566,13 +14576,7 @@ snapshots: dependencies: mimic-response: 3.1.0 - dedent@1.7.1(babel-plugin-macros@3.1.0): - optionalDependencies: - babel-plugin-macros: 3.1.0 - - dedent@1.7.2(babel-plugin-macros@3.1.0): - optionalDependencies: - babel-plugin-macros: 3.1.0 + dedent@1.7.2: {} deep-eql@4.1.4: dependencies: @@ -14630,7 +14634,7 @@ snapshots: dom-helpers@3.4.0: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.7 dom-serializer@2.0.0: dependencies: @@ -14664,8 +14668,6 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.279: {} - electron-to-chromium@1.5.380: {} emoji-regex@8.0.0: {} @@ -14688,26 +14690,27 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.5: + engine.io@6.6.9: dependencies: '@types/cors': 2.8.19 '@types/node': 25.0.10 + '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.6 debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.18.3 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - enhanced-resolve@5.18.4: + enhanced-resolve@5.21.6: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.3 enhanced-resolve@5.24.1: dependencies: @@ -14723,6 +14726,8 @@ snapshots: entities@6.0.1: {} + entities@8.0.0: {} + envinfo@7.21.0: {} errno@0.1.8: @@ -14738,22 +14743,29 @@ snapshots: error-stack-parser-es@1.0.5: {} - es-abstract@1.24.1: + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 @@ -14762,7 +14774,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -14780,31 +14792,31 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 + typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-iterator-helpers@1.2.2: + es-iterator-helpers@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -14816,13 +14828,13 @@ snapshots: has-symbols: 1.1.0 internal-slot: 1.1.0 iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 + math-intrinsics: 1.1.0 es-module-lexer@1.7.0: {} es-module-lexer@2.1.0: {} - es-object-atoms@1.1.1: + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -14831,48 +14843,22 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 - es-to-primitive@1.3.0: + es-to-primitive@1.3.4: dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 - es-toolkit@1.47.1: {} - - esbuild@0.27.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 + es-toolkit@1.49.0: {} esbuild@0.27.2: optionalDependencies: @@ -14903,6 +14889,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -14911,84 +14926,74 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-import-context@0.1.9(unrs-resolver@1.11.1): + eslint-import-context@0.1.9(unrs-resolver@1.12.2): dependencies: - get-tsconfig: 4.13.0 + get-tsconfig: 4.14.0 stable-hash-x: 0.2.0 optionalDependencies: - unrs-resolver: 1.11.1 - - eslint-import-resolver-node@0.3.9: - dependencies: - debug: 3.2.7 - is-core-module: 2.16.2 - resolve: 1.22.12 - transitivePeerDependencies: - - supports-color - optional: true + unrs-resolver: 1.12.2 - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0)): dependencies: - '@typescript-eslint/types': 8.54.0 - comment-parser: 1.4.5 + '@typescript-eslint/types': 8.62.0 + comment-parser: 1.4.7 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) - eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + eslint: 9.39.2(jiti@2.7.0) + eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 - minimatch: 10.1.1 - semver: 7.7.3 + minimatch: 10.2.5 + semver: 7.8.5 stable-hash-x: 0.2.0 - unrs-resolver: 1.11.1 + unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint-import-resolver-node: 0.3.9 + '@typescript-eslint/utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - supports-color - eslint-plugin-jest-dom@5.5.0(@testing-library/dom@10.4.1)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-jest-dom@5.5.0(@testing-library/dom@10.4.1)(eslint@9.39.2(jiti@2.7.0)): dependencies: - '@babel/runtime': 7.28.6 - eslint: 9.39.2(jiti@2.6.1) + '@babel/runtime': 7.29.7 + eslint: 9.39.2(jiti@2.7.0) requireindex: 1.2.0 optionalDependencies: '@testing-library/dom': 10.4.1 - eslint-plugin-playwright@2.5.1(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-playwright@2.10.4(eslint@9.39.2(jiti@2.7.0)): dependencies: - eslint: 9.39.2(jiti@2.6.1) - globals: 16.5.0 + eslint: 9.39.2(jiti@2.7.0) + globals: 17.7.0 - eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@2.7.0)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) - eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.7.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.2.2 - eslint: 9.39.2(jiti@2.6.1) + es-iterator-helpers: 1.3.3 + eslint: 9.39.2(jiti@2.7.0) estraverse: 5.3.0 - hasown: 2.0.2 + hasown: 2.0.4 jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 + minimatch: 3.1.5 object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 - resolve: 2.0.0-next.5 + resolve: 2.0.0-next.7 semver: 6.3.1 string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-testing-library@7.15.4(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-plugin-testing-library@7.16.2(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/scope-manager': 8.62.0 + '@typescript-eslint/utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.7.0) transitivePeerDependencies: - supports-color - typescript @@ -15007,21 +15012,23 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.2(jiti@2.6.1): + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.2(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.2 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.12.6 + '@types/estree': 1.0.9 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -15040,18 +15047,18 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color espree@10.4.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -15068,11 +15075,6 @@ snapshots: estraverse@5.3.0: {} - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - optional: true - esutils@2.0.3: {} etag@1.8.1: {} @@ -15128,19 +15130,16 @@ snapshots: dependencies: homedir-polyfill: 1.0.3 - expect-type@1.4.0: - optional: true - express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 ip-address: 10.0.1 - express@4.22.1: + express@4.22.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.4 + body-parser: 1.20.5 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 @@ -15157,9 +15156,9 @@ snapshots: methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.12 + path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.14.1 + qs: 6.15.3 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -15175,8 +15174,8 @@ snapshots: express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.2 - content-disposition: 1.0.1 + body-parser: 2.3.0 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -15194,19 +15193,17 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.1 - range-parser: 1.2.1 + qs: 6.15.3 + range-parser: 1.3.0 router: 2.2.0 send: 1.2.1 serve-static: 2.2.1 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 transitivePeerDependencies: - supports-color - exsolve@1.0.8: {} - exsolve@1.1.0: {} extendable-error@0.1.7: {} @@ -15225,15 +15222,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fastq@1.20.1: dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 + reusify: 1.1.0 fdir@6.5.0(picomatch@4.0.4): optionalDependencies: @@ -15249,7 +15242,7 @@ snapshots: file-uri-to-path@1.0.0: {} - filesize@11.0.17: {} + filesize@11.0.19: {} fill-range@7.1.1: dependencies: @@ -15298,10 +15291,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.3.3: {} + flatted@3.4.2: {} for-each@0.3.5: dependencies: @@ -15318,14 +15311,14 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.29.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + framer-motion@12.42.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - motion-dom: 12.29.2 - motion-utils: 12.29.2 + motion-dom: 12.42.0 + motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) fresh@0.5.2: {} @@ -15336,7 +15329,7 @@ snapshots: fs-extra@11.3.3: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-extra@7.0.1: @@ -15359,14 +15352,17 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.8: + function.prototype.name@1.2.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 - define-properties: 1.2.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 functions-have-names: 1.2.3 - hasown: 2.0.2 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 is-callable: 1.2.7 + is-document.all: 1.0.0 functions-have-names@1.2.3: {} @@ -15381,12 +15377,12 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-nonce@1.0.1: {} @@ -15398,7 +15394,7 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-stream@6.0.1: {} @@ -15415,7 +15411,7 @@ snapshots: get-them-args@1.3.2: {} - get-tsconfig@4.13.0: + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -15429,22 +15425,20 @@ snapshots: dependencies: is-glob: 4.0.3 - glob-to-regexp@0.4.1: {} - glob@10.5.0: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@13.0.0: dependencies: - minimatch: 10.1.1 - minipass: 7.1.2 - path-scurry: 2.0.1 + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 global-modules@1.0.0: dependencies: @@ -15464,6 +15458,8 @@ snapshots: globals@16.5.0: {} + globals@17.7.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -15480,7 +15476,7 @@ snapshots: globrex@0.1.2: {} - goober@2.1.18(csstype@3.2.3): + goober@2.1.19(csstype@3.2.3): dependencies: csstype: 3.2.3 @@ -15488,7 +15484,7 @@ snapshots: graceful-fs@4.2.11: {} - graphql@16.12.0: {} + graphql@16.14.2: {} has-bigints@1.1.0: {} @@ -15510,14 +15506,9 @@ snapshots: dependencies: has-symbols: 1.1.0 - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - hasown@2.0.4: dependencies: function-bind: 1.1.2 - optional: true he@1.2.0: {} @@ -15531,9 +15522,9 @@ snapshots: hosted-git-info@2.8.9: {} - html-encoding-sniffer@6.0.0(@noble/hashes@2.0.1): + html-encoding-sniffer@6.0.0(@noble/hashes@2.2.0): dependencies: - '@exodus/bytes': 1.10.0(@noble/hashes@2.0.1) + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) transitivePeerDependencies: - '@noble/hashes' @@ -15590,7 +15581,7 @@ snapshots: transitivePeerDependencies: - supports-color - human-id@4.1.3: {} + human-id@4.2.0: {} human-signals@2.1.0: {} @@ -15619,17 +15610,24 @@ snapshots: image-size@0.5.5: optional: true - immutable@5.1.6: {} + immutable@5.1.8: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@2.0.5: + import-in-the-middle@2.0.6: + dependencies: + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + + import-in-the-middle@3.2.0: dependencies: - acorn: 8.15.0 - acorn-import-attributes: 1.9.5(acorn@8.15.0) + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) cjs-module-lexer: 2.2.0 module-details-from-path: 1.0.4 @@ -15641,16 +15639,16 @@ snapshots: ini@1.3.8: {} - input-otp@1.4.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + input-otp@1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 + hasown: 2.0.4 + side-channel: 1.1.1 intl-parse-accept-language@1.0.0: {} @@ -15660,7 +15658,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -15689,14 +15687,9 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - is-core-module@2.16.2: dependencies: hasown: 2.0.4 - optional: true is-data-view@1.0.2: dependencies: @@ -15711,6 +15704,10 @@ snapshots: is-docker@2.2.1: {} + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -15759,7 +15756,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 is-set@2.0.3: {} @@ -15790,7 +15787,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 is-unicode-supported@1.3.0: {} @@ -15851,7 +15848,7 @@ snapshots: iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 has-symbols: 1.1.0 @@ -15871,40 +15868,40 @@ snapshots: jiti@2.4.2: {} - jiti@2.6.1: {} + jiti@2.7.0: {} js-tokens@4.0.0: {} - js-yaml@3.14.2: + js-yaml@3.15.0: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.1: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 - jsdom@27.4.0(@noble/hashes@2.0.1): + jsdom@27.4.0(@noble/hashes@2.2.0): dependencies: '@acemir/cssom': 0.9.31 - '@asamuzakjp/dom-selector': 6.7.6 - '@exodus/bytes': 1.10.0(@noble/hashes@2.0.1) + '@asamuzakjp/dom-selector': 6.8.1 + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) cssstyle: 5.3.7 data-urls: 6.0.1 decimal.js: 10.6.0 - html-encoding-sniffer: 6.0.0(@noble/hashes@2.0.1) + html-encoding-sniffer: 6.0.0(@noble/hashes@2.2.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - parse5: 8.0.0 + parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.0 + tough-cookie: 6.0.1 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.19.0 + ws: 8.21.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' @@ -15932,13 +15929,13 @@ snapshots: json5@2.2.3: {} - jsonata@2.1.0: {} + jsonata@2.2.1: {} jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 - jsonfile@6.2.0: + jsonfile@6.2.1: dependencies: universalify: 2.0.1 optionalDependencies: @@ -15966,26 +15963,21 @@ snapshots: kleur@4.1.5: {} - launch-editor@2.12.0: - dependencies: - picocolors: 1.1.1 - shell-quote: 1.8.3 - launch-editor@2.14.1: dependencies: picocolors: 1.1.1 - shell-quote: 1.8.4 + shell-quote: 1.9.0 leac@0.6.0: {} - less-loader@12.3.3(@rspack/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(less@4.6.6)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)): + less-loader@12.3.3(@rspack/core@2.1.0(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23))(less@4.6.7)(webpack@5.108.1(lightningcss@1.32.0)): dependencies: - less: 4.6.6 + less: 4.6.7 optionalDependencies: - '@rspack/core': 2.0.8(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2) + '@rspack/core': 2.1.0(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(@swc/helpers@0.5.23) + webpack: 5.108.1(lightningcss@1.32.0) - less@4.6.6: + less@4.6.7: dependencies: copy-anything: 3.0.5 parse-node-version: 1.0.1 @@ -16003,54 +15995,54 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.30.2: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-arm64@1.30.2: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.30.2: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-freebsd-x64@1.30.2: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.30.2: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.30.2: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.30.2: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.30.2: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-linux-x64-musl@1.30.2: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.30.2: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.30.2: + lightningcss-win32-x64-msvc@1.32.0: optional: true - lightningcss@1.30.2: + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.30.2 - lightningcss-darwin-arm64: 1.30.2 - lightningcss-darwin-x64: 1.30.2 - lightningcss-freebsd-x64: 1.30.2 - lightningcss-linux-arm-gnueabihf: 1.30.2 - lightningcss-linux-arm64-gnu: 1.30.2 - lightningcss-linux-arm64-musl: 1.30.2 - lightningcss-linux-x64-gnu: 1.30.2 - lightningcss-linux-x64-musl: 1.30.2 - lightningcss-win32-arm64-msvc: 1.30.2 - lightningcss-win32-x64-msvc: 1.30.2 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 lines-and-columns@1.2.4: {} @@ -16087,8 +16079,6 @@ snapshots: lodash.startcase@4.4.0: {} - lodash@4.17.23: {} - lodash@4.18.1: {} log-symbols@5.1.0: @@ -16106,6 +16096,8 @@ snapshots: lru-cache@11.2.5: {} + lru-cache@11.5.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -16124,7 +16116,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.8.5 make-dir@5.1.0: optional: true @@ -16133,7 +16125,7 @@ snapshots: math-intrinsics@1.1.0: {} - mdn-data@2.12.2: {} + mdn-data@2.27.1: {} media-typer@0.3.0: {} @@ -16154,7 +16146,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime-db@1.33.0: {} @@ -16184,39 +16176,62 @@ snapshots: min-indent@1.0.1: {} - miniflare@4.20260124.0: + miniflare@4.20260625.0: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 - undici: 7.18.2 - workerd: 1.20260124.0 - ws: 8.18.0 + undici: 7.28.0 + workerd: 1.20260625.1 + ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: - bufferutil - utf-8-validate - minimatch@10.1.1: + minimatch@10.2.5: dependencies: - '@isaacs/brace-expansion': 5.0.0 + brace-expansion: 5.0.6 - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.15 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.1 minimist@1.2.8: {} - minipass@7.1.2: {} + minimizer-webpack-plugin@5.6.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.48.0 + webpack: 5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16) + optionalDependencies: + esbuild: 0.27.2 + lightningcss: 1.32.0 + postcss: 8.5.16 + optional: true + + minimizer-webpack-plugin@5.6.1(lightningcss@1.32.0)(webpack@5.108.1(lightningcss@1.32.0)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.48.0 + webpack: 5.108.1(lightningcss@1.32.0) + optionalDependencies: + lightningcss: 1.32.0 + + minipass@7.1.3: {} mkdirp-classic@0.5.3: {} module-details-from-path@1.0.4: {} - moo@0.5.2: {} + moo@0.5.3: {} morgan@1.10.1: dependencies: @@ -16228,11 +16243,11 @@ snapshots: transitivePeerDependencies: - supports-color - motion-dom@12.29.2: + motion-dom@12.42.0: dependencies: - motion-utils: 12.29.2 + motion-utils: 12.39.0 - motion-utils@12.29.2: {} + motion-utils@12.39.0: {} mri@1.2.0: {} @@ -16247,7 +16262,7 @@ snapshots: '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 cookie: 1.1.1 - graphql: 16.12.0 + graphql: 16.14.2 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -16256,10 +16271,10 @@ snapshots: rettime: 0.7.0 statuses: 2.0.2 strict-event-emitter: 0.5.1 - tough-cookie: 6.0.0 - type-fest: 5.4.2 + tough-cookie: 6.0.1 + type-fest: 5.7.0 until-async: 3.0.2 - yargs: 17.7.2 + yargs: 17.7.3 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -16267,9 +16282,7 @@ snapshots: mute-stream@2.0.0: {} - nanoid@3.3.11: {} - - nanoid@3.3.12: {} + nanoid@3.3.15: {} napi-build-utils@2.0.0: {} @@ -16280,7 +16293,7 @@ snapshots: nearley@2.20.1: dependencies: commander: 2.20.3 - moo: 0.5.2 + moo: 0.5.3 railroad-diagrams: 1.0.0 randexp: 0.4.6 @@ -16300,13 +16313,20 @@ snapshots: nice-try@1.0.5: {} - node-abi@3.87.0: + node-abi@3.92.0: dependencies: semver: 7.8.5 node-addon-api@7.1.1: optional: true + node-exports-info@1.6.2: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -16318,8 +16338,6 @@ snapshots: css-select: 5.2.2 he: 1.2.0 - node-releases@2.0.27: {} - node-releases@2.0.50: {} node-schedule@2.1.1: @@ -16336,7 +16354,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.11 + resolve: 1.22.12 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -16348,10 +16366,10 @@ snapshots: chalk: 2.4.2 cross-spawn: 6.0.6 memorystream: 0.3.1 - minimatch: 3.1.2 + minimatch: 3.1.5 pidtree: 0.3.1 read-pkg: 3.0.0 - shell-quote: 1.8.3 + shell-quote: 1.9.0 string.prototype.padend: 3.1.6 npm-run-path@4.0.1: @@ -16379,36 +16397,33 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - obug@2.1.3: - optional: true + es-object-atoms: 1.1.2 on-finished@2.3.0: dependencies: @@ -16450,7 +16465,7 @@ snapshots: is-unicode-supported: 1.3.0 log-symbols: 5.1.0 stdin-discarder: 0.1.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wcwidth: 1.0.1 outdent@0.5.0: {} @@ -16506,7 +16521,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.28.6 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -16517,9 +16532,9 @@ snapshots: parse-passwd@1.0.0: {} - parse5@8.0.0: + parse5@8.0.1: dependencies: - entities: 6.0.1 + entities: 8.0.0 parseley@0.12.1: dependencies: @@ -16545,20 +16560,20 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 - path-scurry@2.0.1: + path-scurry@2.0.2: dependencies: lru-cache: 11.2.5 - minipass: 7.1.2 + minipass: 7.1.3 - path-to-regexp@0.1.12: {} + path-to-regexp@0.1.13: {} path-to-regexp@3.3.0: {} path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.2: {} path-type@3.0.0: dependencies: @@ -16574,7 +16589,7 @@ snapshots: pg-int8@1.0.1: {} - pg-protocol@1.11.0: {} + pg-protocol@1.15.0: {} pg-types@2.2.0: dependencies: @@ -16586,12 +16601,8 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} - picomatch@2.3.2: {} - picomatch@4.0.3: {} - picomatch@4.0.4: {} pidtree@0.3.1: {} @@ -16602,12 +16613,6 @@ snapshots: pkg-pr-new@0.0.75: {} - pkg-types@2.3.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.8 - pathe: 2.0.3 - pkg-types@2.3.1: dependencies: confbox: 0.2.4 @@ -16616,22 +16621,30 @@ snapshots: playwright-core@1.58.0: {} + playwright-core@1.61.1: {} + playwright@1.58.0: dependencies: playwright-core: 1.58.0 optionalDependencies: fsevents: 2.3.2 + playwright@1.61.1: + dependencies: + playwright-core: 1.61.1 + optionalDependencies: + fsevents: 2.3.2 + pngjs@5.0.0: {} possible-typed-array-names@1.1.0: {} - postcss-nested@5.0.6(postcss@8.5.15): + postcss-nested@5.0.6(postcss@8.5.16): dependencies: - postcss: 8.5.15 - postcss-selector-parser: 6.1.2 + postcss: 8.5.16 + postcss-selector-parser: 6.1.4 - postcss-selector-parser@6.1.2: + postcss-selector-parser@6.1.4: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -16640,13 +16653,13 @@ snapshots: postcss@8.5.15: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.15 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.6: + postcss@8.5.16: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.15 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -16668,11 +16681,11 @@ snapshots: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 2.0.0 - node-abi: 3.87.0 - pump: 3.0.3 + node-abi: 3.92.0 + pump: 3.0.4 rc: 1.2.8 simple-get: 4.0.1 - tar-fs: 2.1.4 + tar-fs: 2.1.5 tunnel-agent: 0.6.0 prelude-ls@1.2.1: {} @@ -16682,7 +16695,7 @@ snapshots: jsox: 1.2.125 node-sql-parser: 5.4.0 prettier: 3.8.1 - sql-formatter: 15.7.0 + sql-formatter: 15.8.2 tslib: 2.8.1 prettier-plugin-tailwindcss@0.6.14(prettier@3.8.1): @@ -16740,7 +16753,7 @@ snapshots: prr@1.0.1: optional: true - pump@3.0.3: + pump@3.0.4: dependencies: end-of-stream: 1.4.5 once: 1.4.0 @@ -16753,9 +16766,10 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.14.1: + qs@6.15.3: dependencies: - side-channel: 1.1.0 + es-define-property: 1.0.1 + side-channel: 1.1.1 quansync@0.2.11: {} @@ -16772,6 +16786,8 @@ snapshots: range-parser@1.2.1: {} + range-parser@1.3.0: {} + raw-body@2.5.3: dependencies: bytes: 3.1.2 @@ -16793,9 +16809,9 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-d3-tree@3.6.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-d3-tree@3.6.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@bkrem/react-transition-group': 1.3.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@bkrem/react-transition-group': 1.3.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@types/d3-hierarchy': 1.1.11 clone: 2.1.2 d3-hierarchy: 1.1.9 @@ -16803,19 +16819,19 @@ snapshots: d3-shape: 1.3.7 d3-zoom: 3.0.0 dequal: 2.0.3 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) uuid: 8.3.2 - react-dom@19.2.4(react@19.2.4): + react-dom@19.2.7(react@19.2.7): dependencies: - react: 19.2.4 + react: 19.2.7 scheduler: 0.27.0 - react-hotkeys-hook@5.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-hotkeys-hook@5.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) react-is@16.13.1: {} @@ -16827,54 +16843,54 @@ snapshots: react-refresh@0.18.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.10)(react@19.2.4): + react-remove-scroll-bar@2.3.8(@types/react@19.2.10)(react@19.2.7): dependencies: - react: 19.2.4 - react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.4) + react: 19.2.7 + react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.7) tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.10 - react-remove-scroll@2.7.2(@types/react@19.2.10)(react@19.2.4): + react-remove-scroll@2.7.2(@types/react@19.2.10)(react@19.2.7): dependencies: - react: 19.2.4 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.10)(react@19.2.4) - react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.4) + react: 19.2.7 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.10)(react@19.2.7) + react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.7) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.10)(react@19.2.4) - use-sidecar: 1.1.3(@types/react@19.2.10)(react@19.2.4) + use-callback-ref: 1.3.3(@types/react@19.2.10)(react@19.2.7) + use-sidecar: 1.1.3(@types/react@19.2.10)(react@19.2.7) optionalDependencies: '@types/react': 19.2.10 - react-router-devtools@6.2.0(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(solid-js@1.9.11)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)): + react-router-devtools@6.2.1(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.7(react@19.2.7))(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(solid-js@1.9.13)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)): dependencies: - '@babel/core': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@radix-ui/react-accordion': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tanstack/devtools-client': 0.0.5 - '@tanstack/devtools-event-client': 0.4.0 - '@tanstack/devtools-vite': 0.4.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) - '@tanstack/react-devtools': 0.9.3(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.11) + '@tanstack/devtools-event-client': 0.4.4 + '@tanstack/devtools-vite': 0.4.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)) + '@tanstack/react-devtools': 0.9.13(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(csstype@3.2.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(solid-js@1.9.13) '@types/react': 19.2.10 '@types/react-dom': 19.2.3(@types/react@19.2.10) chalk: 5.6.2 clsx: 2.1.1 - framer-motion: 12.29.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - goober: 2.1.18(csstype@3.2.3) - react: 19.2.4 - react-d3-tree: 3.6.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-dom: 19.2.4(react@19.2.4) - react-hotkeys-hook: 5.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react-tooltip: 5.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + framer-motion: 12.42.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + goober: 2.1.19(csstype@3.2.3) + react: 19.2.7 + react-d3-tree: 3.6.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-dom: 19.2.7(react@19.2.7) + react-hotkeys-hook: 5.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-tooltip: 5.30.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.13 - '@rollup/rollup-darwin-arm64': 4.57.0 - '@rollup/rollup-linux-x64-gnu': 4.57.0 + '@biomejs/cli-darwin-arm64': 2.5.1 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 transitivePeerDependencies: - '@emotion/is-prop-valid' - bufferutil @@ -16883,48 +16899,48 @@ snapshots: - supports-color - utf-8-validate - react-router-dom@6.29.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router-dom@6.30.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@remix-run/router': 1.22.0 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-router: 6.29.0(react@19.2.4) + '@remix-run/router': 1.23.3 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 6.30.4(react@19.2.7) - react-router-dom@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router-dom@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-router@6.29.0(react@19.2.4): + react-router@6.30.4(react@19.2.7): dependencies: - '@remix-run/router': 1.22.0 - react: 19.2.4 + '@remix-run/router': 1.23.3 + react: 19.2.7 - react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: cookie: 1.1.1 - react: 19.2.4 + react: 19.2.7 set-cookie-parser: 2.7.2 optionalDependencies: - react-dom: 19.2.4(react@19.2.4) + react-dom: 19.2.7(react@19.2.7) - react-style-singleton@2.2.3(@types/react@19.2.10)(react@19.2.4): + react-style-singleton@2.2.3(@types/react@19.2.10)(react@19.2.7): dependencies: get-nonce: 1.0.1 - react: 19.2.4 + react: 19.2.7 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.10 - react-tooltip@5.30.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + react-tooltip@5.30.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@floating-ui/dom': 1.7.5 + '@floating-ui/dom': 1.7.6 classnames: 2.5.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - react@19.2.4: {} + react@19.2.7: {} read-pkg@3.0.0: dependencies: @@ -16935,7 +16951,7 @@ snapshots: read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 - js-yaml: 3.14.2 + js-yaml: 3.15.0 pify: 4.0.1 strip-bom: 3.0.0 @@ -16962,18 +16978,18 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -17003,18 +17019,17 @@ snapshots: remix-flat-routes@0.8.5: dependencies: fs-extra: 11.3.3 - minimatch: 10.1.1 + minimatch: 10.2.5 - remix-utils@9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(@standard-schema/spec@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): + remix-utils@9.0.0(@oslojs/crypto@1.0.1)(@oslojs/encoding@1.1.0)(intl-parse-accept-language@1.0.0)(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): dependencies: type-fest: 4.41.0 optionalDependencies: '@oslojs/crypto': 1.0.1 '@oslojs/encoding': 1.1.0 - '@standard-schema/spec': 1.1.0 intl-parse-accept-language: 1.0.0 - react: 19.2.4 - react-router: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.7 + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) require-directory@2.1.1: {} @@ -17042,29 +17057,25 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.12: dependencies: es-errors: 1.3.0 is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - optional: true resolve@1.22.8: dependencies: - is-core-module: 2.16.1 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@2.0.0-next.5: + resolve@2.0.0-next.7: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.2 + object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -17083,35 +17094,35 @@ snapshots: dependencies: glob: 10.5.0 - rollup@4.57.0: + rollup@4.62.2: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.57.0 - '@rollup/rollup-android-arm64': 4.57.0 - '@rollup/rollup-darwin-arm64': 4.57.0 - '@rollup/rollup-darwin-x64': 4.57.0 - '@rollup/rollup-freebsd-arm64': 4.57.0 - '@rollup/rollup-freebsd-x64': 4.57.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.57.0 - '@rollup/rollup-linux-arm-musleabihf': 4.57.0 - '@rollup/rollup-linux-arm64-gnu': 4.57.0 - '@rollup/rollup-linux-arm64-musl': 4.57.0 - '@rollup/rollup-linux-loong64-gnu': 4.57.0 - '@rollup/rollup-linux-loong64-musl': 4.57.0 - '@rollup/rollup-linux-ppc64-gnu': 4.57.0 - '@rollup/rollup-linux-ppc64-musl': 4.57.0 - '@rollup/rollup-linux-riscv64-gnu': 4.57.0 - '@rollup/rollup-linux-riscv64-musl': 4.57.0 - '@rollup/rollup-linux-s390x-gnu': 4.57.0 - '@rollup/rollup-linux-x64-gnu': 4.57.0 - '@rollup/rollup-linux-x64-musl': 4.57.0 - '@rollup/rollup-openbsd-x64': 4.57.0 - '@rollup/rollup-openharmony-arm64': 4.57.0 - '@rollup/rollup-win32-arm64-msvc': 4.57.0 - '@rollup/rollup-win32-ia32-msvc': 4.57.0 - '@rollup/rollup-win32-x64-gnu': 4.57.0 - '@rollup/rollup-win32-x64-msvc': 4.57.0 + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 fsevents: 2.3.3 router@2.2.0: @@ -17120,23 +17131,19 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.2 transitivePeerDependencies: - supports-color rsbuild-plugin-dts@0.22.1(@rsbuild/core@2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0))(typescript@5.9.3): dependencies: '@ast-grep/napi': 0.37.0 - '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0) + '@rsbuild/core': 2.0.15(@module-federation/runtime-tools@2.5.1)(core-js@3.47.0) optionalDependencies: typescript: 5.9.3 rslog@2.1.3: {} - rspack-plugin-virtual-module@1.0.1: - dependencies: - fs-extra: 11.3.3 - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -17145,9 +17152,9 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -17230,9 +17237,9 @@ snapshots: sass-embedded@1.100.0: dependencies: - '@bufbuild/protobuf': 2.11.0 + '@bufbuild/protobuf': 2.12.1 colorjs.io: 0.5.2 - immutable: 5.1.6 + immutable: 5.1.8 rxjs: 7.8.2 supports-color: 8.1.1 sync-child-process: 1.0.2 @@ -17260,7 +17267,7 @@ snapshots: sass@1.100.0: dependencies: chokidar: 5.0.0 - immutable: 5.1.6 + immutable: 5.1.8 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.6 @@ -17275,25 +17282,19 @@ snapshots: scheduler@0.27.0: {} - schema-utils@3.3.0: - dependencies: - '@types/json-schema': 7.0.15 - ajv: 6.15.0 - ajv-keywords: 3.5.2(ajv@6.15.0) - schema-utils@4.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) selderee@0.11.0: dependencies: @@ -17305,10 +17306,6 @@ snapshots: semver@7.6.3: {} - semver@7.7.3: {} - - semver@7.8.4: {} - semver@7.8.5: {} send@0.19.2: @@ -17340,7 +17337,7 @@ snapshots: mime-types: 3.0.2 ms: 2.1.3 on-finished: 2.4.1 - range-parser: 1.2.1 + range-parser: 1.3.0 statuses: 2.0.2 transitivePeerDependencies: - supports-color @@ -17351,12 +17348,12 @@ snapshots: seroval@1.5.4: {} - serve-handler@6.1.6: + serve-handler@6.1.7: dependencies: bytes: 3.0.0 content-disposition: 0.5.2 mime-types: 2.1.18 - minimatch: 3.1.2 + minimatch: 3.1.5 path-is-inside: 1.0.2 path-to-regexp: 3.3.0 range-parser: 1.2.0 @@ -17379,10 +17376,10 @@ snapshots: transitivePeerDependencies: - supports-color - serve@14.2.5: + serve@14.2.6: dependencies: '@zeit/schemas': 2.36.0 - ajv: 8.12.0 + ajv: 8.18.0 arg: 5.0.2 boxen: 7.0.0 chalk: 5.0.1 @@ -17390,7 +17387,7 @@ snapshots: clipboardy: 3.0.0 compression: 1.8.1 is-port-reachable: 4.0.0 - serve-handler: 6.1.6 + serve-handler: 6.1.7 update-check: 1.5.4 transitivePeerDependencies: - supports-color @@ -17421,15 +17418,15 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 setprototypeof@1.2.0: {} sharp@0.34.5: dependencies: - '@img/colour': 1.0.0 + '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.8.5 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.5 '@img/sharp-darwin-x64': 0.34.5 @@ -17470,11 +17467,11 @@ snapshots: shell-exec@1.0.2: {} - shell-quote@1.8.3: {} - shell-quote@1.8.4: {} - side-channel-list@1.0.0: + shell-quote@1.9.0: {} + + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -17494,17 +17491,14 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.1.0: + side-channel@1.1.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - siginfo@2.0.0: - optional: true - signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -17521,16 +17515,16 @@ snapshots: slash@3.0.0: {} - socket.io-adapter@2.5.6: + socket.io-adapter@2.5.8: dependencies: debug: 4.4.3 - ws: 8.18.3 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.5: + socket.io-parser@4.2.6: dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3 @@ -17543,24 +17537,24 @@ snapshots: base64id: 2.0.0 cors: 2.8.6 debug: 4.3.7 - engine.io: 6.6.5 - socket.io-adapter: 2.5.6 - socket.io-parser: 4.2.5 + engine.io: 6.6.9 + socket.io-adapter: 2.5.8 + socket.io-parser: 4.2.6 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - solid-js@1.9.11: + solid-js@1.9.13: dependencies: csstype: 3.2.3 seroval: 1.5.4 seroval-plugins: 1.5.4(seroval@1.5.4) - sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + sonner@2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) sorted-array-functions@1.3.0: {} @@ -17583,38 +17577,32 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 + spdx-license-ids: 3.0.23 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 + spdx-license-ids: 3.0.23 - spdx-license-ids@3.0.22: {} + spdx-license-ids@3.0.23: {} - spin-delay@2.0.1(react@19.2.4): + spin-delay@2.0.1(react@19.2.7): dependencies: - react: 19.2.4 + react: 19.2.7 sprintf-js@1.0.3: {} - sql-formatter@15.7.0: + sql-formatter@15.8.2: dependencies: argparse: 2.0.1 nearley: 2.20.1 stable-hash-x@0.2.0: {} - stackback@0.0.2: - optional: true - statuses@2.0.2: {} - std-env@3.10.0: - optional: true - stdin-discarder@0.1.0: dependencies: bl: 5.1.0 @@ -17626,10 +17614,10 @@ snapshots: strict-event-emitter@0.5.1: {} - string-replace-loader@3.3.0(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)): + string-replace-loader@3.3.0(webpack@5.108.1(lightningcss@1.32.0)): dependencies: schema-utils: 4.3.3 - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2) + webpack: 5.108.1(lightningcss@1.32.0) string-width@4.2.3: dependencies: @@ -17641,58 +17629,59 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string.prototype.matchall@4.0.12: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 internal-slot: 1.1.0 regexp.prototype.flags: 1.5.4 set-function-name: 2.0.2 - side-channel: 1.1.0 + side-channel: 1.1.1 string.prototype.padend@3.1.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 - string.prototype.trim@1.2.10: + string.prototype.trim@1.2.11: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 - string.prototype.trimend@1.0.9: + string.prototype.trimend@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string_decoder@1.3.0: dependencies: @@ -17702,7 +17691,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -17762,15 +17751,17 @@ snapshots: tailwindcss@4.1.18: {} + tailwindcss@4.3.1: {} + tapable@2.3.0: {} tapable@2.3.3: {} - tar-fs@2.1.4: + tar-fs@2.1.5: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 - pump: 3.0.3 + pump: 3.0.4 tar-stream: 2.2.0 tar-stream@2.2.0: @@ -17783,30 +17774,6 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.6.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.48.0 - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15) - optionalDependencies: - esbuild: 0.27.2 - lightningcss: 1.30.2 - postcss: 8.5.15 - optional: true - - terser-webpack-plugin@5.6.1(esbuild@0.27.2)(lightningcss@1.30.2)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.48.0 - webpack: 5.97.1(esbuild@0.27.2)(lightningcss@1.30.2) - optionalDependencies: - esbuild: 0.27.2 - lightningcss: 1.30.2 - terser@5.48.0: dependencies: '@jridgewell/source-map': 0.3.11 @@ -17816,17 +17783,6 @@ snapshots: text-encoder-lite@2.0.0: {} - tinybench@2.9.0: - optional: true - - tinyexec@1.2.4: - optional: true - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -17834,14 +17790,11 @@ snapshots: tinypool@1.1.1: {} - tinyrainbow@3.1.0: - optional: true - - tldts-core@7.0.19: {} + tldts-core@7.4.5: {} - tldts@7.0.19: + tldts@7.4.5: dependencies: - tldts-core: 7.0.19 + tldts-core: 7.4.5 to-data-view@2.0.0: {} @@ -17851,9 +17804,9 @@ snapshots: toidentifier@1.0.1: {} - tough-cookie@6.0.0: + tough-cookie@6.0.1: dependencies: - tldts: 7.0.19 + tldts: 7.4.5 tr46@0.0.3: {} @@ -17863,7 +17816,7 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -17876,7 +17829,7 @@ snapshots: tsx@4.21.0: dependencies: esbuild: 0.27.2 - get-tsconfig: 4.13.0 + get-tsconfig: 4.14.0 optionalDependencies: fsevents: 2.3.3 @@ -17884,8 +17837,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - turbo-stream@2.4.0: {} - turbo-stream@2.4.1: {} type-check@0.4.0: @@ -17898,7 +17849,7 @@ snapshots: type-fest@4.41.0: {} - type-fest@5.4.2: + type-fest@5.7.0: dependencies: tagged-tag: 1.0.0 @@ -17907,9 +17858,9 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: + type-is@2.1.0: dependencies: - content-type: 1.0.5 + content-type: 2.0.0 media-typer: 1.1.0 mime-types: 3.0.2 @@ -17921,7 +17872,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -17930,29 +17881,29 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.7: + typed-array-length@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.62.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.62.0(eslint@9.39.2(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -17968,13 +17919,10 @@ snapshots: undici-types@7.16.0: {} - undici-types@7.24.6: - optional: true - - undici@7.18.2: {} - undici@7.24.7: {} + undici@7.28.0: {} + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 @@ -17989,45 +17937,42 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.15.0 + acorn: 8.17.0 chokidar: 3.6.0 - webpack-sources: 3.3.3 + webpack-sources: 3.5.0 webpack-virtual-modules: 0.5.0 - unrs-resolver@1.11.1: + unrs-resolver@1.12.2: dependencies: napi-postinstall: 0.3.4 optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 - '@unrs/resolver-binding-android-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-x64': 1.11.1 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 until-async@3.0.2: {} upath@2.0.1: {} - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - update-browserslist-db@1.2.3(browserslist@4.28.4): dependencies: browserslist: 4.28.4 @@ -18043,17 +17988,17 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.2.10)(react@19.2.4): + use-callback-ref@1.3.3(@types/react@19.2.10)(react@19.2.7): dependencies: - react: 19.2.4 + react: 19.2.7 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.10 - use-sidecar@1.1.3(@types/react@19.2.10)(react@19.2.4): + use-sidecar@1.1.3(@types/react@19.2.10)(react@19.2.7): dependencies: detect-node-es: 1.1.0 - react: 19.2.4 + react: 19.2.7 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.10 @@ -18064,10 +18009,6 @@ snapshots: uuid@8.3.2: {} - valibot@1.2.0(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - valibot@1.4.2(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -18081,47 +18022,26 @@ snapshots: vary@1.1.2: {} - vite-env-only@3.0.3(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)): + vite-env-only@3.0.3(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)): dependencies: - '@babel/core': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/parser': 7.28.6 - '@babel/traverse': 7.28.6 - '@babel/types': 7.28.6 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 babel-dead-code-elimination: 1.0.12 micromatch: 4.0.8 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) - transitivePeerDependencies: - - supports-color - - vite-node@3.2.4(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0): + vite-node@3.2.4(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) transitivePeerDependencies: - '@types/node' - jiti @@ -18136,95 +18056,34 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@6.0.5(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)): + vite-tsconfig-paths@6.1.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) + vite: 7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0) transitivePeerDependencies: - supports-color - typescript - vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0): + vite@7.3.1(@types/node@25.0.10)(jiti@2.7.0)(less@4.6.7)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0): dependencies: esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.0 - tinyglobby: 0.2.15 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.16 + rollup: 4.62.2 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.0.10 fsevents: 2.3.3 - jiti: 2.6.1 - less: 4.6.6 - lightningcss: 1.30.2 - sass: 1.100.0 - sass-embedded: 1.100.0 - terser: 5.48.0 - tsx: 4.21.0 - yaml: 2.7.0 - - vite@7.3.1(@types/node@25.9.4)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0): - dependencies: - esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.57.0 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 25.9.4 - fsevents: 2.3.3 - jiti: 2.6.1 - less: 4.6.6 - lightningcss: 1.30.2 + jiti: 2.7.0 + less: 4.6.7 + lightningcss: 1.32.0 sass: 1.100.0 sass-embedded: 1.100.0 terser: 5.48.0 tsx: 4.21.0 - yaml: 2.7.0 - - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@27.4.0(@noble/hashes@2.0.1))(less@4.6.6)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0): - dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(msw@2.12.7(@types/node@25.0.10)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 - expect-type: 1.4.0 - magic-string: 0.30.21 - obug: 2.1.3 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.2.4 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(tsx@4.21.0)(yaml@2.7.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@opentelemetry/api': 1.9.0 - '@types/node': 25.0.10 - jsdom: 27.4.0(@noble/hashes@2.0.1) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - optional: true w3c-xmlserializer@5.0.0: dependencies: @@ -18246,35 +18105,32 @@ snapshots: webidl-conversions@8.0.1: {} - webpack-sources@3.3.3: {} - webpack-sources@3.5.0: {} webpack-virtual-modules@0.5.0: {} - webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2): + webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16): dependencies: - '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.17.0 + acorn-import-phases: 1.0.4(acorn@8.17.0) browserslist: 4.28.4 chrome-trace-event: 1.0.4 enhanced-resolve: 5.24.1 - es-module-lexer: 1.7.0 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 - glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 loader-runner: 4.3.2 - mime-types: 2.1.35 + mime-db: 1.54.0 + minimizer-webpack-plugin: 5.6.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)(webpack@5.108.1(esbuild@0.27.2)(lightningcss@1.32.0)(postcss@8.5.16)) neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(esbuild@0.27.2)(lightningcss@1.30.2)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)) watchpack: 2.5.2 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -18290,30 +18146,30 @@ snapshots: - lightningcss - postcss - uglify-js + optional: true - webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15): + webpack@5.108.1(lightningcss@1.32.0): dependencies: - '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.17.0 + acorn-import-phases: 1.0.4(acorn@8.17.0) browserslist: 4.28.4 chrome-trace-event: 1.0.4 enhanced-resolve: 5.24.1 - es-module-lexer: 1.7.0 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 - glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 loader-runner: 4.3.2 - mime-types: 2.1.35 + mime-db: 1.54.0 + minimizer-webpack-plugin: 5.6.1(lightningcss@1.32.0)(webpack@5.108.1(lightningcss@1.32.0)) neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.3 tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)(webpack@5.97.1(esbuild@0.27.2)(lightningcss@1.30.2)(postcss@8.5.15)) watchpack: 2.5.2 webpack-sources: 3.5.0 transitivePeerDependencies: @@ -18329,7 +18185,6 @@ snapshots: - lightningcss - postcss - uglify-js - optional: true whatwg-mimetype@4.0.0: {} @@ -18356,7 +18211,7 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 - function.prototype.name: 1.1.8 + function.prototype.name: 1.2.0 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 @@ -18367,7 +18222,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.20 + which-typed-array: 1.1.22 which-collection@1.0.2: dependencies: @@ -18378,10 +18233,10 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.20: + which-typed-array@1.1.22: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -18396,38 +18251,32 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - optional: true - widest-line@4.0.1: dependencies: string-width: 5.1.2 word-wrap@1.2.5: {} - workerd@1.20260124.0: + workerd@1.20260625.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260124.0 - '@cloudflare/workerd-darwin-arm64': 1.20260124.0 - '@cloudflare/workerd-linux-64': 1.20260124.0 - '@cloudflare/workerd-linux-arm64': 1.20260124.0 - '@cloudflare/workerd-windows-64': 1.20260124.0 + '@cloudflare/workerd-darwin-64': 1.20260625.1 + '@cloudflare/workerd-darwin-arm64': 1.20260625.1 + '@cloudflare/workerd-linux-64': 1.20260625.1 + '@cloudflare/workerd-linux-arm64': 1.20260625.1 + '@cloudflare/workerd-windows-64': 1.20260625.1 - wrangler@4.61.0(@cloudflare/workers-types@4.20260127.0): + wrangler@4.105.0(@cloudflare/workers-types@4.20260628.1): dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.11.0(unenv@2.0.0-rc.24)(workerd@1.20260124.0) + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260625.1) blake3-wasm: 2.1.5 - esbuild: 0.27.0 - miniflare: 4.20260124.0 + esbuild: 0.28.1 + miniflare: 4.20260625.0 path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260124.0 + workerd: 1.20260625.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260127.0 + '@cloudflare/workers-types': 4.20260628.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil @@ -18449,16 +18298,10 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {} - ws@8.18.0: {} - - ws@8.18.3: {} - - ws@8.19.0: {} - ws@8.21.0: {} xml-name-validator@5.0.0: {} @@ -18473,12 +18316,6 @@ snapshots: yallist@3.1.1: {} - yaml@1.10.3: - optional: true - - yaml@2.7.0: - optional: true - yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 @@ -18510,6 +18347,16 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@17.7.3: + 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 + yocto-queue@0.1.0: {} yoctocolors-cjs@2.1.3: {} @@ -18525,8 +18372,56 @@ snapshots: dependencies: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.14 + '@speed-highlight/core': 1.2.17 cookie: 1.1.1 youch-core: 0.3.3 + yuku-analyzer@0.5.39: + dependencies: + '@yuku-toolchain/types': 0.5.37 + optionalDependencies: + '@yuku-analyzer/binding-darwin-arm64': 0.5.39 + '@yuku-analyzer/binding-darwin-x64': 0.5.39 + '@yuku-analyzer/binding-freebsd-x64': 0.5.39 + '@yuku-analyzer/binding-linux-arm-gnu': 0.5.39 + '@yuku-analyzer/binding-linux-arm-musl': 0.5.39 + '@yuku-analyzer/binding-linux-arm64-gnu': 0.5.39 + '@yuku-analyzer/binding-linux-arm64-musl': 0.5.39 + '@yuku-analyzer/binding-linux-x64-gnu': 0.5.39 + '@yuku-analyzer/binding-linux-x64-musl': 0.5.39 + '@yuku-analyzer/binding-win32-arm64': 0.5.39 + '@yuku-analyzer/binding-win32-x64': 0.5.39 + + yuku-codegen@0.5.39: + dependencies: + '@yuku-toolchain/types': 0.5.37 + optionalDependencies: + '@yuku-codegen/binding-darwin-arm64': 0.5.39 + '@yuku-codegen/binding-darwin-x64': 0.5.39 + '@yuku-codegen/binding-freebsd-x64': 0.5.39 + '@yuku-codegen/binding-linux-arm-gnu': 0.5.39 + '@yuku-codegen/binding-linux-arm-musl': 0.5.39 + '@yuku-codegen/binding-linux-arm64-gnu': 0.5.39 + '@yuku-codegen/binding-linux-arm64-musl': 0.5.39 + '@yuku-codegen/binding-linux-x64-gnu': 0.5.39 + '@yuku-codegen/binding-linux-x64-musl': 0.5.39 + '@yuku-codegen/binding-win32-arm64': 0.5.39 + '@yuku-codegen/binding-win32-x64': 0.5.39 + + yuku-parser@0.5.39: + dependencies: + '@yuku-toolchain/types': 0.5.37 + optionalDependencies: + '@yuku-parser/binding-darwin-arm64': 0.5.39 + '@yuku-parser/binding-darwin-x64': 0.5.39 + '@yuku-parser/binding-freebsd-x64': 0.5.39 + '@yuku-parser/binding-linux-arm-gnu': 0.5.39 + '@yuku-parser/binding-linux-arm-musl': 0.5.39 + '@yuku-parser/binding-linux-arm64-gnu': 0.5.39 + '@yuku-parser/binding-linux-arm64-musl': 0.5.39 + '@yuku-parser/binding-linux-x64-gnu': 0.5.39 + '@yuku-parser/binding-linux-x64-musl': 0.5.39 + '@yuku-parser/binding-win32-arm64': 0.5.39 + '@yuku-parser/binding-win32-x64': 0.5.39 + zod@3.25.76: {} diff --git a/rslib.config.ts b/rslib.config.ts index 2966d22..566bf25 100644 --- a/rslib.config.ts +++ b/rslib.config.ts @@ -5,14 +5,30 @@ import { } from '@rsbuild/config/rslib.config.js'; import { defineConfig } from '@rslib/core'; const config = defineConfig({ - source: { - entry: { - index: './src/index.ts', - 'templates/entry.server': './src/templates/entry.server.tsx', - 'templates/entry.client': './src/templates/entry.client.tsx', + lib: [ + { + ...esmConfig, + source: { + entry: { + index: './src/index.ts', + 'parallel-route-transform-worker': + './src/parallel-route-transform-worker.ts', + 'templates/entry.server': './src/templates/entry.server.tsx', + 'templates/entry.client': './src/templates/entry.client.tsx', + }, + }, }, - }, - lib: [esmConfig, cjsConfig], + { + ...cjsConfig, + source: { + entry: { + index: './src/index.ts', + 'templates/entry.server': './src/templates/entry.server.tsx', + 'templates/entry.client': './src/templates/entry.client.tsx', + }, + }, + }, + ], tools: { rspack: { externals: [ diff --git a/scripts/bench-builds.mjs b/scripts/bench-builds.mjs index b356798..07cc1ba 100644 --- a/scripts/bench-builds.mjs +++ b/scripts/bench-builds.mjs @@ -85,6 +85,51 @@ const profiles = { ], }; +const DEFAULT_RESERVED_CORES = 2; +const MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES = 4; +const DEFAULT_ROUTE_TRANSFORM_WORKER_LIMIT = 4; +const SMALL_MACHINE_ROUTE_TRANSFORM_WORKER_LIMIT = 1; +const SMALL_MACHINE_CPU_COUNT = 4; + +const getAvailableCpuCount = () => + typeof os.availableParallelism === 'function' + ? os.availableParallelism() + : os.cpus().length; + +const getDefaultConcurrency = cpuCount => + Math.max(0, Math.floor(cpuCount) - DEFAULT_RESERVED_CORES); + +const getDefaultRouteTransformWorkerCount = cpuCount => { + const workerCount = getDefaultConcurrency(cpuCount); + if (workerCount < 1) { + return 0; + } + const workerLimit = + cpuCount <= SMALL_MACHINE_CPU_COUNT + ? SMALL_MACHINE_ROUTE_TRANSFORM_WORKER_LIMIT + : DEFAULT_ROUTE_TRANSFORM_WORKER_LIMIT; + return Math.min(workerCount, workerLimit); +}; + +const resolveConcurrency = parallelTransforms => { + const availableCpuCount = getAvailableCpuCount(); + const spareCoreCount = getDefaultConcurrency(availableCpuCount); + return { + availableCpuCount, + reservedCoreCount: DEFAULT_RESERVED_CORES, + spareCoreCount, + minParallelEnvironmentBuildSpareCores: + MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES, + parallelEnvironmentBuilds: + spareCoreCount >= MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES, + routeTransformWorkers: + parallelTransforms === false + ? 0 + : (parallelTransforms ?? + getDefaultRouteTransformWorkerCount(availableCpuCount)), + }; +}; + const parseArgs = argv => { const { values } = parseCliArgs({ args: argv, @@ -125,16 +170,13 @@ const parseArgs = argv => { if (value === 'auto') { return undefined; } - if (value === 'true' || value === '1') { - return true; - } - const maxWorkers = Number(value); - if (!Number.isInteger(maxWorkers) || maxWorkers < 1) { + const workerCount = Number(value); + if (!Number.isInteger(workerCount) || workerCount < 1) { throw new Error( - '--parallel-transforms must be true, false, auto, or a positive integer.' + '--parallel-transforms must be false, auto, or a positive integer.' ); } - return { maxWorkers }; + return workerCount; }; const args = { @@ -376,6 +418,7 @@ const runDevServerUntilReady = async ({ let stdout = ''; let stderr = ''; let ready = false; + let readyWallMs = null; let readyMs = null; let routePhasePending = false; let routeTotalMs = null; @@ -405,7 +448,7 @@ const runDevServerUntilReady = async ({ signal, stdout, stderr, - wallMs: performance.now() - startedAt, + wallMs: readyWallMs ?? performance.now() - startedAt, readyMs, routeTotalMs, routeRequests, @@ -443,7 +486,7 @@ const runDevServerUntilReady = async ({ }; const handleReady = async () => { - readyMs = performance.now() - startedAt; + readyMs = readyWallMs ?? performance.now() - startedAt; if (devRoutePaths.length > 0) { routePhasePending = true; const routeStartedAt = performance.now(); @@ -473,6 +516,7 @@ const runDevServerUntilReady = async ({ } if ([...requiredReady].every(environment => seenReady.has(environment))) { ready = true; + readyWallMs = performance.now() - startedAt; void handleReady(); } }; @@ -663,6 +707,7 @@ const formatRss = value => value == null ? '-' : `${Math.round(value / 1024)} MB`; const renderMarkdown = result => { + const concurrency = result.concurrency; const lines = [ '# Rsbuild React Router Benchmark Baseline', '', @@ -675,6 +720,9 @@ const renderMarkdown = result => { `- Mode: ${result.mode}`, `- Iterations: ${result.iterations}`, `- Warmup: ${result.warmup}`, + `- Available CPUs: ${concurrency.availableCpuCount}`, + `- Spare cores: ${concurrency.spareCoreCount}`, + `- Parallel environment builds: ${String(concurrency.parallelEnvironmentBuilds)}`, ...(result.mode === 'dev' ? [ `- Dev routes: ${result.devRoutes}`, @@ -682,6 +730,7 @@ const renderMarkdown = result => { ] : []), `- Parallel transforms: ${formatParallelTransforms(result.parallelTransforms)}`, + `- Resolved route transform workers: ${concurrency.routeTransformWorkers}`, `- Plugin performance logging: ${String(result.logPerformance)}`, `- Rspack profile: ${result.rspackProfile ?? 'false'}`, ...(result.rspackTraceOutput @@ -834,10 +883,7 @@ const formatParallelTransforms = parallelTransforms => { if (!parallelTransforms) { return 'false'; } - if (parallelTransforms === true) { - return 'true'; - } - return `maxWorkers=${parallelTransforms.maxWorkers}`; + return `workers=${parallelTransforms}`; }; const git = async args => { @@ -937,6 +983,7 @@ const resolveRspackTraceOutput = async ({ const main = async () => { const args = parseArgs(process.argv.slice(2)); + const concurrency = resolveConcurrency(args.parallelTransforms); const useTime = await hasGnuTime(); const outputPaths = resolveOutputPaths(args); const pluginImportPath = pathToFileURL( @@ -1120,6 +1167,7 @@ const main = async () => { fixture: fixtureResult.fixture, fixtureStats: fixtureResult.stats ?? null, parallelTransforms: args.parallelTransforms, + resolvedRouteTransformWorkers: concurrency.routeTransformWorkers, devRoutePaths, cwd: path.relative(rootDir, fixtureRoot), command: @@ -1149,6 +1197,7 @@ const main = async () => { warmup: args.warmup, clean: args.clean, logPerformance: args.logPerformance, + concurrency, devRoutes: args.mode === 'dev' ? args.devRoutes : null, devRouteTimeoutMs: args.mode === 'dev' ? args.devRouteTimeoutMs : null, parallelTransforms: args.parallelTransforms, diff --git a/scripts/benchmark/fixture.mjs b/scripts/benchmark/fixture.mjs index f0f43ee..c4b1cae 100644 --- a/scripts/benchmark/fixture.mjs +++ b/scripts/benchmark/fixture.mjs @@ -244,12 +244,7 @@ const renderParallelTransformsOption = parallelTransforms => { if (parallelTransforms === false) { return [` parallelTransforms: false,`]; } - if (parallelTransforms === true) { - return [` parallelTransforms: true,`]; - } - return [ - ` parallelTransforms: { maxWorkers: ${parallelTransforms.maxWorkers} },`, - ]; + return [` parallelTransforms: ${parallelTransforms},`]; }; const createRsbuildConfig = ({ diff --git a/scripts/compare-benchmarks.mjs b/scripts/compare-benchmarks.mjs index 89e663f..ca686dd 100644 --- a/scripts/compare-benchmarks.mjs +++ b/scripts/compare-benchmarks.mjs @@ -9,18 +9,28 @@ const { values } = parseArgs({ before: { type: 'string' }, after: { type: 'string' }, benchmark: { type: 'string', default: 'synthetic-256-ssr-esm-split' }, + operations: { + type: 'string', + default: 'route:chunk,route:client-entry,route:split-exports', + }, }, }); if (!values.before || !values.after) { throw new Error( - 'Usage: node scripts/compare-benchmarks.mjs --before --after [--benchmark ]' + 'Usage: node scripts/compare-benchmarks.mjs --before --after [--benchmark ] [--operations op,op]' ); } const readJson = async file => JSON.parse(await readFile(file, 'utf8')); const before = await readJson(values.before); const after = await readJson(values.after); +const operations = new Set( + values.operations + .split(',') + .map(value => value.trim()) + .filter(Boolean) +); const findBenchmark = (result, id) => { const benchmark = result.benchmarks?.find(item => item.id === id); @@ -35,6 +45,19 @@ const findBenchmark = (result, id) => { const metric = (benchmark, path) => path.split('.').reduce((value, key) => value?.[key], benchmark); +const operationMetric = (benchmark, operation, key) => { + const matches = + benchmark.pluginOperations?.filter(item => item.operation === operation) ?? + []; + const values = matches + .map(item => item[key]) + .filter(value => typeof value === 'number'); + if (values.length === 0) { + return null; + } + return values.reduce((sum, value) => sum + value, 0); +}; + const percentDelta = (beforeValue, afterValue) => { if (beforeValue == null || afterValue == null || beforeValue === 0) { return '-'; @@ -42,6 +65,7 @@ const percentDelta = (beforeValue, afterValue) => { return `${(((afterValue - beforeValue) / beforeValue) * 100).toFixed(1)}%`; }; +const formatNumber = value => (value == null ? '-' : value.toFixed(1)); const formatMs = value => value == null ? '-' : `${(value / 1000).toFixed(2)}s`; const formatKb = value => @@ -81,6 +105,23 @@ const rows = [ }, ]; +for (const operation of operations) { + rows.push( + { + label: `${operation} totalMs`, + before: operationMetric(beforeBenchmark, operation, 'totalMs'), + after: operationMetric(afterBenchmark, operation, 'totalMs'), + format: formatNumber, + }, + { + label: `${operation} wallMs`, + before: operationMetric(beforeBenchmark, operation, 'wallMs'), + after: operationMetric(afterBenchmark, operation, 'wallMs'), + format: formatNumber, + } + ); +} + console.log(`Benchmark comparison: ${values.benchmark}`); console.log(''); console.log('| Metric | Before | After | Delta |'); diff --git a/scripts/report-benchmark-ci.mjs b/scripts/report-benchmark-ci.mjs index ade5c75..e6f2ccc 100644 --- a/scripts/report-benchmark-ci.mjs +++ b/scripts/report-benchmark-ci.mjs @@ -67,6 +67,15 @@ const indexBenchmarks = result => const base = await readJson(values.base); const head = await readJson(values.head); +const baseMode = base.mode ?? 'build'; +const headMode = head.mode ?? 'build'; + +if (baseMode !== headMode) { + throw new Error( + `Cannot compare benchmark results with different modes: base=${baseMode}, head=${headMode}.` + ); +} + const baseBenchmarks = indexBenchmarks(base); const headBenchmarks = indexBenchmarks(head); const benchmarkIds = [ @@ -148,7 +157,7 @@ const report = { }, runUrl: values['run-url'] ?? null, profile: head.profile ?? base.profile ?? null, - mode: head.mode ?? base.mode ?? 'build', + mode: headMode, iterations: head.iterations ?? base.iterations ?? null, warmup: head.warmup ?? base.warmup ?? null, summary, diff --git a/scripts/test-package-interop.mjs b/scripts/test-package-interop.mjs new file mode 100644 index 0000000..6b1fcdc --- /dev/null +++ b/scripts/test-package-interop.mjs @@ -0,0 +1,117 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +const require = createRequire(import.meta.url); +const execFileAsync = promisify(execFile); +const esm = await import('../dist/index.js'); +const commonjs = require('../dist/index.cjs'); +const packageRoot = fileURLToPath(new URL('..', import.meta.url)); +const build = { + entry: { module: { default: () => new Response() } }, + routes: {}, + assets: { routes: {}, version: 'interop' }, + assetsBuildDirectory: '/app/build/client', + basename: '/', + future: {}, + isSpaMode: false, + prerender: [], + publicPath: '/', + routeDiscovery: { mode: 'initial' }, + ssr: true, +}; + +const collect = hooks => hook => hooks.push(hook); +const noop = () => undefined; + +async function verifyRegistration(writer, reader) { + const starts = []; + const closes = []; + const api = { + context: { action: 'dev', rootPath: process.cwd() }, + logger: { info: noop, warn: noop, error: noop }, + getNormalizedConfig: () => ({}), + modifyRsbuildConfig: noop, + modifyEnvironmentConfig: noop, + onBeforeBuild: noop, + onBeforeStartDevServer: collect(starts), + onCloseDevServer: collect(closes), + onCloseBuild: noop, + onAfterEnvironmentCompile: noop, + onAfterBuild: noop, + processAssets: noop, + transform: noop, + onBeforeDevCompile: noop, + onAfterCreateCompiler: noop, + onAfterDevCompile: noop, + }; + await writer.pluginReactRouter({ customServer: true }).setup(api); + + const startHook = starts.find(hook => hook.order === 'pre'); + const closeHook = closes.find(hook => hook.order === 'pre'); + assert(startHook, 'Expected a pre dev-server start hook'); + assert(closeHook, 'Expected a pre dev-server close hook'); + const start = startHook.handler; + const server = { + close: async () => undefined, + environments: { node: { loadBundle: async () => build } }, + sockWrite: noop, + }; + await start({ environments: {}, server }); + + const pending = reader.loadReactRouterServerBuild(server); + for (const close of closes) { + if (typeof close === 'function') { + await close(); + } else { + await close.handler?.(); + } + } + await assert.rejects(pending, /closed before a React Router build was ready/); + await assert.rejects( + reader.loadReactRouterServerBuild(server), + /not registered/ + ); +} + +async function verifyPackIncludesOriginalSource() { + const { stdout } = await execFileAsync( + 'npm', + ['pack', '--dry-run', '--json'], + { + cwd: packageRoot, + } + ); + const [pack] = JSON.parse(stdout); + const files = new Set(pack.files.map(file => file.path)); + + assert( + files.has('src/index.ts'), + 'Expected npm package to include src/index.ts' + ); + assert( + files.has('src/templates/entry.client.tsx'), + 'Expected npm package to include source templates' + ); +} + +await verifyPackIncludesOriginalSource(); + +process.chdir( + fileURLToPath(new URL('../tests/fixtures/dev-runtime/', import.meta.url)) +); +await verifyRegistration(esm, commonjs); +await verifyRegistration(commonjs, esm); + +assert.deepEqual( + await esm.resolveReactRouterServerBuild({ default: build }), + build +); +assert.deepEqual( + await commonjs.resolveReactRouterServerBuild({ default: build }), + build +); + +console.log('ESM and CommonJS package entrypoints share runtime state.'); diff --git a/src/babel.ts b/src/babel.ts deleted file mode 100644 index 4c52d9c..0000000 --- a/src/babel.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { types as Babel } from '@babel/core'; -import generatorPkg from '@babel/generator'; -import { type ParseResult, parse } from '@babel/parser'; -/* eslint-disable @typescript-eslint/consistent-type-imports */ -import type { NodePath } from '@babel/traverse'; -import traversePkg from '@babel/traverse'; -import * as t from '@babel/types'; - -// Babel packages are CommonJS. Depending on the bundler/runtime interop mode, -// their "default" may either be the exported function or a module namespace. -// We normalize to always get the callable function. -const traverse: typeof import('@babel/traverse').default = - (traversePkg as any).default ?? (traversePkg as any); -const generate: typeof import('@babel/generator').default = - (generatorPkg as any).default ?? (generatorPkg as any); - -export { traverse, generate, parse, t }; -export type { Babel, NodePath, ParseResult }; diff --git a/src/bounded-cache.ts b/src/bounded-cache.ts new file mode 100644 index 0000000..339d8cf --- /dev/null +++ b/src/bounded-cache.ts @@ -0,0 +1,18 @@ +export const setBoundedCacheEntry = ( + cache: Map, + key: Key, + value: Value, + maxEntries: number +): void => { + if (maxEntries <= 0) { + cache.clear(); + return; + } + if (!cache.has(key) && cache.size >= maxEntries) { + const oldestEntry = cache.keys().next(); + if (!oldestEntry.done) { + cache.delete(oldestEntry.value); + } + } + cache.set(key, value); +}; diff --git a/src/build-manifest.ts b/src/build-manifest.ts index 1cbdbae..5c49bab 100644 --- a/src/build-manifest.ts +++ b/src/build-manifest.ts @@ -147,7 +147,8 @@ export const getBuildManifest = async ({ }; export const getRoutesByServerBundleId = ( - buildManifest: BuildManifest | undefined + buildManifest: BuildManifest | undefined, + sourceRoutes: Record ): Record> => { if (!buildManifest || !('routeIdToServerBundleId' in buildManifest)) { return {}; @@ -158,7 +159,7 @@ export const getRoutesByServerBundleId = ( buildManifest.routeIdToServerBundleId )) { routesByServerBundleId[serverBundleId] ??= {}; - const branch = getRouteBranch(buildManifest.routes, routeId); + const branch = getRouteBranch(sourceRoutes, routeId); for (const route of branch) { routesByServerBundleId[serverBundleId][route.id] = route; } diff --git a/src/build-output-transforms.ts b/src/build-output-transforms.ts new file mode 100644 index 0000000..c06bae2 --- /dev/null +++ b/src/build-output-transforms.ts @@ -0,0 +1,273 @@ +import type { RsbuildPluginAPI, TransformHandler } from '@rsbuild/core'; +import jsesc from 'jsesc'; +import { relative } from 'pathe'; +import { PLUGIN_NAME } from './constants.js'; +import { + getReactRouterManifestForDev, + type ReactRouterManifestStats, +} from './manifest.js'; +import type { RouteTransformExecutor } from './parallel-route-transforms.js'; +import type { ReactRouterPerformanceProfiler } from './performance.js'; +import { createBundlerRouteExportResolver } from './route-export-resolution.js'; +import type { RouteChunkConfig } from './route-chunks.js'; +import type { PluginOptions, Route } from './types.js'; +import { isSourceMapEnabled } from './warnings/warn-on-client-source-maps.js'; + +type ReactRouterManifest = Awaited< + ReturnType +>; + +type RegisterBuildOutputTransformsOptions = { + api: RsbuildPluginAPI; + resolvedServerOutput: 'module' | 'commonjs'; + performanceProfiler: ReactRouterPerformanceProfiler; + getLatestServerManifest: () => ReactRouterManifest | null; + getLatestServerManifestByBundleId: ( + bundleId: string + ) => ReactRouterManifest | undefined; + routes: Record; + pluginOptions: PluginOptions; + getClientStats: () => ReactRouterManifestStats | undefined; + appDirectory: string; + getAssetPrefix: () => string; + routeChunkOptions: Parameters[5]; + routeTransformExecutor: RouteTransformExecutor; + routeByFilePath: Map; + routeChunkConfig: RouteChunkConfig; + isBuild: boolean; + splitRouteModules: boolean; + ssr: boolean; + isSpaMode: boolean; + rootRoutePath: string; +}; + +export const registerBuildOutputTransforms = ({ + api, + resolvedServerOutput, + performanceProfiler, + getLatestServerManifest, + getLatestServerManifestByBundleId, + routes, + pluginOptions, + getClientStats, + appDirectory, + getAssetPrefix, + routeChunkOptions, + routeTransformExecutor, + routeByFilePath, + routeChunkConfig, + isBuild, + splitRouteModules, + ssr, + isSpaMode, + rootRoutePath, +}: RegisterBuildOutputTransformsOptions): void => { + const transformRouteModule = async (args: Parameters[0]) => + performanceProfiler.record( + args.environment?.name, + 'route:module', + args.resource, + async () => + routeTransformExecutor.run({ + kind: 'routeModule', + code: args.code, + resource: args.resource, + resourcePath: args.resourcePath, + environmentName: args.environment.name, + sourceMaps: isSourceMapEnabled( + args.environment.config.output.sourceMap + ), + ssr, + isBuild, + isSpaMode, + rootRoutePath, + }) + ); + + api.processAssets( + { stage: 'additional', targets: ['node'] }, + ({ sources, compilation }) => { + const packageJsonPath = 'package.json'; + const source = new sources.RawSource( + `{"type": "${resolvedServerOutput}"}` + ); + + if (compilation.getAsset(packageJsonPath)) { + compilation.updateAsset(packageJsonPath, source); + } else { + compilation.emitAsset(packageJsonPath, source); + } + } + ); + + api.transform( + { + test: /virtual\/react-router\/(browser|server)-manifest/, + }, + async args => + performanceProfiler.record( + args.environment?.name, + 'manifest:transform', + args.resource, + async () => { + if (args.environment.name === 'web') { + return { + code: `window.__reactRouterManifest = "PLACEHOLDER";`, + }; + } + + const bundleMatch = args.resource.match( + /virtual\/react-router\/server-manifest(?:-([^?]+))?/ + ); + const bundleId = bundleMatch?.[1]?.replace(/\.js$/, ''); + const latestServerManifest = getLatestServerManifest(); + const manifest = + (latestServerManifest + ? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ?? + latestServerManifest) + : null) ?? + (await getReactRouterManifestForDev( + routes, + pluginOptions, + getClientStats(), + appDirectory, + getAssetPrefix(), + routeChunkOptions + )); + return { + code: `export default ${jsesc(manifest, { es6: true })};`, + }; + } + ) + ); + + api.transform( + { + resourceQuery: /__react-router-build-client-route/, + }, + async args => + performanceProfiler.record( + args.environment?.name, + 'route:client-entry', + args.resource, + async () => + routeTransformExecutor.run({ + kind: 'routeClientEntry', + code: args.code, + resourcePath: args.resourcePath, + environmentName: args.environment?.name, + isBuild, + routeChunkConfig, + }) + ) + ); + + api.transform( + { + resourceQuery: /route-chunk=/, + environments: ['web'], + }, + async args => + performanceProfiler.record( + args.environment?.name, + 'route:chunk', + args.resource, + async () => + routeTransformExecutor.run({ + kind: 'routeChunk', + code: args.code, + resource: args.resource, + resourcePath: args.resourcePath, + isBuild, + routeChunkConfig, + }) + ) + ); + + if (isBuild && splitRouteModules) { + api.transform( + { + test: path => routeByFilePath.has(path), + resourceQuery: { + not: /__react-router-build-client-route|react-router-route|route-chunk=/, + }, + environments: ['web'], + }, + async args => + performanceProfiler.record( + args.environment?.name, + 'route:split-exports', + args.resource, + async () => + routeTransformExecutor.run({ + kind: 'splitRouteExports', + code: args.code, + resourcePath: args.resourcePath, + routeChunkConfig, + }) + ) + ); + } + + api.transform( + { + test: /[\\/]\.server[\\/]|\.server(\.[cm]?[jt]sx?)?$/, + environments: ['web'], + }, + async args => + performanceProfiler.record( + args.environment?.name, + 'module:server-only-guard', + args.resource, + async () => { + const relativePath = relative(process.cwd(), args.resourcePath); + throw new Error( + `[${PLUGIN_NAME}] Server-only module referenced by client: ${relativePath}` + ); + } + ) + ); + + api.transform( + { + test: /[\\/]\.client[\\/]|\.client(\.[cm]?[jt]sx?)?$/, + environments: ['node'], + }, + async args => + performanceProfiler.record( + args.environment?.name, + 'module:client-only-stub', + args.resource, + async () => { + return routeTransformExecutor.run({ + kind: 'clientOnlyStub', + code: args.code, + resourcePath: args.resourcePath, + resolveExportAllModule: + typeof args.resolve === 'function' + ? createBundlerRouteExportResolver(args.resolve) + : undefined, + }); + } + ) + ); + + api.transform( + { + resourceQuery: /\?react-router-route/, + order: 'post', + }, + transformRouteModule + ); + + api.transform( + { + test: path => routeByFilePath.has(path), + resourceQuery: { + not: /__react-router-build-client-route|react-router-route|route-chunk=/, + }, + order: 'post', + }, + transformRouteModule + ); +}; diff --git a/src/concurrency.ts b/src/concurrency.ts new file mode 100644 index 0000000..bf51f8e --- /dev/null +++ b/src/concurrency.ts @@ -0,0 +1,34 @@ +import { availableParallelism, cpus } from 'node:os'; + +const DEFAULT_RESERVED_CORES = 2; + +export const getAvailableCpuCount = (): number => + typeof availableParallelism === 'function' + ? availableParallelism() + : cpus().length; + +export const getDefaultConcurrency = ( + cpuCount: number = getAvailableCpuCount() +): number => Math.max(0, Math.floor(cpuCount) - DEFAULT_RESERVED_CORES); + +export const mapWithConcurrency = async ( + items: readonly Item[], + concurrency: number, + worker: (item: Item, index: number) => Promise +): Promise => { + const results = new Array(items.length); + let nextIndex = 0; + const workerCount = Math.max(1, Math.min(concurrency, items.length)); + await Promise.all( + Array.from({ length: workerCount }, async () => { + while (true) { + const index = nextIndex++; + if (index >= items.length) { + return; + } + results[index] = await worker(items[index], index); + } + }) + ); + return results; +}; diff --git a/src/constants.ts b/src/constants.ts index 12c1732..7af426d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,6 +28,10 @@ export const SERVER_ONLY_ROUTE_EXPORTS = [ 'headers', ] as const; +export const SERVER_ONLY_ROUTE_EXPORTS_SET: ReadonlySet = new Set( + SERVER_ONLY_ROUTE_EXPORTS +); + // Client route exports are split into non-component exports and component exports. // This mirrors upstream React Router Vite plugin intent and is used for export filtering. export const CLIENT_NON_COMPONENT_EXPORTS = [ @@ -52,11 +56,19 @@ export const CLIENT_ROUTE_EXPORTS: readonly ( | (typeof CLIENT_COMPONENT_EXPORTS)[number] )[] = [...CLIENT_NON_COMPONENT_EXPORTS, ...CLIENT_COMPONENT_EXPORTS]; +export const CLIENT_ROUTE_EXPORTS_SET: ReadonlySet = new Set( + CLIENT_ROUTE_EXPORTS +); + export const NAMED_COMPONENT_EXPORTS = [ 'HydrateFallback', 'ErrorBoundary', ] as const; +export const NAMED_COMPONENT_EXPORTS_SET: ReadonlySet = new Set( + NAMED_COMPONENT_EXPORTS +); + export const SERVER_EXPORTS = { loader: 'loader', action: 'action', diff --git a/src/dev-generation.ts b/src/dev-generation.ts new file mode 100644 index 0000000..0c6075d --- /dev/null +++ b/src/dev-generation.ts @@ -0,0 +1,627 @@ +import type { RsbuildDevServer, Rspack } from '@rsbuild/core'; +import type { ServerBuild } from 'react-router'; +import { + evaluateServerBuilds, + getEnvironmentStats, + isSafeOneSidedChange, + pinServerBuildsToManifests, + snapshotDependencies, + type DependencySnapshot, + type DevCompilationIdentity, + type DevGraphChanges, + type DevGraphIdentity, + type ReactRouterDevBuildPlan, + type ReactRouterDevManifestSet, + type ReactRouterServerBuilds, + type WebArtifact, +} from './dev-runtime-artifacts.js'; + +export { snapshotDevChangedFiles } from './dev-runtime-artifacts.js'; +export type { + DevChangedFiles, + DevGraphChanges, + DevGraphIdentity, + ReactRouterDevBuildPlan, + ReactRouterDevManifest, + ReactRouterDevManifestSet, +} from './dev-runtime-artifacts.js'; + +type Deferred = { + promise: Promise; + resolve: (value: T) => void; + reject: (error: Error) => void; +}; + +type CommittedGeneration = { + buildsByEntryName: ReactRouterServerBuilds; + webIdentity: DevCompilationIdentity; + nodeIdentity: DevCompilationIdentity; + web: WebArtifact; + nodeDependencies: DependencySnapshot; +}; + +type RuntimeState = + | { + kind: 'starting'; + attemptId: number; + readiness: Deferred; + } + | { kind: 'failed'; attemptId: number; error: Error } + | { + kind: 'ready'; + committed: CommittedGeneration; + pendingAttemptId: number | null; + } + | { kind: 'closed'; error: Error }; + +export type ReactRouterDevRuntime = { + beginAttempt: () => void; + captureWeb: ( + compilation: Rspack.Compilation, + manifestsByEntryName: ReactRouterDevManifestSet + ) => void; + finishAttempt: ( + stats: Rspack.Stats | Rspack.MultiStats, + changes: DevGraphChanges, + identity: DevGraphIdentity + ) => Promise<'committed' | 'ignored' | 'retry-node'>; + failAttempt: (error: Error) => void; + load: (entryName?: string) => Promise; + close: (error?: Error) => void; +}; + +type CreateReactRouterDevRuntimeOptions = { + server: RsbuildDevServer; + buildPlan: ReactRouterDevBuildPlan; + onEvaluationError: (error: Error) => void; + onCssAssetOwnershipChanged?: () => void; + onWarning?: (message: string) => void; +}; + +const collectManifestCssAssetOwnership = ( + manifest: ReactRouterDevManifestSet[string] +): Set => { + const ownership = new Set(); + for (const asset of manifest.entry?.css ?? []) { + ownership.add(`entry\0${asset}`); + } + for (const [routeId, route] of Object.entries(manifest.routes ?? {})) { + for (const asset of route.css ?? []) { + ownership.add(`route\0${routeId}\0${asset}`); + } + } + return ownership; +}; + +const hasRemovedCssAssetOwnership = ( + previous: ReactRouterDevManifestSet, + next: ReactRouterDevManifestSet +): boolean => { + for (const [entryName, previousManifest] of Object.entries(previous)) { + const previousOwnership = + collectManifestCssAssetOwnership(previousManifest); + if (previousOwnership.size === 0) { + continue; + } + const nextManifest = next[entryName]; + if (!nextManifest) { + return true; + } + const nextOwnership = collectManifestCssAssetOwnership(nextManifest); + for (const owner of previousOwnership) { + if (!nextOwnership.has(owner)) { + return true; + } + } + } + return false; +}; + +const hasAddedCssAssetOwnership = ( + previous: ReactRouterDevManifestSet, + next: ReactRouterDevManifestSet +): boolean => hasRemovedCssAssetOwnership(next, previous); + +const collectManifestCssAssets = ( + manifest: ReactRouterDevManifestSet[string] +): Set => { + const assets = new Set(manifest.entry?.css ?? []); + for (const route of Object.values(manifest.routes ?? {})) { + for (const asset of route.css ?? []) { + assets.add(asset); + } + } + return assets; +}; + +const normalizeManifestForCssOwnershipCheck = ( + manifest: ReactRouterDevManifestSet[string] +) => { + const cssAssets = collectManifestCssAssets(manifest); + const nonCssImports = (imports: string[] = []) => + imports.filter(importPath => !cssAssets.has(importPath)); + + return { + entry: { + imports: nonCssImports(manifest.entry?.imports), + module: manifest.entry?.module, + }, + routes: Object.fromEntries( + Object.entries(manifest.routes ?? {}) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([routeId, route]) => [ + routeId, + { + caseSensitive: route.caseSensitive, + clientActionModule: route.clientActionModule, + clientLoaderModule: route.clientLoaderModule, + clientMiddlewareModule: route.clientMiddlewareModule, + errorBoundary: route.hasErrorBoundary, + hasAction: route.hasAction, + hasClientAction: route.hasClientAction, + hasClientLoader: route.hasClientLoader, + hasClientMiddleware: route.hasClientMiddleware, + hasDefaultExport: route.hasDefaultExport, + hasLoader: route.hasLoader, + hydrateFallbackModule: route.hydrateFallbackModule, + id: route.id, + imports: nonCssImports(route.imports), + index: route.index, + module: route.module, + parentId: route.parentId, + path: route.path, + }, + ]) + ), + }; +}; + +const hasOnlyCssAssetOwnershipChanges = ( + previous: ReactRouterDevManifestSet, + next: ReactRouterDevManifestSet +): boolean => { + const previousEntryNames = Object.keys(previous).sort(); + const nextEntryNames = Object.keys(next).sort(); + if (previousEntryNames.join('\0') !== nextEntryNames.join('\0')) { + return false; + } + return previousEntryNames.every(entryName => { + const previousManifest = normalizeManifestForCssOwnershipCheck( + previous[entryName] + ); + const nextManifest = normalizeManifestForCssOwnershipCheck(next[entryName]); + return JSON.stringify(previousManifest) === JSON.stringify(nextManifest); + }); +}; + +const createDeferred = (): Deferred => { + let resolve!: (value: T) => void; + let reject!: (error: Error) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + // Compilation can fail before a request asks for the build. Observe the + // rejection now while returning the same promise to future callers. + void promise.catch(() => undefined); + return { promise, resolve, reject }; +}; + +export const createReactRouterDevRuntime = ({ + server, + buildPlan, + onEvaluationError, + onCssAssetOwnershipChanged = () => undefined, + onWarning = () => undefined, +}: CreateReactRouterDevRuntimeOptions): ReactRouterDevRuntime => { + let nextAttemptId = 1; + let reloadAfterCssRemoval = false; + let state: RuntimeState = { + kind: 'starting', + attemptId: 0, + readiness: createDeferred(), + }; + const manifestsByCompilation = new WeakMap< + Rspack.Compilation, + ReactRouterDevManifestSet + >(); + + const notifyCssAssetOwnershipChanged = (): void => { + try { + onCssAssetOwnershipChanged(); + } catch (cause) { + const reason = cause instanceof Error ? cause.message : String(cause); + onWarning( + `[rsbuild-plugin-react-router] Failed to notify the browser after CSS asset ownership changed: ${reason}` + ); + } + }; + + const uniqueEntryNames = new Set(buildPlan.entryNames); + if ( + uniqueEntryNames.size !== buildPlan.entryNames.length || + !uniqueEntryNames.has(buildPlan.defaultEntryName) + ) { + throw new Error( + '[rsbuild-plugin-react-router] The development server build plan must contain unique entries and include its default entry.' + ); + } + + const selectBuild = ( + generation: CommittedGeneration, + requestedEntryName?: string + ): ServerBuild => { + const entryName = requestedEntryName ?? buildPlan.defaultEntryName; + const build = generation.buildsByEntryName[entryName]; + if (!build) { + throw new Error( + `[rsbuild-plugin-react-router] Committed React Router server build ${JSON.stringify(entryName)} was not found.` + ); + } + return build; + }; + + const getCurrentAttemptId = (): number | null => { + if (state.kind === 'starting') { + return state.attemptId; + } + return state.kind === 'ready' ? state.pendingAttemptId : null; + }; + + const isCurrentAttempt = (attemptId: number): boolean => + getCurrentAttemptId() === attemptId; + + const rejectAttempt = ( + attemptId: number, + error: Error, + report: boolean + ): void => { + if (!isCurrentAttempt(attemptId)) { + return; + } + if (state.kind === 'starting') { + const { readiness } = state; + state = { kind: 'failed', attemptId, error }; + readiness.reject(error); + } else if (state.kind === 'ready') { + state = { ...state, pendingAttemptId: null }; + } + if (report) { + onEvaluationError(error); + } + }; + + const commit = ( + attemptId: number, + committed: CommittedGeneration + ): boolean => { + if (!isCurrentAttempt(attemptId)) { + return false; + } + if (state.kind === 'starting') { + const { readiness } = state; + state = { kind: 'ready', committed, pendingAttemptId: null }; + readiness.resolve(committed); + } else if (state.kind === 'ready') { + state = { kind: 'ready', committed, pendingAttemptId: null }; + } + return true; + }; + + const discardUnsafeOneSidedResult = ( + attemptId: number, + previous: CommittedGeneration, + webChanged: boolean, + changes: DevGraphChanges + ): boolean => { + const side = webChanged ? 'web-only' : 'node-only'; + const changedFiles = webChanged ? changes.web : changes.node; + const unchangedDependencies = webChanged + ? previous.nodeDependencies + : previous.web.dependencies; + if (isSafeOneSidedChange(changedFiles, unchangedDependencies)) { + return false; + } + onWarning( + `[rsbuild-plugin-react-router] Discarded an incomplete ${side} development result and kept the last-good build.` + ); + rejectAttempt( + attemptId, + new Error(`Incomplete ${side} development result.`), + false + ); + return true; + }; + + return { + beginAttempt(): void { + if (state.kind === 'closed') { + return; + } + const attemptId = nextAttemptId++; + if (state.kind === 'failed') { + state = { + kind: 'starting', + attemptId, + readiness: createDeferred(), + }; + } else if (state.kind === 'starting') { + state = { ...state, attemptId }; + } else { + state = { ...state, pendingAttemptId: attemptId }; + } + }, + + captureWeb(compilation, manifestsByEntryName): void { + if (state.kind !== 'closed') { + manifestsByCompilation.set( + compilation, + structuredClone(manifestsByEntryName) + ); + } + }, + + async finishAttempt(stats, changes, identity) { + const attemptId = getCurrentAttemptId(); + if (attemptId === null) { + return 'ignored'; + } + const webStats = getEnvironmentStats(stats, 'web'); + const nodeStats = getEnvironmentStats(stats, 'node'); + if (!webStats || !nodeStats) { + rejectAttempt( + attemptId, + new Error( + '[rsbuild-plugin-react-router] Development compilation did not provide both web and node results.' + ), + true + ); + return 'ignored'; + } + if ( + webStats.compilation.needAdditionalPass || + nodeStats.compilation.needAdditionalPass + ) { + return 'ignored'; + } + if (webStats.hasErrors() || nodeStats.hasErrors()) { + rejectAttempt( + attemptId, + new Error( + '[rsbuild-plugin-react-router] The React Router development compilation failed.' + ), + false + ); + return 'ignored'; + } + + const webCompilation = webStats.compilation; + const nodeCompilation = nodeStats.compilation; + const webIdentity = identity.web; + const nodeIdentity = identity.node; + if (!webIdentity || !nodeIdentity) { + rejectAttempt( + attemptId, + new Error( + '[rsbuild-plugin-react-router] Development compilation identity was unavailable.' + ), + true + ); + return 'ignored'; + } + const previous = state.kind === 'ready' ? state.committed : undefined; + const webChanged = !previous || previous.webIdentity !== webIdentity; + const nodeChanged = !previous || previous.nodeIdentity !== nodeIdentity; + + if (!webChanged && !nodeChanged) { + return 'ignored'; + } + + const manifestsByEntryName = webChanged + ? manifestsByCompilation.get(webCompilation) + : previous?.web.manifestsByEntryName; + if (!manifestsByEntryName) { + rejectAttempt( + attemptId, + new Error( + '[rsbuild-plugin-react-router] The web compilation completed without a matching React Router manifest. Keeping the last-good development build.' + ), + true + ); + return 'ignored'; + } + const cssAssetsRemoved = + !!previous && + webChanged && + hasRemovedCssAssetOwnership( + previous.web.manifestsByEntryName, + manifestsByEntryName + ); + const cssAssetsAdded = + !!previous && + webChanged && + hasAddedCssAssetOwnership( + previous.web.manifestsByEntryName, + manifestsByEntryName + ); + const cssOnlyWebManifestChange = + (cssAssetsRemoved || cssAssetsAdded) && + hasOnlyCssAssetOwnershipChanges( + previous.web.manifestsByEntryName, + manifestsByEntryName + ); + const reusePreviousNodeBuild = !!previous && cssOnlyWebManifestChange; + const sameCompileAttempt = + !!identity.webAttempt && + !!identity.nodeAttempt && + identity.webAttempt === identity.nodeAttempt; + + if ( + nodeChanged && + identity.nodeWeb !== webIdentity && + !sameCompileAttempt && + !reusePreviousNodeBuild + ) { + const message = + '[rsbuild-plugin-react-router] Discarded web and node results from different compiler cycles and kept the last-good build.'; + if (!previous) { + return 'retry-node'; + } + onWarning(message); + rejectAttempt(attemptId, new Error(message), false); + return 'retry-node'; + } + + const shouldEvaluateNode = nodeChanged && !reusePreviousNodeBuild; + if ( + previous && + webChanged !== shouldEvaluateNode && + !cssOnlyWebManifestChange && + discardUnsafeOneSidedResult(attemptId, previous, webChanged, changes) + ) { + return 'ignored'; + } + + try { + const buildsByEntryName = shouldEvaluateNode + ? await evaluateServerBuilds(server, buildPlan.entryNames) + : previous!.buildsByEntryName; + if (!isCurrentAttempt(attemptId)) { + return 'ignored'; + } + const web = webChanged + ? { + manifestsByEntryName, + dependencies: snapshotDependencies(webCompilation), + } + : previous!.web; + const committed = commit(attemptId, { + buildsByEntryName: pinServerBuildsToManifests( + buildsByEntryName, + buildPlan.entryNames, + web.manifestsByEntryName + ), + webIdentity, + nodeIdentity: shouldEvaluateNode + ? nodeIdentity + : previous!.nodeIdentity, + web, + nodeDependencies: shouldEvaluateNode + ? snapshotDependencies(nodeCompilation) + : previous!.nodeDependencies, + }); + if (!committed) { + return 'ignored'; + } + if (cssAssetsRemoved) { + reloadAfterCssRemoval = !cssAssetsAdded; + notifyCssAssetOwnershipChanged(); + } else if (cssAssetsAdded) { + if (reloadAfterCssRemoval) { + notifyCssAssetOwnershipChanged(); + } + reloadAfterCssRemoval = false; + } + return 'committed'; + } catch (cause) { + rejectAttempt( + attemptId, + cause instanceof Error ? cause : new Error(String(cause)), + true + ); + return 'ignored'; + } + }, + + failAttempt(error): void { + const attemptId = getCurrentAttemptId(); + if (attemptId !== null) { + rejectAttempt(attemptId, error, false); + } + }, + + load(entryName?: string): Promise { + if (entryName && !uniqueEntryNames.has(entryName)) { + return Promise.reject( + new Error( + `[rsbuild-plugin-react-router] React Router server build ${JSON.stringify(entryName)} is not part of this development server build plan.` + ) + ); + } + if (state.kind === 'ready') { + return Promise.resolve(selectBuild(state.committed, entryName)); + } + if (state.kind === 'starting') { + const selected = state.readiness.promise.then(generation => + selectBuild(generation, entryName) + ); + // Compilation may fail before the request awaiting this selection has + // a chance to attach its own rejection handler. + void selected.catch(() => undefined); + return selected; + } + return Promise.reject(state.error); + }, + + close(error?: Error): void { + if (state.kind === 'closed') { + return; + } + const closeError = + error ?? + new Error( + '[rsbuild-plugin-react-router] The development server closed before a React Router build was ready.' + ); + if (state.kind === 'starting') { + state.readiness.reject(closeError); + } + state = { kind: 'closed', error: closeError }; + }, + }; +}; + +const DEV_RUNTIME_KEY = Symbol.for( + 'rsbuild-plugin-react-router.dev-runtime.v1' +); + +const getRegisteredRuntime = ( + server: RsbuildDevServer +): ReactRouterDevRuntime | undefined => + Reflect.get(server, DEV_RUNTIME_KEY) as ReactRouterDevRuntime | undefined; + +export const registerReactRouterDevRuntime = ( + server: RsbuildDevServer, + runtime: ReactRouterDevRuntime +): void => { + // Symbol.for keeps registration shared when the plugin and public helper are + // loaded through different ESM and CommonJS package entrypoints. + Object.defineProperty(server, DEV_RUNTIME_KEY, { + configurable: true, + enumerable: false, + value: runtime, + }); +}; + +export const unregisterReactRouterDevRuntime = ( + server: RsbuildDevServer, + runtime: ReactRouterDevRuntime +): void => { + if (getRegisteredRuntime(server) === runtime) { + Reflect.deleteProperty(server, DEV_RUNTIME_KEY); + } +}; + +export const loadReactRouterServerBuild = ( + server: RsbuildDevServer, + entryName?: string +): Promise => { + const runtime = getRegisteredRuntime(server); + if (!runtime) { + return Promise.reject( + new Error( + '[rsbuild-plugin-react-router] This Rsbuild development server is not registered with the React Router plugin. Add pluginReactRouter() before calling loadReactRouterServerBuild().' + ) + ); + } + return runtime.load(entryName); +}; diff --git a/src/dev-runtime-artifacts.ts b/src/dev-runtime-artifacts.ts new file mode 100644 index 0000000..7e3a4a2 --- /dev/null +++ b/src/dev-runtime-artifacts.ts @@ -0,0 +1,210 @@ +import { isAbsolute, relative } from 'node:path'; +import type { RsbuildDevServer, Rspack } from '@rsbuild/core'; +import type { ServerBuild } from 'react-router'; +import type { ReactRouterManifestForDev } from './manifest.js'; +import { resolveServerBuildModule } from './server-utils.js'; + +export type ReactRouterDevManifest = ReactRouterManifestForDev; + +export type ReactRouterDevBuildPlan = { + defaultEntryName: string; + entryNames: readonly string[]; +}; + +export type ReactRouterDevManifestSet = Readonly< + Record +>; + +export type ReactRouterServerBuilds = Readonly>; + +export type DependencySnapshot = { + files: ReadonlySet; + contexts: ReadonlySet; + missing: ReadonlySet; +}; + +export type DevChangedFiles = { + known: boolean; + files: ReadonlySet; +}; + +export type DevGraphChanges = { + web: DevChangedFiles; + node: DevChangedFiles; +}; + +export type DevCompilationIdentity = symbol; +export type DevCompileAttemptIdentity = symbol; + +export type DevGraphIdentity = { + web: DevCompilationIdentity | undefined; + node: DevCompilationIdentity | undefined; + nodeWeb: DevCompilationIdentity | undefined; + webAttempt: DevCompileAttemptIdentity | undefined; + nodeAttempt: DevCompileAttemptIdentity | undefined; +}; + +export type WebArtifact = { + manifestsByEntryName: ReactRouterDevManifestSet; + dependencies: DependencySnapshot; +}; + +export const snapshotDevChangedFiles = ( + compiler: Pick | undefined +): DevChangedFiles => { + if (!compiler) { + return { known: false, files: new Set() }; + } + return { + known: + compiler.modifiedFiles !== undefined || + compiler.removedFiles !== undefined, + files: new Set([ + ...(compiler.modifiedFiles ?? []), + ...(compiler.removedFiles ?? []), + ]), + }; +}; + +export const snapshotDependencies = ( + compilation: Rspack.Compilation +): DependencySnapshot => ({ + files: new Set([ + ...compilation.fileDependencies, + ...(compilation.buildDependencies ?? []), + ]), + contexts: new Set(compilation.contextDependencies), + missing: new Set(compilation.missingDependencies), +}); + +const isWithinDirectory = (directory: string, file: string): boolean => { + const relativePath = relative(directory, file); + return ( + relativePath === '' || + (!relativePath.startsWith('..') && !isAbsolute(relativePath)) + ); +}; + +export const isSafeOneSidedChange = ( + changes: DevChangedFiles, + dependencies: DependencySnapshot +): boolean => { + if (!changes.known || changes.files.size === 0) { + return false; + } + for (const file of changes.files) { + if (dependencies.files.has(file) || dependencies.missing.has(file)) { + return false; + } + for (const directory of dependencies.contexts) { + if (isWithinDirectory(directory, file)) { + return false; + } + } + } + return true; +}; + +export const getEnvironmentStats = ( + stats: Rspack.Stats | Rspack.MultiStats, + name: 'web' | 'node' +): Rspack.Stats | undefined => { + const children = Array.isArray((stats as Rspack.MultiStats).stats) + ? (stats as Rspack.MultiStats).stats + : [stats as Rspack.Stats]; + return children.find(child => { + const compilation = child.compilation; + return compilation.name === name || compilation.compiler?.name === name; + }); +}; + +const evaluateServerBuild = async ( + server: RsbuildDevServer, + entryName: string +): Promise => { + const loaded = await server.environments.node.loadBundle(entryName); + return resolveServerBuildModule( + loaded, + `Server entry ${JSON.stringify(entryName)}` + ); +}; + +export const evaluateServerBuilds = async ( + server: RsbuildDevServer, + entryNames: readonly string[] +): Promise => { + const evaluated = await Promise.all( + entryNames.map(async entryName => [ + entryName, + await evaluateServerBuild(server, entryName), + ]) + ); + return Object.fromEntries(evaluated) as Record; +}; + +const assertBuildMatchesManifest = ( + entryName: string, + build: ServerBuild, + manifest: ReactRouterDevManifest +): void => { + for (const [routeId, manifestRoute] of Object.entries(manifest.routes)) { + const routeModule = build.routes[routeId]?.module; + if (!routeModule) { + throw new Error( + `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} is missing from the evaluated server build.` + ); + } + if ( + Boolean(manifestRoute.hasLoader) !== + (typeof routeModule.loader === 'function') + ) { + throw new Error( + `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} loader export does not match its web manifest.` + ); + } + if ( + Boolean(manifestRoute.hasAction) !== + (typeof routeModule.action === 'function') + ) { + throw new Error( + `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} route ${JSON.stringify(routeId)} action export does not match its web manifest.` + ); + } + } +}; + +const pinBuildToManifest = ( + entryName: string, + build: ServerBuild, + manifest: ReactRouterDevManifest +): ServerBuild => { + assertBuildMatchesManifest(entryName, build, manifest); + return { + ...build, + assets: structuredClone(manifest) as ServerBuild['assets'], + }; +}; + +export const pinServerBuildsToManifests = ( + builds: ReactRouterServerBuilds, + entryNames: readonly string[], + manifestsByEntryName: ReactRouterDevManifestSet +): ReactRouterServerBuilds => { + const pinned: Record = {}; + for (const entryName of entryNames) { + const build = builds[entryName]; + if (!build) { + throw new Error( + `[rsbuild-plugin-react-router] Expected server build ${JSON.stringify(entryName)} was not evaluated.` + ); + } + const manifest = manifestsByEntryName[entryName]; + if (!manifest) { + throw new Error( + `[rsbuild-plugin-react-router] Server build ${JSON.stringify(entryName)} has no matching web manifest.` + ); + } + pinned[entryName] = pinBuildToManifest(entryName, build, manifest); + } + return pinned; +}; diff --git a/src/dev-runtime-compilation.ts b/src/dev-runtime-compilation.ts new file mode 100644 index 0000000..81b045d --- /dev/null +++ b/src/dev-runtime-compilation.ts @@ -0,0 +1,118 @@ +import type { Rspack } from '@rsbuild/core'; +import type { + DevCompileAttemptIdentity, + DevCompilationIdentity, + DevGraphChanges, + DevGraphIdentity, +} from './dev-runtime-artifacts.js'; + +export type DevCompilerPair = { + web: Rspack.Compiler; + node: Rspack.Compiler; + settledCompilations: WeakSet; + pendingAttempt?: PendingDevCompilation; + currentAttemptIdentity?: DevCompileAttemptIdentity; + latestCompletedWebIdentity?: DevCompilationIdentity; + latestWebStart?: CompilationStart; + latestNodeStart?: CompilationStart; +}; + +export type PendingDevCompilation = { + stats: Rspack.Stats | Rspack.MultiStats; + changes: DevGraphChanges; + identity: DevGraphIdentity; + webCompilation: Rspack.Compilation; + nodeCompilation: Rspack.Compilation; +}; + +export type CompilationStart = + | { status: 'pending' } + | { status: 'started'; identity: DevCompilationIdentity }; + +export const isLatestStartedCompilation = ( + identity: DevCompilationIdentity | undefined, + start: CompilationStart | undefined +): boolean => + !identity || (start?.status === 'started' && start.identity === identity); + +export const hasPendingCompilation = (pair: DevCompilerPair): boolean => + pair.latestWebStart?.status === 'pending' || + pair.latestNodeStart?.status === 'pending'; + +export type CompilationIdentityTracker = { + getCompilationIdentity( + compilation: Rspack.Compilation + ): DevCompilationIdentity; + getWebIdentityForNodeCompilation( + compilation: Rspack.Compilation + ): DevCompilationIdentity | undefined; + getAttemptIdentityForCompilation( + compilation: Rspack.Compilation + ): DevCompileAttemptIdentity | undefined; + setAttemptIdentityForCompilation( + compilation: Rspack.Compilation, + identity: DevCompileAttemptIdentity + ): void; + setWebIdentityForNodeCompilation( + compilation: Rspack.Compilation, + identity: DevCompilationIdentity + ): void; +}; + +export const createCompilationIdentityTracker = + (): CompilationIdentityTracker => { + const identityByCompilation = new WeakMap< + Rspack.Compilation, + DevCompilationIdentity + >(); + const webIdentityByNodeCompilation = new WeakMap< + Rspack.Compilation, + DevCompilationIdentity + >(); + const attemptIdentityByCompilation = new WeakMap< + Rspack.Compilation, + DevCompileAttemptIdentity + >(); + + return { + getCompilationIdentity( + compilation: Rspack.Compilation + ): DevCompilationIdentity { + const existing = identityByCompilation.get(compilation); + if (existing) { + return existing; + } + const identity = Symbol(); + // Keep compact lineage tokens in committed state without retaining entire + // Rspack compilation graphs across failed rebuilds. + identityByCompilation.set(compilation, identity); + return identity; + }, + + getWebIdentityForNodeCompilation( + compilation: Rspack.Compilation + ): DevCompilationIdentity | undefined { + return webIdentityByNodeCompilation.get(compilation); + }, + + getAttemptIdentityForCompilation( + compilation: Rspack.Compilation + ): DevCompileAttemptIdentity | undefined { + return attemptIdentityByCompilation.get(compilation); + }, + + setAttemptIdentityForCompilation( + compilation: Rspack.Compilation, + identity: DevCompileAttemptIdentity + ): void { + attemptIdentityByCompilation.set(compilation, identity); + }, + + setWebIdentityForNodeCompilation( + compilation: Rspack.Compilation, + identity: DevCompilationIdentity + ): void { + webIdentityByNodeCompilation.set(compilation, identity); + }, + }; + }; diff --git a/src/dev-runtime-controller.ts b/src/dev-runtime-controller.ts new file mode 100644 index 0000000..4746d71 --- /dev/null +++ b/src/dev-runtime-controller.ts @@ -0,0 +1,464 @@ +import type { RsbuildConfig, RsbuildPluginAPI, Rspack } from '@rsbuild/core'; +import type { ServerBuild } from 'react-router'; +import { PLUGIN_NAME } from './constants.js'; +import { + createCompilationIdentityTracker, + hasPendingCompilation, + isLatestStartedCompilation, + type DevCompilerPair, +} from './dev-runtime-compilation.js'; +import { + createReactRouterDevRuntime, + loadReactRouterServerBuild, + registerReactRouterDevRuntime, + unregisterReactRouterDevRuntime, +} from './dev-generation.js'; +import { + getEnvironmentStats, + snapshotDevChangedFiles, + type ReactRouterDevBuildPlan, + type ReactRouterDevManifestSet, +} from './dev-runtime-artifacts.js'; +import { + createDevRuntimeSessionManager, + type RuntimeBinding, +} from './dev-runtime-session.js'; + +type ServerSetup = Exclude< + NonNullable['setup']>, + unknown[] +>; + +export type ReactRouterDevRuntimeController = { + captureWeb: ( + compilation: Rspack.Compilation, + manifestsByEntryName: ReactRouterDevManifestSet + ) => void; + createBuildLoader: (entryName?: string) => () => Promise; +}; + +type CreateControllerOptions = { + api: RsbuildPluginAPI; + isBuild: boolean; + buildPlan: ReactRouterDevBuildPlan; +}; + +const escapeHtml = (value: string): string => + value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>'); + +export const createReactRouterDevRuntimeController = ({ + api, + isBuild, + buildPlan, +}: CreateControllerOptions): ReactRouterDevRuntimeController => { + if (isBuild) { + return { + captureWeb() {}, + createBuildLoader() { + return () => + Promise.reject( + new Error( + `[${PLUGIN_NAME}] The development server runtime is unavailable during a production build.` + ) + ); + }, + }; + } + + const closeBinding = (binding: RuntimeBinding, error?: Error): void => { + const pair = binding.compilers; + if (pair) { + pair.pendingAttempt = undefined; + pair.currentAttemptIdentity = undefined; + pair.latestCompletedWebIdentity = undefined; + pair.latestWebStart = undefined; + pair.latestNodeStart = undefined; + } + binding.compilers = undefined; + binding.runtime.close(error); + unregisterReactRouterDevRuntime(binding.server, binding.runtime); + }; + + const sessions = createDevRuntimeSessionManager(closeBinding); + const compilationIdentities = createCompilationIdentityTracker(); + const { getCompilationIdentity } = compilationIdentities; + + const finishRuntimeAttempt = async ( + binding: RuntimeBinding, + pair: DevCompilerPair, + stats: Rspack.Stats | Rspack.MultiStats, + changes: Parameters[1], + identity: Parameters[2] + ): Promise => { + const result = await binding.runtime.finishAttempt( + stats, + changes, + identity + ); + if ( + result === 'retry-node' && + sessions.getActiveBinding()?.id === binding.id + ) { + pair.node.watching?.invalidate(); + } + }; + + const flushSettledAttempt = ( + binding: RuntimeBinding, + pair: DevCompilerPair + ): void => { + const pending = pair.pendingAttempt; + if ( + !pending || + sessions.getActiveBinding()?.id !== binding.id || + !pair.settledCompilations.has(pending.webCompilation) || + !pair.settledCompilations.has(pending.nodeCompilation) + ) { + return; + } + pair.pendingAttempt = undefined; + if ( + !isLatestStartedCompilation(pending.identity.web, pair.latestWebStart) || + !isLatestStartedCompilation(pending.identity.node, pair.latestNodeStart) + ) { + return; + } + void binding.runtime + .finishAttempt(pending.stats, pending.changes, pending.identity) + .then(result => { + if ( + result === 'retry-node' && + sessions.getActiveBinding()?.id === binding.id + ) { + pair.node.watching?.invalidate(); + } + }) + .catch(cause => { + if (sessions.getActiveBinding()?.id === binding.id) { + binding.runtime.failAttempt( + cause instanceof Error ? cause : new Error(String(cause)) + ); + } + }); + }; + + const rejectUnsupportedCompiler = (reason: string): void => { + const message = `[${PLUGIN_NAME}] Could not coordinate React Router development output because ${reason}.`; + api.logger.warn(message); + const binding = sessions.getActiveBinding(); + if (!binding) { + return; + } + const error = new Error(message); + sessions.terminate(binding, error); + }; + + // Rsbuild runs server.setup before onBeforeStartDevServer. Prepending the + // observer here ensures setup callbacks cannot capture an unobserved close. + api.modifyRsbuildConfig({ + order: 'post', + handler(config) { + const existingSetup = config.server?.setup; + const setup = existingSetup + ? Array.isArray(existingSetup) + ? existingSetup + : [existingSetup] + : []; + const observeServer: ServerSetup = context => { + if (context.action === 'dev') { + sessions.observeClose(context.server); + } + }; + return { + ...config, + server: { + ...config.server, + setup: [observeServer, ...setup], + }, + }; + }, + }); + + api.onBeforeStartDevServer({ + order: 'pre', + async handler({ server }) { + sessions.assertCanStart(); + const runtime = createReactRouterDevRuntime({ + server, + buildPlan, + onEvaluationError(error) { + if (sessions.getActiveBinding()?.runtime !== runtime) { + return; + } + api.logger.error(error.message); + server.sockWrite('errors', { + text: [error.message], + html: escapeHtml(error.message), + }); + }, + onCssAssetOwnershipChanged() { + if (sessions.getActiveBinding()?.runtime !== runtime) { + return; + } + server.sockWrite('full-reload', { path: '*' }); + }, + onWarning: message => api.logger.warn(message), + }); + const binding = sessions.createBinding(server, runtime); + registerReactRouterDevRuntime(server, runtime); + sessions.bindCloseObservation(binding); + }, + }); + + api.onCloseDevServer({ + order: 'pre', + handler() { + const binding = sessions.getActiveBinding(); + if (!binding) { + return; + } + closeBinding(binding); + sessions.markClosing(binding); + }, + }); + + api.onBeforeDevCompile({ + order: 'pre', + handler() { + const binding = sessions.getActiveBinding(); + const pair = binding?.compilers; + if (!binding || !pair || hasPendingCompilation(pair)) { + return; + } + pair.pendingAttempt = undefined; + pair.currentAttemptIdentity = Symbol(); + binding.runtime.beginAttempt(); + }, + }); + + api.onAfterCreateCompiler(({ compiler }) => { + if (!('compilers' in compiler)) { + rejectUnsupportedCompiler('Rsbuild did not create a multi-compiler'); + return; + } + const web = compiler.compilers.find(item => item.name === 'web'); + const node = compiler.compilers.find(item => item.name === 'node'); + if (!web || !node) { + rejectUnsupportedCompiler('the web or node compiler was missing'); + return; + } + const binding = sessions.getActiveBinding(); + if (!binding) { + return; + } + const pair: DevCompilerPair = { + web, + node, + settledCompilations: new WeakSet(), + }; + binding.compilers = pair; + const sessionId = binding.id; + const runtime = binding.runtime; + const failCurrentAttempt = (side: 'web' | 'node', error: Error): void => { + if (sessions.getActiveBinding()?.id === sessionId) { + if (side === 'web') { + pair.latestWebStart = undefined; + } else { + pair.latestNodeStart = undefined; + } + pair.pendingAttempt = undefined; + runtime.failAttempt(error); + } + }; + const beginCompilerAttempt = ( + side: 'latestWebStart' | 'latestNodeStart' + ): void => { + if ( + sessions.getActiveBinding()?.id === sessionId && + pair[side]?.status !== 'pending' + ) { + const attemptAlreadyPending = hasPendingCompilation(pair); + // Invalidation can arrive before the aggregate before-compile hook. + // Supersede any evaluation that could resolve in that gap immediately. + pair[side] = { status: 'pending' }; + pair.pendingAttempt = undefined; + if (!attemptAlreadyPending) { + pair.currentAttemptIdentity = Symbol(); + runtime.beginAttempt(); + } + } + }; + web.hooks.invalid.tap(`${PLUGIN_NAME}:dev-web-invalid`, () => + beginCompilerAttempt('latestWebStart') + ); + node.hooks.invalid.tap(`${PLUGIN_NAME}:dev-node-invalid`, () => + beginCompilerAttempt('latestNodeStart') + ); + web.hooks.done.tap( + { name: `${PLUGIN_NAME}:dev-web-complete`, stage: -1000 }, + stats => { + if (sessions.getActiveBinding()?.id !== sessionId) { + return; + } + pair.latestCompletedWebIdentity = getCompilationIdentity( + stats.compilation + ); + } + ); + web.hooks.thisCompilation.tap( + `${PLUGIN_NAME}:dev-web-compilation`, + compilation => { + if (sessions.getActiveBinding()?.id === sessionId) { + if (pair.currentAttemptIdentity) { + compilationIdentities.setAttemptIdentityForCompilation( + compilation, + pair.currentAttemptIdentity + ); + } + pair.latestWebStart = { + status: 'started', + identity: getCompilationIdentity(compilation), + }; + } + } + ); + node.hooks.thisCompilation.tap( + `${PLUGIN_NAME}:dev-node-web-compilation`, + compilation => { + if (sessions.getActiveBinding()?.id !== sessionId) { + return; + } + pair.latestNodeStart = { + status: 'started', + identity: getCompilationIdentity(compilation), + }; + if (pair.currentAttemptIdentity) { + compilationIdentities.setAttemptIdentityForCompilation( + compilation, + pair.currentAttemptIdentity + ); + } + if (pair.latestCompletedWebIdentity) { + compilationIdentities.setWebIdentityForNodeCompilation( + compilation, + pair.latestCompletedWebIdentity + ); + } + } + ); + const settleCompilation = (stats: Rspack.Stats): void => { + if (sessions.getActiveBinding()?.id !== sessionId) { + return; + } + pair.settledCompilations.add(stats.compilation); + flushSettledAttempt(binding, pair); + }; + web.hooks.afterDone.tap( + `${PLUGIN_NAME}:dev-web-settled`, + settleCompilation + ); + node.hooks.afterDone.tap( + `${PLUGIN_NAME}:dev-node-settled`, + settleCompilation + ); + web.hooks.failed.tap(`${PLUGIN_NAME}:dev-web-failed`, error => + failCurrentAttempt('web', error) + ); + node.hooks.failed.tap(`${PLUGIN_NAME}:dev-node-failed`, error => + failCurrentAttempt('node', error) + ); + }); + + api.onAfterDevCompile(async ({ stats }) => { + const binding = sessions.getActiveBinding(); + const pair = binding?.compilers; + if (!binding || !pair) { + return; + } + const webStats = getEnvironmentStats(stats, 'web'); + const nodeStats = getEnvironmentStats(stats, 'node'); + if ( + (webStats && webStats.compilation.compiler !== pair.web) || + (nodeStats && nodeStats.compilation.compiler !== pair.node) + ) { + return; + } + const webIdentity = webStats + ? getCompilationIdentity(webStats.compilation) + : undefined; + const nodeIdentity = nodeStats + ? getCompilationIdentity(nodeStats.compilation) + : undefined; + if ( + !isLatestStartedCompilation(webIdentity, pair.latestWebStart) || + !isLatestStartedCompilation(nodeIdentity, pair.latestNodeStart) + ) { + return; + } + const changes = { + web: snapshotDevChangedFiles(pair.web), + node: snapshotDevChangedFiles(pair.node), + }; + const identity = { + web: webIdentity, + node: nodeIdentity, + nodeWeb: nodeStats + ? compilationIdentities.getWebIdentityForNodeCompilation( + nodeStats.compilation + ) + : undefined, + webAttempt: webStats + ? compilationIdentities.getAttemptIdentityForCompilation( + webStats.compilation + ) + : undefined, + nodeAttempt: nodeStats + ? compilationIdentities.getAttemptIdentityForCompilation( + nodeStats.compilation + ) + : undefined, + }; + if (!webStats || !nodeStats) { + await finishRuntimeAttempt(binding, pair, stats, changes, identity); + return; + } + pair.pendingAttempt = { + stats, + changes, + identity, + webCompilation: webStats.compilation, + nodeCompilation: nodeStats.compilation, + }; + flushSettledAttempt(binding, pair); + }); + + return { + captureWeb(compilation, manifestsByEntryName): void { + const binding = sessions.getActiveBinding(); + if (binding?.compilers?.web === compilation.compiler) { + binding.runtime.captureWeb(compilation, manifestsByEntryName); + } + }, + + createBuildLoader(entryName?: string): () => Promise { + const server = sessions.getActiveBinding()?.server; + if (server) { + return () => loadReactRouterServerBuild(server, entryName); + } + const state = sessions.getState(); + if (state.status === 'terminal') { + const { error } = state; + return () => Promise.reject(error); + } + return () => + Promise.reject( + new Error( + `[${PLUGIN_NAME}] The development server runtime is not ready.` + ) + ); + }, + }; +}; diff --git a/src/dev-runtime-session.ts b/src/dev-runtime-session.ts new file mode 100644 index 0000000..047a3c9 --- /dev/null +++ b/src/dev-runtime-session.ts @@ -0,0 +1,184 @@ +import type { RsbuildDevServer } from '@rsbuild/core'; +import { PLUGIN_NAME } from './constants.js'; +import type { ReactRouterDevRuntime } from './dev-generation.js'; +import type { DevCompilerPair } from './dev-runtime-compilation.js'; + +export type RuntimeBinding = { + id: number; + server: RsbuildDevServer; + runtime: ReactRouterDevRuntime; + compilers?: DevCompilerPair; +}; + +type CloseOutcome = { ok: true } | { ok: false; cause: unknown }; + +type CloseObservation = { + binding?: RuntimeBinding; + promise?: Promise; + outcome?: CloseOutcome; +}; + +export type ControllerState = + | { status: 'idle' } + | { status: 'active'; binding: RuntimeBinding } + | { status: 'closing'; binding: RuntimeBinding } + | { status: 'terminal'; error: Error }; + +type CloseBinding = (binding: RuntimeBinding, error?: Error) => void; + +export type DevRuntimeSessionManager = { + getState(): ControllerState; + getActiveBinding(): RuntimeBinding | undefined; + observeClose(server: RsbuildDevServer): void; + assertCanStart(): void; + createBinding( + server: RsbuildDevServer, + runtime: ReactRouterDevRuntime + ): RuntimeBinding; + bindCloseObservation(binding: RuntimeBinding): void; + markClosing(binding: RuntimeBinding): void; + terminate(binding: RuntimeBinding, error: Error): void; +}; + +export const createDevRuntimeSessionManager = ( + closeBinding: CloseBinding +): DevRuntimeSessionManager => { + let state: ControllerState = { status: 'idle' }; + let nextSessionId = 1; + const closeObservationByServer = new WeakMap< + RsbuildDevServer, + CloseObservation + >(); + + const getState = (): ControllerState => state; + + const getActiveBinding = (): RuntimeBinding | undefined => + state.status === 'active' ? state.binding : undefined; + + const isCurrentBinding = (binding: RuntimeBinding): boolean => + (state.status === 'active' || state.status === 'closing') && + state.binding === binding; + + const completeClose = (binding: RuntimeBinding): void => { + if (!isCurrentBinding(binding)) { + return; + } + if (state.status === 'active') { + closeBinding(binding); + } + state = { status: 'idle' }; + }; + + const failClose = (binding: RuntimeBinding, cause: unknown): void => { + if (!isCurrentBinding(binding)) { + return; + } + const error = new Error( + `[${PLUGIN_NAME}] The previous development server failed to close. Restart the process before retrying because Rsbuild may not have finished tearing down its compiler and watchers.`, + { cause } + ); + closeBinding(binding, error); + state = { status: 'terminal', error }; + }; + + const applyCloseOutcome = ( + observation: CloseObservation, + outcome: CloseOutcome + ): void => { + observation.outcome = outcome; + const { binding } = observation; + if (!binding) { + return; + } + if (outcome.ok) { + completeClose(binding); + } else { + failClose(binding, outcome.cause); + } + observation.binding = undefined; + }; + + const observeClose = (server: RsbuildDevServer): CloseObservation => { + const existing = closeObservationByServer.get(server); + if (existing) { + return existing; + } + const observation: CloseObservation = {}; + const close = server.close.bind(server); + server.close = () => { + if (observation.promise) { + return observation.promise; + } + let closePromise: Promise; + try { + closePromise = close(); + } catch (cause) { + closePromise = Promise.reject(cause); + } + observation.promise = closePromise; + void closePromise.then( + () => applyCloseOutcome(observation, { ok: true }), + cause => applyCloseOutcome(observation, { ok: false, cause }) + ); + return closePromise; + }; + closeObservationByServer.set(server, observation); + return observation; + }; + + return { + getState, + getActiveBinding, + + observeClose(server: RsbuildDevServer): void { + observeClose(server); + }, + + assertCanStart(): void { + if (state.status === 'terminal') { + throw state.error; + } + if (state.status === 'active') { + throw new Error( + `[${PLUGIN_NAME}] A development server is already active. Await its close() before calling createDevServer() again. If startup failed before returning the server, restart the process before retrying.` + ); + } + if (state.status === 'closing') { + throw new Error( + `[${PLUGIN_NAME}] The previous development server is still closing. Await its close() before calling createDevServer() again.` + ); + } + }, + + createBinding( + server: RsbuildDevServer, + runtime: ReactRouterDevRuntime + ): RuntimeBinding { + const binding = { id: nextSessionId++, server, runtime }; + state = { status: 'active', binding }; + return binding; + }, + + bindCloseObservation(binding: RuntimeBinding): void { + const observation = observeClose(binding.server); + observation.binding = binding; + if (observation.outcome) { + applyCloseOutcome(observation, observation.outcome); + } + }, + + markClosing(binding: RuntimeBinding): void { + if (isCurrentBinding(binding)) { + state = { status: 'closing', binding }; + } + }, + + terminate(binding: RuntimeBinding, error: Error): void { + if (!isCurrentBinding(binding)) { + return; + } + closeBinding(binding, error); + state = { status: 'terminal', error }; + }, + }; +}; diff --git a/src/dev-server.ts b/src/dev-server.ts index b175fed..daff753 100644 --- a/src/dev-server.ts +++ b/src/dev-server.ts @@ -1,67 +1,57 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; -import { normalizeBuildModule, resolveBuildExports } from './server-utils.js'; +import type { ServerBuild } from 'react-router'; export type DevServerMiddleware = ( req: IncomingMessage, res: ServerResponse, - next: (err?: any) => void + next: (err?: unknown) => void ) => Promise; -export const createDevServerMiddleware = (server: any): DevServerMiddleware => { - return async ( - req: IncomingMessage, - res: ServerResponse, - next: (err?: any) => void - ): Promise => { - try { - const tryLoadBundle = async (entryName: string) => { - try { - return await server.environments.node.loadBundle(entryName); - } catch (error) { - if ( - error instanceof Error && - error.message.includes("Can't find entry") - ) { - return null; - } - throw error; - } - }; +type RequestHandler = (request: Request) => Response | Promise; +type BuildProvider = () => Promise; - const bundle = - (await tryLoadBundle('static/js/app')) ?? (await tryLoadBundle('app')); +export type DevServerMiddlewareDependencies = { + loadBuild: BuildProvider; + createRequestHandler?: ( + build: BuildProvider, + mode: 'development' + ) => RequestHandler; + createRequestListener?: ( + handler: RequestHandler + ) => (req: IncomingMessage, res: ServerResponse) => void | Promise; +}; - if (!bundle || !bundle.routes) { - throw new Error('Server bundle not found or invalid'); - } +export const createDevServerMiddleware = ( + dependencies: DevServerMiddlewareDependencies +): DevServerMiddleware => { + let listenerPromise: + | Promise< + (req: IncomingMessage, res: ServerResponse) => void | Promise + > + | undefined; - // Use the modern request listener implementation directly to reduce - // our reliance on the deprecated `@mjackson/node-fetch-server` package. - const rr = await import('react-router'); - const nfs = await import('@remix-run/node-fetch-server'); - if (typeof rr.createRequestHandler !== 'function') { - throw new Error( - '[rsbuild-plugin-react-router] Missing `createRequestHandler` export from `react-router`' - ); - } - if (typeof nfs.createRequestListener !== 'function') { - throw new Error( - '[rsbuild-plugin-react-router] Missing `createRequestListener` export from `@remix-run/node-fetch-server`' - ); - } - const normalizedBuild = normalizeBuildModule(bundle); - const build = await resolveBuildExports(normalizedBuild); - const requestHandler = rr.createRequestHandler(build, 'development'); - // `createRequestListener` provides `client` info but React Router's - // request handler expects an app-defined `loadContext` object. - // For the built-in dev middleware we don't currently provide a load - // context, so pass `undefined`. - const listener = nfs.createRequestListener(request => - requestHandler(request) + const getListener = () => { + listenerPromise ??= (async () => { + const createRequestHandler = + dependencies.createRequestHandler ?? + (await import('react-router')).createRequestHandler; + const createRequestListener = + dependencies.createRequestListener ?? + (await import('@remix-run/node-fetch-server')).createRequestListener; + const requestHandler = createRequestHandler( + dependencies.loadBuild, + 'development' ); + return createRequestListener(request => requestHandler(request)); + })(); + return listenerPromise; + }; + + return async (req, res, next): Promise => { + try { + const listener = await getListener(); await listener(req, res); } catch (error) { - console.error('SSR Error:', error); next(error); } }; diff --git a/src/export-utils.ts b/src/export-utils.ts index d5f67d4..6a4c526 100644 --- a/src/export-utils.ts +++ b/src/export-utils.ts @@ -1,64 +1,219 @@ -import { readFile } from 'node:fs/promises'; -import { extname } from 'pathe'; -import * as esbuild from 'esbuild'; -import { init, parse as parseExports } from 'es-module-lexer'; -import { JS_LOADERS } from './constants.js'; - -const getEsbuildLoader = (resourcePath: string): esbuild.Loader => { - const ext = extname(resourcePath) as keyof typeof JS_LOADERS; - return JS_LOADERS[ext] ?? 'js'; +import { readFile, stat } from 'node:fs/promises'; +import { langFromPath, parse } from 'yuku-parser'; +import { setBoundedCacheEntry } from './bounded-cache.js'; +import { + getExportedName, + getIdentifierNamesFromPattern, + getProgram, + type AnyNode, + type ProgramNode, +} from './route-ast.js'; + +type ExportInfo = { + readonly exportNames: readonly string[]; + readonly exportAllModules: readonly string[]; }; -export const transformToEsm = async ( - code: string, - resourcePath: string -): Promise => { - return ( - await esbuild.transform(code, { - jsx: 'automatic', - format: 'esm', - platform: 'neutral', - loader: getEsbuildLoader(resourcePath), - }) - ).code; +type RouteModuleAnalysis = { + readonly code: string; + readonly exports: readonly string[]; + readonly exportAllModules: readonly string[]; +}; + +type RouteModuleAnalysisCacheEntry = { + mtimeMs: number; + size: number; + analysis: Promise; }; -export const getExportNames = async (code: string): Promise => { - await init; - const [, exportSpecifiers] = await parseExports(code); - return Array.from( - new Set(exportSpecifiers.map(specifier => specifier.n).filter(Boolean)) +const exportInfoCache = new Map>(); +const routeModuleAnalysisCache = new Map< + string, + RouteModuleAnalysisCacheEntry +>(); + +const MAX_EXPORT_UTILS_CACHE_ENTRIES = 2048; + +const cachePromiseOnReject = ( + promise: Promise, + invalidate: () => void +): Promise => + promise.catch(error => { + invalidate(); + throw error; + }); + +const parseProgram = (code: string, resourcePath?: string): ProgramNode => { + const result = parse(code, { + sourceType: 'module', + lang: resourcePath ? langFromPath(resourcePath) : 'tsx', + }); + const errors = result.diagnostics.filter( + diagnostic => diagnostic.severity === 'error' ); + if (errors.length > 0) { + throw new Error(errors.map(error => error.message).join('\n')); + } + return getProgram(result); }; -export const getExportNamesAndExportAll = async ( - code: string -): Promise<{ exportNames: string[]; exportAllModules: string[] }> => { - await init; - const [imports, exportSpecifiers] = await parseExports(code); +const isTypeOnlyExport = (node: AnyNode): boolean => + node.exportKind === 'type' || + node.type === 'TSExportAssignment' || + node.declaration?.declare === true || + (node.type === 'ExportDefaultDeclaration' && + node.declaration?.type === 'TSInterfaceDeclaration'); + +export const collectProgramExportNames = (program: ProgramNode): string[] => { const exportNames = new Set(); - for (const specifier of exportSpecifiers) { - if (specifier.n) { - exportNames.add(specifier.n); + for (const statement of program.body ?? []) { + if (isTypeOnlyExport(statement)) { + continue; + } + + if (statement.type === 'ExportAllDeclaration') { + const exported = getExportedName(statement.exported); + if (exported) { + exportNames.add(exported); + } + continue; + } + + if (statement.type === 'ExportDefaultDeclaration') { + exportNames.add('default'); + continue; + } + + if (statement.type !== 'ExportNamedDeclaration') { + continue; + } + const declaration = statement.declaration; + if (declaration) { + if (declaration.type === 'VariableDeclaration') { + for (const declarator of declaration.declarations ?? []) { + for (const name of getIdentifierNamesFromPattern(declarator.id)) { + exportNames.add(name); + } + } + } else if ( + (declaration.type === 'FunctionDeclaration' || + declaration.type === 'ClassDeclaration' || + declaration.type === 'TSEnumDeclaration') && + declaration.id?.name + ) { + exportNames.add(declaration.id.name); + } + continue; + } + + for (const specifier of statement.specifiers ?? []) { + if (specifier.exportKind === 'type') { + continue; + } + const exported = getExportedName(specifier.exported); + if (exported) { + exportNames.add(exported); + } } } - const exportAllModules: string[] = []; - for (const entry of imports) { - if (!entry.n) { + return Array.from(exportNames); +}; + +const collectExportAllModules = (program: AnyNode): string[] => { + const modules: string[] = []; + for (const statement of program.body ?? []) { + if ( + statement.type !== 'ExportAllDeclaration' || + isTypeOnlyExport(statement) + ) { continue; } - const statement = code.slice(entry.ss, entry.se); - if (/^\s*export\s*\*\s*from\s*['"]/.test(statement)) { - exportAllModules.push(entry.n); + if (statement.exported) { + continue; + } + const source = statement.source?.value; + if (typeof source === 'string') { + modules.push(source); } } - return { exportNames: Array.from(exportNames), exportAllModules }; + return modules; +}; + +export const getExportNames = async ( + code: string +): Promise => { + return (await getExportNamesAndExportAll(code)).exportNames; +}; + +export const getExportNamesAndExportAll = async ( + code: string +): Promise => { + const cached = exportInfoCache.get(code); + if (cached) { + return cached; + } + + const exportInfo = (async () => { + const program = parseProgram(code); + return { + exportNames: collectProgramExportNames(program), + exportAllModules: collectExportAllModules(program), + }; + })(); + + let trackedExportInfo: Promise; + trackedExportInfo = cachePromiseOnReject(exportInfo, () => { + if (exportInfoCache.get(code) === trackedExportInfo) { + exportInfoCache.delete(code); + } + }); + + setBoundedCacheEntry( + exportInfoCache, + code, + trackedExportInfo, + MAX_EXPORT_UTILS_CACHE_ENTRIES + ); + return trackedExportInfo; }; -export const getRouteModuleExports = async ( +export const getRouteModuleAnalysis = async ( resourcePath: string -): Promise => { - const source = await readFile(resourcePath, 'utf8'); - const code = await transformToEsm(source, resourcePath); - return getExportNames(code); +): Promise => { + const stats = await stat(resourcePath); + const cached = routeModuleAnalysisCache.get(resourcePath); + if (cached?.mtimeMs === stats.mtimeMs && cached.size === stats.size) { + return cached.analysis; + } + + const analysis = (async () => { + const source = await readFile(resourcePath, 'utf8'); + const program = parseProgram(source, resourcePath); + return { + code: source, + exports: collectProgramExportNames(program), + exportAllModules: collectExportAllModules(program), + }; + })(); + + let trackedAnalysis: Promise; + trackedAnalysis = cachePromiseOnReject(analysis, () => { + if ( + routeModuleAnalysisCache.get(resourcePath)?.analysis === trackedAnalysis + ) { + routeModuleAnalysisCache.delete(resourcePath); + } + }); + + setBoundedCacheEntry( + routeModuleAnalysisCache, + resourcePath, + { + mtimeMs: stats.mtimeMs, + size: stats.size, + analysis: trackedAnalysis, + }, + MAX_EXPORT_UTILS_CACHE_ENTRIES + ); + return trackedAnalysis; }; diff --git a/src/index.ts b/src/index.ts index 358bbc2..31399ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,43 +1,36 @@ -import { existsSync, readFileSync, statSync } from 'node:fs'; -import { mkdir, readFile, writeFile } from 'node:fs/promises'; -import { createRequire } from 'node:module'; -import { pathToFileURL } from 'node:url'; +import { existsSync, readFileSync } from 'node:fs'; import fsExtra from 'fs-extra'; import type { Config } from './react-router-config.js'; import type { RouteConfigEntry } from '@react-router/dev/routes'; -import type { RsbuildPlugin, Rspack } from '@rsbuild/core'; +import { + rspack, + type RsbuildEntryDescription, + type RsbuildPlugin, + type Rspack, +} from '@rsbuild/core'; import { createJiti } from 'jiti'; -import jsesc from 'jsesc'; -import { basename as pathBasename, dirname, relative, resolve } from 'pathe'; -import { RspackVirtualModulePlugin } from 'rspack-plugin-virtual-module'; -import { generate, parse } from './babel.js'; +import { relative, resolve } from 'pathe'; + +import { getDefaultConcurrency } from './concurrency.js'; import { BUILD_CLIENT_ROUTE_QUERY_STRING, - CLIENT_ROUTE_EXPORTS, JS_EXTENSIONS, PLUGIN_NAME, - SERVER_ONLY_ROUTE_EXPORTS, } from './constants.js'; +import { guardReactRouterLazyCompilation } from './lazy-compilation.js'; import { createDevServerMiddleware } from './dev-server.js'; import { generateWithProps, - removeExports, - transformRoute, findEntryFile, normalizeAssetPrefix, - removeUnusedImports, } from './plugin-utils.js'; import type { PluginOptions } from './types.js'; import { generateServerBuild, - normalizeBuildModule, - resolveBuildExports, + resolveReactRouterServerBuild, } from './server-utils.js'; -import { - getPrerenderConcurrency, - resolvePrerenderPaths, - validatePrerenderConfig, -} from './prerender.js'; +import { resolvePrerenderPaths, validatePrerenderConfig } from './prerender.js'; +import { runReactRouterPrerenderBuild } from './prerender-build.js'; import { resolveReactRouterConfig, type ResolvedReactRouterConfig, @@ -45,36 +38,62 @@ import { import { getReactRouterManifestForDev, configRoutesToRouteManifest, + configRoutesToRouteManifestEntries, + createReactRouterManifestStats, + type ReactRouterManifestStats, + type RouteManifestModuleExports, } from './manifest.js'; -import { createModifyBrowserManifestPlugin } from './modify-browser-manifest.js'; -import { createRequestHandler, matchRoutes } from 'react-router'; -import { - getExportNames, - getExportNamesAndExportAll, - getRouteModuleExports, - transformToEsm, -} from './export-utils.js'; +import { registerModifyBrowserManifestAssets } from './modify-browser-manifest.js'; +import { registerBuildOutputTransforms } from './build-output-transforms.js'; import { - detectRouteChunksIfEnabled, getRouteChunkEntryName, - getRouteChunkIfEnabled, getRouteChunkModuleId, - getRouteChunkNameFromModuleId, routeChunkExportNames, - validateRouteChunks, type RouteChunkCache, type RouteChunkConfig, } from './route-chunks.js'; +import { createRouteTransformExecutor } from './parallel-route-transforms.js'; +import { + createRouteTopologyWatcher, + createRouteManifestSnapshot, + ensureDevRestartMarker, + getRouteRestartMarkerPath, + mergeWatchFiles, + type WatchFileConfig, +} from './route-watch.js'; import { validateRouteConfig } from './route-config.js'; import { getBuildManifest, getRoutesByServerBundleId, } from './build-manifest.js'; +import { + createReactRouterNodeEntries, + createReactRouterServerBuildPlan, +} from './server-build-plan.js'; import { warnOnClientSourceMaps } from './warnings/warn-on-client-source-maps.js'; import { validatePluginOrderFromConfig } from './validation/validate-plugin-order.js'; import { getSsrExternals } from './ssr-externals.js'; - -const redirectStatusCodes = new Set([301, 302, 303, 307, 308]); +import { + createReactRouterPerformanceProfiler, + roundMs, +} from './performance.js'; +import { mapVirtualModules } from './virtual-modules.js'; +import { createReactRouterDevRuntimeController } from './dev-runtime-controller.js'; +import { registerReactRouterTypegen } from './typegen.js'; + +export { loadReactRouterServerBuild } from './dev-generation.js'; +export { resolveReactRouterServerBuild }; + +const MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES = 4; + +export const shouldParallelizeEnvironmentBuilds = ({ + isBuild, + spareCoreCount = getDefaultConcurrency(), +}: { + isBuild: boolean; + spareCoreCount?: number; +}): boolean => + !isBuild && spareCoreCount >= MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES; type ModuleFederationPluginLike = { name?: string; @@ -112,6 +131,10 @@ const ensureFederationAsyncStartup = ( } }; +const cssUrlAssetExtensions = + /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)$/; +const urlAssetResourceQuery = /(?:\?|&)url(?:&|$)/; + export const pluginReactRouter = ( options: PluginOptions = {} ): RsbuildPlugin => ({ @@ -127,7 +150,12 @@ export const pluginReactRouter = ( ...defaultOptions, ...options, }; - + const logPerformance = pluginOptions.logPerformance === true; + const setupStartMs = logPerformance ? performance.now() : 0; + const performanceProfiler = createReactRouterPerformanceProfiler({ + enabled: logPerformance, + log: message => api.logger.info(message), + }); const nodeExternals = Array.from( new Set(['express', ...getSsrExternals(process.cwd())]) ); @@ -159,38 +187,20 @@ export const pluginReactRouter = ( warnOnClientSourceMaps(normalized, msg => api.logger.warn(msg), 'web'); }); - // Run typegen on build/dev - api.onBeforeStartDevServer(async () => { - const { execa } = await import('execa'); - // Run typegen in background (non-blocking) for watch mode - const child = execa( - 'npx', - ['--yes', 'react-router', 'typegen', '--watch'], - { - stdio: 'inherit', - detached: false, - cleanup: true, - } - ); - // Don't await - let it run in the background - child.catch(() => { - // Silently ignore errors when the process is killed on server shutdown - }); - }); + registerReactRouterTypegen(api); - api.onBeforeBuild(async () => { - const { execa } = await import('execa'); - // Run typegen synchronously before build - await execa('npx', ['--yes', 'react-router', 'typegen'], { - stdio: 'inherit', - }); + const jiti = createJiti(process.cwd(), { + moduleCache: false, }); - const jiti = createJiti(process.cwd()); - // Read the react-router.config file first (supports .ts, .js, .mjs, etc.) const configPath = findEntryFile(resolve('react-router.config')); const configExists = existsSync(configPath); + const configWatchPaths = configExists + ? configPath + : JS_EXTENSIONS.map(extension => + resolve(`react-router.config${extension}`) + ); let reactRouterUserConfig: Config = {}; if (!configExists) { console.warn( @@ -214,8 +224,11 @@ export const pluginReactRouter = ( } } - const { resolved: resolvedConfig, presets: configPresets } = - await resolveReactRouterConfig(reactRouterUserConfig); + const { + resolved: resolvedConfig, + presets: configPresets, + hasConfiguredServerModuleFormat, + } = await resolveReactRouterConfig(reactRouterUserConfig); const { appDirectory, @@ -228,7 +241,7 @@ export const pluginReactRouter = ( prerender: prerenderConfig, serverBuildFile, serverModuleFormat, - serverBundles, + splitRouteModules, buildEnd, } = resolvedConfig; @@ -236,21 +249,22 @@ export const pluginReactRouter = ( options, 'serverOutput' ); - const resolvedServerOutput = hasExplicitServerOutput - ? options.serverOutput - : serverModuleFormat === 'cjs' - ? 'commonjs' - : 'module'; + let resolvedServerOutput = pluginOptions.serverOutput; + if (!hasExplicitServerOutput) { + resolvedServerOutput = + serverModuleFormat === 'cjs' ? 'commonjs' : 'module'; + } if ( hasExplicitServerOutput && + hasConfiguredServerModuleFormat && serverModuleFormat && - (options.serverOutput === 'commonjs' ? 'cjs' : 'esm') !== + (resolvedServerOutput === 'commonjs' ? 'cjs' : 'esm') !== serverModuleFormat ) { api.logger.warn( `[${PLUGIN_NAME}] Both \`serverOutput\` and \`serverModuleFormat\` are set. ` + - `Using \`serverOutput=${options.serverOutput}\` and ignoring ` + + `Using \`serverOutput=${resolvedServerOutput}\` and ignoring ` + `\`serverModuleFormat=${serverModuleFormat}\`.` ); } @@ -265,14 +279,6 @@ export const pluginReactRouter = ( ); } - if (serverBundles) { - api.logger.warn( - `[${PLUGIN_NAME}] \`serverBundles\` is configured. Rsbuild currently ` + - 'emits a single server bundle, but the build manifest will include the ' + - 'server bundle mapping for compatibility.' - ); - } - const prerenderConfigError = validatePrerenderConfig(prerenderConfig); if (prerenderConfigError) { throw new Error(prerenderConfigError); @@ -314,21 +320,24 @@ export const pluginReactRouter = ( ); } - const routeConfigExport = await jiti.import( - routesPath, - { - default: true, + const loadRouteConfig = async (): Promise => { + const routeConfigExport = await jiti.import( + routesPath, + { + default: true, + } + ); + const routeConfigValue = await routeConfigExport; + const validation = validateRouteConfig({ + routeConfigFile: relative(process.cwd(), routesPath), + routeConfig: routeConfigValue, + }); + if (!validation.valid) { + throw new Error(validation.message); } - ); - const routeConfigValue = await routeConfigExport; - const validation = validateRouteConfig({ - routeConfigFile: relative(process.cwd(), routesPath), - routeConfig: routeConfigValue, - }); - if (!validation.valid) { - throw new Error(validation.message); - } - const routeConfig = validation.routeConfig; + return validation.routeConfig; + }; + const routeConfig = await loadRouteConfig(); const entryClientPath = findEntryFile( resolve(appDirectory, 'entry.client') @@ -342,6 +351,9 @@ export const pluginReactRouter = ( resolve(appDirectory, '../server/index') ); const hasServerApp = existsSync(serverAppPath); + const devServerBuildEntryName = hasServerApp + ? 'static/js/react-router-server-build' + : 'static/js/app'; // Add fallback logic for entry files const templateDir = resolve(__dirname, 'templates'); @@ -356,10 +368,27 @@ export const pluginReactRouter = ( ? entryServerPath : templateServerPath; - const rootRoutePath = findEntryFile(resolve(appDirectory, 'root')); + const getRootRoutePath = () => findEntryFile(resolve(appDirectory, 'root')); + const rootRoutePath = getRootRoutePath(); // React Router's server build expects route files relative to `appDirectory` // so it can resolve them correctly during compilation. const rootRouteFile = relative(appDirectory, rootRoutePath); + const createRouteTopologySnapshot = ( + routeFile: string, + routeConfig: RouteConfigEntry[] + ) => + createRouteManifestSnapshot([ + ['root', { path: '', id: 'root', file: routeFile }], + ...configRoutesToRouteManifestEntries(appDirectory, routeConfig), + ]); + const getWatchedRouteTopology = async (): Promise> => { + const latestRouteConfig = await loadRouteConfig(); + const latestRootRouteFile = relative(appDirectory, getRootRoutePath()); + return createRouteTopologySnapshot( + latestRootRouteFile, + latestRouteConfig + ); + }; const routes = { root: { path: '', id: 'root', file: rootRouteFile }, @@ -386,90 +415,217 @@ export const pluginReactRouter = ( } const isBuild = api.context.action === 'build'; - const splitRouteModules = resolvedConfigWithRoutes.splitRouteModules; - const enforceSplitRouteModules = splitRouteModules === 'enforce'; + const shouldDependOnWebCompiler = !shouldParallelizeEnvironmentBuilds({ + isBuild, + }); + const isPrerenderEnabled = + prerenderConfig !== undefined && prerenderConfig !== false; + const isSpaMode = !ssr && !isPrerenderEnabled; + const routeCount = Object.keys(routes).length; const routeChunkConfig: RouteChunkConfig = { splitRouteModules, appDirectory, rootRouteFile, }; const routeChunkCache: RouteChunkCache = new Map(); + const routeTransformExecutor = createRouteTransformExecutor({ + parallelTransforms: pluginOptions.parallelTransforms, + routeChunkCache, + splitRouteModules: Boolean(splitRouteModules), + }); const routeChunkOptions = { splitRouteModules, rootRouteFile, isBuild, cache: routeChunkCache, }; + const outputClientPath = resolve(buildDirectory, 'client'); + const assetsBuildDirectory = relative(process.cwd(), outputClientPath); + const watchDirectory = resolve(appDirectory); + const routeRestartMarkerPath = getRouteRestartMarkerPath(outputClientPath); + const routeTopologyWatchFiles: WatchFileConfig[] = + pluginOptions.onRouteTopologyChange + ? [] + : [ + { + paths: routesPath, + type: 'reload-server', + }, + { + paths: routeRestartMarkerPath, + type: 'reload-server', + }, + ]; + const routeWatchFiles: WatchFileConfig[] = [ + { + paths: configWatchPaths, + type: 'reload-server', + }, + ...routeTopologyWatchFiles, + ]; + let closeRouteTopologyWatcher: (() => Promise) | undefined; + + api.onBeforeStartDevServer(async () => { + await ensureDevRestartMarker(routeRestartMarkerPath); + closeRouteTopologyWatcher = await createRouteTopologyWatcher({ + watchDirectory, + getRouteTopology: getWatchedRouteTopology, + initialRouteTopology: createRouteTopologySnapshot( + rootRouteFile, + routeConfig + ), + restartMarkerPath: routeRestartMarkerPath, + onRouteTopologyChange: pluginOptions.onRouteTopologyChange, + onError: error => { + api.logger.warn( + `[${PLUGIN_NAME}] Failed to watch route topology changes: ${error}` + ); + }, + }); + }); + + api.onCloseDevServer(async () => { + await closeRouteTopologyWatcher?.(); + closeRouteTopologyWatcher = undefined; + }); + api.onCloseBuild(async () => { + await routeTransformExecutor.close(); + }); + api.onCloseDevServer(async () => { + await routeTransformExecutor.close(); + }); type ReactRouterManifest = Awaited< ReturnType >; + let latestBrowserManifest: ReactRouterManifest | null = null; + let latestBrowserManifestModuleExports: RouteManifestModuleExports = {}; let latestServerManifest: ReactRouterManifest | null = null; const latestServerManifestsByBundleId: Record = {}; + const stageLatestManifests = ( + manifest: ReactRouterManifest, + sri: Record | undefined, + moduleExportsByRouteId: RouteManifestModuleExports, + compilation: Rspack.Compilation + ) => { + performanceProfiler.recordSync( + 'web', + 'manifest:stage', + 'virtual/react-router/browser-manifest', + () => { + latestBrowserManifest = manifest; + latestBrowserManifestModuleExports = moduleExportsByRouteId; + const baseServerManifest = { + ...manifest, + sri, + }; + latestServerManifest = baseServerManifest; + const manifestsByEntryName: Record = { + [devServerBuildEntryName]: baseServerManifest, + }; + + for (const { bundleId, entryName } of serverBundleEntries) { + const bundleRoutes = routesByServerBundleId[bundleId]; + if (!bundleRoutes) { + continue; + } + + const routeIds = new Set(Object.keys(bundleRoutes)); + const filteredRoutes = Object.fromEntries( + Object.entries(manifest.routes).filter(([routeId]) => + routeIds.has(routeId) + ) + ); + const bundleManifest = { + ...baseServerManifest, + routes: filteredRoutes, + }; + latestServerManifestsByBundleId[bundleId] = bundleManifest; + manifestsByEntryName[entryName] = bundleManifest; + } + + if (!isBuild) { + devRuntime.captureWeb(compilation, manifestsByEntryName); + } + } + ); + }; + const routeByFilePath = new Map( Object.values(routes).map(route => [ resolve(appDirectory, route.file), route, ]) ); - const routeExportsCache = new Map(); - const getCachedRouteExports = async (filePath: string) => { - if (routeExportsCache.has(filePath)) { - return routeExportsCache.get(filePath)!; - } - const exports = await getRouteModuleExports(filePath); - routeExportsCache.set(filePath, exports); - return exports; - }; + const manifestChunkNames = new Set(['entry.client']); const webRouteEntries = Object.values(routes).reduce( (acc, route) => { const entryName = route.file.slice(0, route.file.lastIndexOf('.')); const routeFilePath = resolve(appDirectory, route.file); + manifestChunkNames.add(entryName); acc[entryName] = { import: `${routeFilePath}${BUILD_CLIENT_ROUTE_QUERY_STRING}`, + html: false, }; if (isBuild && splitRouteModules && route.id !== 'root') { - let source = ''; + let source: string; try { source = readFileSync(routeFilePath, 'utf8'); - } catch { - source = ''; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return acc; + } + throw error; } - if (source) { - for (const exportName of routeChunkExportNames) { - if (!source.includes(exportName)) { - continue; - } - acc[getRouteChunkEntryName(route.id, exportName)] = { - import: getRouteChunkModuleId(routeFilePath, exportName), - }; + for (const exportName of routeChunkExportNames) { + if (!source.includes(exportName)) { + continue; } + const chunkEntryName = getRouteChunkEntryName(route.id, exportName); + manifestChunkNames.add(chunkEntryName); + acc[chunkEntryName] = { + import: getRouteChunkModuleId(routeFilePath, exportName), + html: false, + }; } } return acc; }, - {} as Record + {} as Record ); - const buildManifest = await getBuildManifest({ reactRouterConfig: resolvedConfigWithRoutes, routes, rootDirectory: process.cwd(), }); - const routesByServerBundleId = getRoutesByServerBundleId(buildManifest); - - const outputClientPath = resolve(buildDirectory, 'client'); - const assetsBuildDirectory = relative(process.cwd(), outputClientPath); + const routesByServerBundleId = getRoutesByServerBundleId( + buildManifest, + routes + ); + const serverBuildPlan = createReactRouterServerBuildPlan({ + routesByServerBundleId, + serverBuildFile, + defaultEntryName: devServerBuildEntryName, + }); + const { serverBundleEntries } = serverBuildPlan; + const devRuntime = createReactRouterDevRuntimeController({ + api, + isBuild, + buildPlan: serverBuildPlan, + }); - let clientStats: Rspack.StatsCompilation | undefined; + let clientStats: ReactRouterManifestStats | undefined; api.onAfterEnvironmentCompile(({ stats, environment }) => { if (environment.name === 'web') { - clientStats = stats?.toJson(); + clientStats = createReactRouterManifestStats( + stats?.compilation, + manifestChunkNames + ); } if (pluginOptions.federation && ssr) { const serverBuildDir = resolve(buildDirectory, 'server'); @@ -479,6 +635,11 @@ export const pluginReactRouter = ( fsExtra.copySync(serverBuildDir, ssrDir); } } + if (logPerformance) { + performanceProfiler.flush(environment.name, { + compilerLifecycleMs: roundMs(performance.now() - setupStartMs), + }); + } }); // Determine prerender paths from config @@ -491,523 +652,37 @@ export const pluginReactRouter = ( warn: message => api.logger.warn(message), } ); - const isPrerenderEnabled = - prerenderConfig !== undefined && prerenderConfig !== false; - const isSpaMode = !ssr && !isPrerenderEnabled; - - const groupRoutesByParentId = (manifest: Record) => { - const grouped: Record = {}; - Object.values(manifest).forEach(route => { - if (!route) return; - const parentId = route.parentId || ''; - if (!grouped[parentId]) { - grouped[parentId] = []; - } - grouped[parentId].push(route); - }); - return grouped; - }; - type MatchRouteObject = - Parameters[0] extends Array ? R : never; - - const createPrerenderRoutes = ( - manifest: Record, - parentId = '', - grouped = groupRoutesByParentId(manifest) - ): MatchRouteObject[] => { - return (grouped[parentId] || []).map(route => { - const common = { id: route.id, path: route.path }; - if (route.index) { - return { index: true, ...common } as MatchRouteObject; - } - return { - ...common, - children: createPrerenderRoutes(manifest, route.id, grouped), - } as MatchRouteObject; - }); - }; - - const normalizePrerenderMatchPath = (path: string) => - `/${path}/`.replace(/^\/\/+/, '/'); - - const prerenderData = async ( - handler: (request: Request) => Promise, - prerenderPath: string, - onlyRoutes: string[] | null, - clientBuildDir: string, - requestInit?: RequestInit - ): Promise => { - let dataRequestPath: string; - if (future?.unstable_trailingSlashAwareDataRequests) { - if (prerenderPath.endsWith('/')) { - dataRequestPath = `${prerenderPath}_.data`; - } else { - dataRequestPath = `${prerenderPath}.data`; - } - } else { - dataRequestPath = - prerenderPath === '/' - ? '/_root.data' - : `${prerenderPath.replace(/\/$/, '')}.data`; - } - - const normalizedPath = `${basename}${dataRequestPath}`.replace( - /\/\/+/g, - '/' - ); - const url = new URL(`http://localhost${normalizedPath}`); - if (onlyRoutes?.length) { - url.searchParams.set('_routes', onlyRoutes.join(',')); - } - const request = new Request(url, requestInit); - const response = await handler(request); - const data = await response.text(); - - if (response.status !== 200 && response.status !== 202) { - throw new Error( - `Prerender (data): Received a ${response.status} status code from ` + - `\`entry.server.tsx\` while prerendering the \`${prerenderPath}\` path.\n` + - `${normalizedPath}` - ); - } - - const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/')); - await mkdir(dirname(outputPath), { recursive: true }); - await writeFile(outputPath, data); - api.logger.info( - `Prerender (data): ${prerenderPath} -> ${relative( - process.cwd(), - outputPath - )}` - ); - return data; - }; - - const prerenderRoute = async ( - handler: (request: Request) => Promise, - prerenderPath: string, - clientBuildDir: string, - requestInit?: RequestInit - ): Promise => { - const normalizedPath = `${basename}${prerenderPath}/`.replace( - /\/\/+/g, - '/' - ); - const request = new Request( - `http://localhost${normalizedPath}`, - requestInit - ); - const response = await handler(request); - let html = await response.text(); - - if (redirectStatusCodes.has(response.status)) { - const location = response.headers.get('Location'); - const delay = response.status === 302 ? 2 : 0; - html = ` - -Redirecting to: ${location} - - - - -\t - Redirecting from ${normalizedPath} to ${location} - - -`; - } else if (response.status !== 200) { - throw new Error( - `Prerender (html): Received a ${response.status} status code from ` + - `\`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.\n` + - html - ); - } - - const outputPath = resolve( - clientBuildDir, - ...normalizedPath.split('/'), - 'index.html' - ); - await mkdir(dirname(outputPath), { recursive: true }); - await writeFile(outputPath, html); - api.logger.info( - `Prerender (html): ${prerenderPath} -> ${relative( - process.cwd(), - outputPath - )}` - ); - }; - - const prerenderResourceRoute = async ( - handler: (request: Request) => Promise, - prerenderPath: string, - clientBuildDir: string, - requestInit?: RequestInit - ): Promise => { - const normalizedPath = `${basename}${prerenderPath}/` - .replace(/\/\/+/g, '/') - .replace(/\/$/g, ''); - const request = new Request( - `http://localhost${normalizedPath}`, - requestInit - ); - const response = await handler(request); - const content = Buffer.from(await response.arrayBuffer()); - - if (response.status !== 200) { - throw new Error( - `Prerender (resource): Received a ${response.status} status code from ` + - `\`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.\n` + - content.toString('utf8') - ); - } - - const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/')); - await mkdir(dirname(outputPath), { recursive: true }); - await writeFile(outputPath, content); - api.logger.info( - `Prerender (resource): ${prerenderPath} -> ${relative( - process.cwd(), - outputPath - )}` - ); - }; - - const handleSpaMode = async ( - handler: (request: Request) => Promise, - build: any, - clientBuildDir: string - ): Promise => { - const request = new Request(`http://localhost${basename}`, { - headers: { - 'X-React-Router-SPA-Mode': 'yes', - }, - }); - const response = await handler(request); - const html = await response.text(); - const isPrerenderSpaFallback = build.prerender?.includes('/'); - const filename = isPrerenderSpaFallback - ? '__spa-fallback.html' - : 'index.html'; - - if (response.status !== 200) { - if (isPrerenderSpaFallback) { - throw new Error( - `Prerender: Received a ${response.status} status code from ` + - `\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` + - html - ); - } - throw new Error( - `SPA Mode: Received a ${response.status} status code from ` + - `\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` + - html - ); - } - - if ( - !html.includes('window.__reactRouterContext =') || - !html.includes('window.__reactRouterRouteModules =') - ) { - throw new Error( - 'SPA Mode: Did you forget to include `` in your root route? ' + - 'Your pre-rendered HTML cannot hydrate without ``.' - ); - } - - const outputPath = resolve(clientBuildDir, filename); - await writeFile(outputPath, html); - const prettyPath = relative(process.cwd(), outputPath); - if (build.prerender?.length) { - api.logger.info(`Prerender (html): SPA Fallback -> ${prettyPath}`); - } else { - api.logger.info(`SPA Mode: Generated ${prettyPath}`); - } - }; - - const validateSsrFalsePrerenderExports = async ( - manifest: Awaited>, - prerenderList: string[] - ) => { - if (prerenderList.length === 0) { - return; - } - - const prerenderRoutes = createPrerenderRoutes(routes); - const prerenderedRoutes = new Set(); - for (const path of prerenderList) { - const matches = matchRoutes( - prerenderRoutes, - normalizePrerenderMatchPath(path) - ); - if (!matches) { - throw new Error( - `Unable to prerender path because it does not match any routes: ${path}` - ); - } - matches.forEach(match => - prerenderedRoutes.add(match.route.id as string) - ); - } - - const routeExports: Record = {}; - for (const route of Object.values(routes)) { - const filePath = resolve(appDirectory, route.file); - routeExports[route.id] = await getRouteModuleExports(filePath); - } - - const errors: string[] = []; - for (const [routeId, route] of Object.entries(manifest.routes)) { - const exports = routeExports[routeId] ?? []; - const invalidApis: string[] = []; - - if (exports.includes('headers')) invalidApis.push('headers'); - if (exports.includes('action')) invalidApis.push('action'); - - if (invalidApis.length > 0) { - errors.push( - `Prerender: ${invalidApis.length} invalid route export(s) in ` + - `\`${routeId}\` when pre-rendering with \`ssr:false\`: ` + - `${invalidApis.map(api => `\`${api}\``).join(', ')}. ` + - `See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.` - ); - } - - if (!prerenderedRoutes.has(routeId)) { - if (exports.includes('loader')) { - errors.push( - `Prerender: 1 invalid route export in \`${routeId}\` when pre-rendering with ` + - `\`ssr:false\`: \`loader\`. ` + - `See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.` - ); - } - - let parentRoute = - route.parentId && manifest.routes[route.parentId] - ? manifest.routes[route.parentId] - : null; - while (parentRoute && parentRoute.id !== 'root') { - if (parentRoute.hasLoader && !parentRoute.hasClientLoader) { - errors.push( - `Prerender: 1 invalid route export in \`${parentRoute.id}\` when ` + - `pre-rendering with \`ssr:false\`: \`loader\`. ` + - `See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.` - ); - } - parentRoute = - parentRoute.parentId && parentRoute.parentId !== 'root' - ? manifest.routes[parentRoute.parentId] - : null; - } - } - } - - if (errors.length > 0) { - api.logger.error(errors.join('\n')); - throw new Error( - 'Invalid route exports found when prerendering with `ssr:false`' - ); - } - }; - - // Handle SPA mode and prerendering after build api.onAfterBuild(async ({ environments }) => { - const webEnv = environments.web; - if (!webEnv) { - return; - } - - const serverBuildDir = resolve(buildDirectory, 'server'); - const defaultServerBuildFile = 'static/js/app.js'; - const configuredServerBuildFile = serverBuildFile || 'index.js'; - const configuredServerBuildPath = resolve( - serverBuildDir, - configuredServerBuildFile - ); - const defaultServerBuildPath = resolve( - serverBuildDir, - defaultServerBuildFile - ); - if ( - configuredServerBuildFile !== defaultServerBuildFile && - existsSync(defaultServerBuildPath) && - !existsSync(configuredServerBuildPath) - ) { - await mkdir(dirname(configuredServerBuildPath), { recursive: true }); - await fsExtra.copy(defaultServerBuildPath, configuredServerBuildPath); - } - const serverBuildPath = existsSync(configuredServerBuildPath) - ? configuredServerBuildPath - : defaultServerBuildPath; - const clientBuildDir = resolve(buildDirectory, 'client'); - - if (!existsSync(serverBuildPath)) { - console.warn( - `[${PLUGIN_NAME}] Server build not found at ${serverBuildPath}. ` + - 'Skipping prerendering.' - ); - return; - } - - await mkdir(clientBuildDir, { recursive: true }); - - if (!ssr || isPrerenderEnabled) { - process.env.IS_RR_BUILD_REQUEST = 'yes'; - const buildModule = await import( - pathToFileURL(serverBuildPath).toString() - ); - const normalizedBuild = normalizeBuildModule(buildModule as any); - const build = await resolveBuildExports(normalizedBuild); - const requestHandler = createRequestHandler(build, 'production'); - - if (isPrerenderEnabled) { - const manifest = await getReactRouterManifestForDev( - routes, - pluginOptions, - clientStats, - appDirectory, - assetPrefix, - routeChunkOptions - ); - if (!ssr) { - await validateSsrFalsePrerenderExports(manifest, prerenderPaths); - } - - const routeTree = createPrerenderRoutes(routes); - for (const path of prerenderPaths) { - const matches = matchRoutes( - routeTree, - normalizePrerenderMatchPath(path) - ); - if (!matches) { - throw new Error( - `Unable to prerender path because it does not match any routes: ${path}` - ); - } - } - - if (prerenderPaths.length > 0) { - api.logger.info( - `Prerender (html): ${prerenderPaths.length} path(s)...` - ); - } - - const buildRoutes = createPrerenderRoutes(build.routes); - const concurrency = getPrerenderConcurrency(prerenderConfig); - const pending = new Set>(); - const enqueue = async (path: string) => { - const matches = matchRoutes( - buildRoutes, - normalizePrerenderMatchPath(path) - ); - if (!matches) return; - - const leafRoute = matches[matches.length - 1]?.route as any; - const manifestRoute = leafRoute - ? build.routes?.[leafRoute.id]?.module - : null; - const isResourceRoute = - manifestRoute && - !manifestRoute.default && - !manifestRoute.ErrorBoundary; - - if (isResourceRoute) { - if (manifestRoute.loader) { - await prerenderData( - requestHandler, - path, - [leafRoute.id], - clientBuildDir - ); - await prerenderResourceRoute( - requestHandler, - path, - clientBuildDir - ); - } else { - api.logger.warn( - `⚠️ Skipping prerendering for resource route without a loader: ${leafRoute?.id}` - ); - } - } else { - const hasLoaders = matches.some(match => { - const routeId = match.route.id; - if (!routeId) { - return false; - } - return build.assets?.routes?.[routeId]?.hasLoader; - }); - let data: string | undefined; - if (hasLoaders) { - data = await prerenderData( - requestHandler, - path, - null, - clientBuildDir - ); - } - await prerenderRoute( - requestHandler, - path, - clientBuildDir, - data - ? { - headers: { - 'X-React-Router-Prerender-Data': encodeURI(data), - }, - } - : undefined - ); - } - }; - - for (const path of prerenderPaths) { - const task = enqueue(path); - pending.add(task); - task.finally(() => pending.delete(task)); - if (pending.size >= concurrency) { - await Promise.race(pending); - } - } - await Promise.all(pending); - } - - if (!ssr) { - await handleSpaMode(requestHandler, build, clientBuildDir); - } - } - - // Remove server output for SPA mode and when not using SSR - // This makes the build deployable as static assets - if (!ssr) { - await fsExtra.remove(serverBuildDir); - api.logger.info( - `[${PLUGIN_NAME}] Removed server build (static deployment)` - ); - } - - if (buildEnd) { - const buildManifest = await getBuildManifest({ - reactRouterConfig: resolvedConfigWithRoutes, - routes, - rootDirectory: process.cwd(), - }); - await buildEnd({ - buildManifest, - reactRouterConfig: resolvedConfigWithRoutes, - viteConfig: api.getNormalizedConfig(), - }); - } + await runReactRouterPrerenderBuild({ + api, + hasWebEnvironment: Boolean(environments.web), + buildDirectory, + serverBuildFile, + ssr, + isPrerenderEnabled, + prerenderConfig, + prerenderPaths, + basename, + future, + routes, + latestBrowserManifest, + latestBrowserManifestModuleExports, + clientStats, + pluginOptions, + appDirectory, + assetPrefix, + routeChunkOptions, + buildManifest, + resolvedConfigWithRoutes, + buildEnd, + }); }); const allowedActionOriginsForBuild = allowedActionOrigins === false ? undefined : allowedActionOrigins; - // Create virtual modules for React Router - const vmodTempDir = `rspack-virtual-module-${process.pid}-${Math.random() - .toString(16) - .slice(2)}`; + // Public requests stay bare while Rspack resolves seeded virtual files. const createVirtualModulePlugin = (publicPath: string) => { const bundleVirtualModules = Object.fromEntries( Object.entries(routesByServerBundleId).map( @@ -1042,8 +717,8 @@ export const pluginReactRouter = ( ]) ); - return new RspackVirtualModulePlugin( - { + return new rspack.experiments.VirtualModulesPlugin( + mapVirtualModules({ 'virtual/react-router/browser-manifest': 'export default {};', 'virtual/react-router/server-manifest': 'export default {};', 'virtual/react-router/server-build': generateServerBuild(routes, { @@ -1062,8 +737,7 @@ export const pluginReactRouter = ( ...bundleVirtualModules, ...bundleManifestModules, 'virtual/react-router/with-props': generateWithProps(), - }, - vmodTempDir + }) ); }; @@ -1072,52 +746,70 @@ export const pluginReactRouter = ( const vmodPlugin = createVirtualModulePlugin(assetPrefix); const useAsyncNodeChunkLoading = options.federation && resolvedServerOutput === 'commonjs'; - const nodeChunkLoading = - resolvedServerOutput === 'module' - ? 'import' - : useAsyncNodeChunkLoading - ? 'async-node' - : 'require'; - const serverBuildFileBase = (serverBuildFile || 'index.js').replace( - /\.js$/, - '' - ); + let nodeChunkLoading: 'import' | 'async-node' | 'require' = 'require'; + if (resolvedServerOutput === 'module') { + nodeChunkLoading = 'import'; + } else if (useAsyncNodeChunkLoading) { + nodeChunkLoading = 'async-node'; + } + const nodeEntries = createReactRouterNodeEntries({ + hasServerApp, + isBuild, + serverAppPath, + entryServerPath: finalEntryServerPath, + defaultEntryName: devServerBuildEntryName, + serverBundleEntries, + }); - const nodeEntries: Record = { - ...(hasServerApp - ? { - 'static/js/app': serverAppPath, - } - : { - 'static/js/app': 'virtual/react-router/server-build', - }), - 'static/js/entry.server': finalEntryServerPath, - }; - - for (const [bundleId, bundleRoutes] of Object.entries( - routesByServerBundleId - )) { - if (!bundleRoutes || Object.keys(bundleRoutes).length === 0) { - continue; - } - nodeEntries[`${bundleId}/${serverBuildFileBase}`] = - `virtual/react-router/server-build-${bundleId}`; - } + const configuredLazyCompilation = + pluginOptions.lazyCompilation === undefined + ? config.dev?.lazyCompilation + : pluginOptions.lazyCompilation; + const guardedLazyCompilation = guardReactRouterLazyCompilation({ + lazyCompilation: configuredLazyCompilation, + entryClientPath: finalEntryClientPath, + }); + const lazyCompilation = + guardedLazyCompilation === undefined + ? {} + : { lazyCompilation: guardedLazyCompilation }; + const shouldCompactFileSizeReport = + isBuild && + routeCount >= 256 && + (config.performance?.printFileSize === undefined || + config.performance.printFileSize === true); return mergeRsbuildConfig(config, { + ...(shouldCompactFileSizeReport + ? { + performance: { + printFileSize: { + total: true, + detail: false, + compressed: false, + }, + }, + } + : {}), output: { assetPrefix: config.output?.assetPrefix || '/', }, dev: { writeToDisk: true, + ...lazyCompilation, + watchFiles: mergeWatchFiles(config.dev?.watchFiles, routeWatchFiles), // Only add SSR middleware if SSR is enabled and not using a custom server // In SPA mode (ssr: false), we just serve static files from the client build setupMiddlewares: pluginOptions.customServer || !ssr ? [] : [ - (middlewares, server) => { - middlewares.push(createDevServerMiddleware(server)); + middlewares => { + middlewares.push( + createDevServerMiddleware({ + loadBuild: devRuntime.createBuildLoader(), + }) + ); }, ], }, @@ -1132,8 +824,10 @@ export const pluginReactRouter = ( entry: { // no query needed when federation is disabled 'entry.client': finalEntryClientPath, - 'virtual/react-router/browser-manifest': - 'virtual/react-router/browser-manifest', + 'virtual/react-router/browser-manifest': { + import: 'virtual/react-router/browser-manifest', + html: false, + }, ...webRouteEntries, }, }, @@ -1148,6 +842,15 @@ export const pluginReactRouter = ( tools: { rspack: { name: 'web', + module: { + rules: [ + { + resourceQuery: urlAssetResourceQuery, + exclude: cssUrlAssetExtensions, + type: 'asset/resource', + }, + ], + }, ...(options.federation ? { output: { @@ -1165,6 +868,7 @@ export const pluginReactRouter = ( module: true, }, optimization: { + avoidEntryIife: true, runtimeChunk: 'single', }, }, @@ -1173,42 +877,44 @@ export const pluginReactRouter = ( // Always include node environment, even for SPA mode (`ssr:false`), // because React Router still needs a server build to prerender the // root route into a hydratable `index.html` at build time. - ...(true - ? { - node: { - source: { - entry: nodeEntries, - }, - output: { - distPath: { - root: resolve(buildDirectory, 'server'), - }, - target: config.environments?.node?.output?.target || 'node', - filename: { - js: '[name].js', - }, - }, - tools: { - rspack: { - target: options.federation ? 'async-node' : 'node', - externals: nodeExternals, - dependencies: ['web'], - externalsType: resolvedServerOutput, - output: { - chunkFormat: resolvedServerOutput, - chunkLoading: nodeChunkLoading, - workerChunkLoading: nodeChunkLoading, - wasmLoading: 'fetch', - module: resolvedServerOutput === 'module', - }, - // optimization: { - // runtimeChunk: 'single', - // }, + node: { + source: { + entry: nodeEntries, + }, + output: { + distPath: { + root: resolve(buildDirectory, 'server'), + }, + target: config.environments?.node?.output?.target || 'node', + filename: { + js: '[name].js', + }, + }, + tools: { + rspack: { + target: options.federation ? 'async-node' : 'node', + module: { + rules: [ + { + resourceQuery: urlAssetResourceQuery, + exclude: cssUrlAssetExtensions, + type: 'asset/resource', }, - }, + ], }, - } - : {}), + externals: nodeExternals, + ...(shouldDependOnWebCompiler ? { dependencies: ['web'] } : {}), + externalsType: resolvedServerOutput, + output: { + chunkFormat: resolvedServerOutput, + chunkLoading: nodeChunkLoading, + workerChunkLoading: nodeChunkLoading, + wasmLoading: 'fetch', + module: resolvedServerOutput === 'module', + }, + }, + }, + }, }, }); }); @@ -1227,64 +933,29 @@ export const pluginReactRouter = ( ensureFederationAsyncStartup(rspackConfig); } - if (name === 'node' && resolvedServerOutput === 'module') { + if (name === 'node') { const output = rspackConfig.output; - const library = output?.library; - const libraryType = - library && - typeof library === 'object' && - !Array.isArray(library) && - 'type' in library - ? library.type - : undefined; - if (output && libraryType === 'commonjs2') { + if (output) { + const library = output.library; + const libraryOptions = + library && + typeof library === 'object' && + !Array.isArray(library) + ? library + : {}; rspackConfig.output = { ...output, - library: { type: 'module' }, + library: { + ...libraryOptions, + type: + resolvedServerOutput === 'module' + ? 'module' + : 'commonjs2', + }, }; } } - if (name === 'web' && rspackConfig.plugins) { - rspackConfig.plugins.push( - createModifyBrowserManifestPlugin( - routes, - pluginOptions, - appDirectory, - assetPrefix, - routeChunkOptions, - { - subResourceIntegrity: - resolvedConfigWithRoutes.subResourceIntegrity, - future, - onManifest: (manifest, sri) => { - const baseServerManifest = { - ...manifest, - sri, - }; - latestServerManifest = baseServerManifest; - for (const [bundleId, bundleRoutes] of Object.entries( - routesByServerBundleId - )) { - if (!bundleRoutes) { - continue; - } - const routeIds = new Set(Object.keys(bundleRoutes)); - const filteredRoutes = Object.fromEntries( - Object.entries(manifest.routes).filter( - ([routeId]) => routeIds.has(routeId) - ) - ); - latestServerManifestsByBundleId[bundleId] = { - ...baseServerManifest, - routes: filteredRoutes, - }; - } - }, - } - ) - ); - } return rspackConfig; }, }, @@ -1292,466 +963,48 @@ export const pluginReactRouter = ( } ); - api.processAssets( - { stage: 'additional', targets: ['node'] }, - ({ sources, compilation }) => { - const packageJsonPath = 'package.json'; - const source = new sources.RawSource( - `{"type": "${resolvedServerOutput}"}` - ); - - if (compilation.getAsset(packageJsonPath)) { - compilation.updateAsset(packageJsonPath, source); - } else { - compilation.emitAsset(packageJsonPath, source); - } - } - ); - - // Add manifest transformations - api.transform( - { - test: /virtual\/react-router\/(browser|server)-manifest/, - }, - async args => { - // For browser manifest, return a placeholder that will be modified by the plugin - if (args.environment.name === 'web') { - return { - code: `window.__reactRouterManifest = "PLACEHOLDER";`, - }; - } - - const bundleMatch = args.resource.match( - /virtual\/react-router\/server-manifest(?:-([^?]+))?/ - ); - const bundleId = bundleMatch?.[1]?.replace(/\\.js$/, ''); - - const manifest = - (isBuild && latestServerManifest - ? bundleId && latestServerManifestsByBundleId[bundleId] - ? latestServerManifestsByBundleId[bundleId] - : latestServerManifest - : null) ?? - (await getReactRouterManifestForDev( - routes, - pluginOptions, - clientStats, - appDirectory, - assetPrefix, - routeChunkOptions - )); - return { - code: `export default ${jsesc(manifest, { es6: true })};`, - }; - } - ); - - api.transform( - { - resourceQuery: /__react-router-build-client-route/, - }, - async args => { - const code = await transformToEsm(args.code, args.resourcePath); - const exportNames = await getExportNames(code); - const isServer = args.environment?.name === 'node'; - const chunkedExports = - !isServer && isBuild && splitRouteModules - ? ( - await detectRouteChunksIfEnabled( - routeChunkCache, - routeChunkConfig, - args.resourcePath, - code - ) - ).chunkedExports - : []; - const chunkedExportSet = new Set(chunkedExports); - const reexports = exportNames.filter(exp => { - if (chunkedExportSet.has(exp)) { - return false; - } - return ( - (CLIENT_ROUTE_EXPORTS as readonly string[]).includes(exp) || - (isServer && - (SERVER_ONLY_ROUTE_EXPORTS as readonly string[]).includes(exp)) - ); - }); - const target = `${args.resourcePath}?react-router-route`; - return { - code: `export { ${reexports.join(', ')} } from ${JSON.stringify( - target - )};`, - }; - } - ); - - api.transform( - { - resourceQuery: /route-chunk=/, - }, - async args => { - if (args.environment?.name !== 'web') { - return { code: args.code, map: null }; - } - const preventEmptyChunkSnippet = (reason: string) => - `Math.random()<0&&console.log(${JSON.stringify(reason)});`; - - if (!isBuild || !splitRouteModules) { - return { - code: preventEmptyChunkSnippet('Split route modules disabled'), - map: null, - }; - } - - const chunkName = getRouteChunkNameFromModuleId(args.resource); - if (!chunkName) { - throw new Error(`Invalid route chunk name in "${args.resource}"`); - } - - const transformed = await transformToEsm(args.code, args.resourcePath); - const chunk = await getRouteChunkIfEnabled( - routeChunkCache, - routeChunkConfig, - args.resourcePath, - chunkName, - transformed - ); - - if (enforceSplitRouteModules && chunkName === 'main' && chunk) { - const exportNames = await getExportNames(chunk); - validateRouteChunks({ - config: routeChunkConfig, - id: args.resourcePath, - valid: { - clientAction: !exportNames.includes('clientAction'), - clientLoader: !exportNames.includes('clientLoader'), - clientMiddleware: !exportNames.includes('clientMiddleware'), - HydrateFallback: !exportNames.includes('HydrateFallback'), - }, - }); - } - - return { - code: chunk ?? preventEmptyChunkSnippet(`No ${chunkName} chunk`), - map: null, - }; - } - ); - - api.transform( - { - test: /\.[cm]?[jt]sx?$/, - }, - async args => { - if (args.environment?.name !== 'web') { - return { code: args.code, map: null }; - } - if (!isBuild || !splitRouteModules) { - return { code: args.code, map: null }; - } - if ( - args.resource.includes(BUILD_CLIENT_ROUTE_QUERY_STRING) || - args.resource.includes('?react-router-route') || - args.resource.includes('route-chunk=') - ) { - return { code: args.code, map: null }; - } - const route = routeByFilePath.get(args.resourcePath); - if (!route) { - return { code: args.code, map: null }; - } - - const transformed = await transformToEsm(args.code, args.resourcePath); - const { hasRouteChunks, chunkedExports } = - await detectRouteChunksIfEnabled( - routeChunkCache, - routeChunkConfig, - args.resourcePath, - transformed - ); - if (!hasRouteChunks) { - return { code: args.code, map: null }; - } - - const sourceExports = await getCachedRouteExports(args.resourcePath); - const chunkedExportSet = new Set(chunkedExports); - const isMainChunkExport = (name: string) => !chunkedExportSet.has(name); - const mainChunkReexports = sourceExports - .filter(isMainChunkExport) - .join(', '); - const chunkBasePath = `./${pathBasename(args.resourcePath)}`; - - return { - code: [ - mainChunkReexports - ? `export { ${mainChunkReexports} } from "${getRouteChunkModuleId( - chunkBasePath, - 'main' - )}";` - : null, - ...chunkedExports.map( - exportName => - `export { ${exportName} } from "${getRouteChunkModuleId( - chunkBasePath, - exportName - )}";` - ), - ] - .filter(Boolean) - .join('\n'), - map: null, - }; - } - ); - - api.transform( + registerModifyBrowserManifestAssets( + api, + routes, + pluginOptions, + appDirectory, + () => assetPrefix, + routeChunkOptions, { - test: /[\\/]\.server[\\/]|\.server(\.[cm]?[jt]sx?)?$/, - }, - async args => { - if (args.environment?.name !== 'web') { - return { code: args.code, map: null }; - } - - const relativePath = relative(process.cwd(), args.resourcePath); - throw new Error( - `[${PLUGIN_NAME}] Server-only module referenced by client: ${relativePath}` - ); + subResourceIntegrity: resolvedConfigWithRoutes.subResourceIntegrity, + future, + manifestChunkNames, + onManifest: (manifest, sri, moduleExportsByRouteId, context) => + stageLatestManifests( + manifest, + sri, + moduleExportsByRouteId, + context.compilation + ), } ); - api.transform( - { - test: /[\\/]\.client[\\/]|\.client(\.[cm]?[jt]sx?)?$/, - }, - async args => { - if (args.environment?.name !== 'node') { - return { code: args.code, map: null }; - } - - const code = await transformToEsm(args.code, args.resourcePath); - const { exportNames: directExportNames, exportAllModules } = - await getExportNamesAndExportAll(code); - const exportNames = new Set(directExportNames); - const unresolvedExportAll = new Set(); - const visitedModules = new Set(); - - const resolveIndexFile = (dirPath: string): string | null => { - for (const ext of JS_EXTENSIONS) { - const candidate = resolve(dirPath, `index${ext}`); - if (!existsSync(candidate)) { - continue; - } - try { - if (statSync(candidate).isFile()) { - return candidate; - } - } catch { - continue; - } - } - return null; - }; - - const resolvePathWithExtensions = (basePath: string): string | null => { - if (existsSync(basePath)) { - try { - const stats = statSync(basePath); - if (stats.isFile()) { - return basePath; - } - if (stats.isDirectory()) { - return resolveIndexFile(basePath); - } - } catch { - // Ignore invalid paths and fall back to extension probing. - } - } - - for (const ext of JS_EXTENSIONS) { - const candidate = `${basePath}${ext}`; - if (!existsSync(candidate)) { - continue; - } - try { - if (statSync(candidate).isFile()) { - return candidate; - } - } catch { - continue; - } - } - - return resolveIndexFile(basePath); - }; - - const resolveExportAllModule = ( - specifier: string, - importerPath: string - ): string | null => { - if (specifier.startsWith('.') || specifier.startsWith('/')) { - const basePath = specifier.startsWith('/') - ? specifier - : resolve(dirname(importerPath), specifier); - const resolvedPath = resolvePathWithExtensions(basePath); - if (resolvedPath) { - return resolvedPath; - } - } - - try { - const resolver = createRequire(pathToFileURL(importerPath).href); - return resolver.resolve(specifier); - } catch { - return null; - } - }; - - const collectExportNamesFromModule = async ( - modulePath: string - ): Promise => { - if (visitedModules.has(modulePath)) { - return; - } - visitedModules.add(modulePath); - const source = await readFile(modulePath, 'utf8'); - const moduleCode = await transformToEsm(source, modulePath); - const { - exportNames: moduleExportNames, - exportAllModules: moduleExportAll, - } = await getExportNamesAndExportAll(moduleCode); - for (const name of moduleExportNames) { - if (name !== 'default') { - exportNames.add(name); - } - } - for (const nestedSpecifier of moduleExportAll) { - const nestedPath = resolveExportAllModule( - nestedSpecifier, - modulePath - ); - if (!nestedPath) { - unresolvedExportAll.add(nestedSpecifier); - continue; - } - await collectExportNamesFromModule(nestedPath); - } - }; - - for (const specifier of exportAllModules) { - const resolvedPath = resolveExportAllModule( - specifier, - args.resourcePath - ); - if (!resolvedPath) { - unresolvedExportAll.add(specifier); - continue; - } - await collectExportNamesFromModule(resolvedPath); - } - - if (unresolvedExportAll.size > 0) { - throw new Error( - `[${PLUGIN_NAME}] Client-only module uses \`export * from\` with ` + - `unresolvable specifier(s): ${Array.from(unresolvedExportAll) - .map(spec => `\`${spec}\``) - .join(', ')}. ` + - `Please explicitly re-export named bindings in ` + - `\`${relative(process.cwd(), args.resourcePath)}\`.` - ); - } - return { - code: Array.from(exportNames) - .map(name => - name === 'default' - ? 'export default undefined;' - : `export const ${name} = undefined;` - ) - .join('\n'), - map: null, - }; - } - ); - - api.transform( - { - resourceQuery: /\?react-router-route/, - }, - async args => { - let code: string; - try { - code = await transformToEsm(args.code, args.resourcePath); - } catch (error) { - console.error(args.resourcePath); - throw error; - } - - // Match React Router Vite behavior: - // In SPA mode, server-only route exports are invalid (except root `loader`), - // and `HydrateFallback` is only allowed on the root route. - // - // Important: `es-module-lexer` can't parse TS/TSX directly, so we scan - // the ESBuild-transformed JS output. - if (args.environment.name === 'web' && !ssr && isSpaMode) { - const exportNames = await getExportNames(code); - - const isRootRoute = args.resourcePath === rootRoutePath; - - const invalidServerOnly = exportNames.filter(exp => { - if (isRootRoute && exp === 'loader') return false; - return (SERVER_ONLY_ROUTE_EXPORTS as readonly string[]).includes( - exp - ); - }); - - if (invalidServerOnly.length > 0) { - const list = invalidServerOnly.map(e => `\`${e}\``).join(', '); - throw new Error( - `SPA Mode: ${invalidServerOnly.length} invalid route export(s) in ` + - `\`${relative(process.cwd(), args.resourcePath)}\`: ${list}. ` + - `See https://reactrouter.com/how-to/spa for more information.` - ); - } - - if (!isRootRoute && exportNames.includes('HydrateFallback')) { - throw new Error( - `SPA Mode: Invalid \`HydrateFallback\` export found in ` + - `\`${relative(process.cwd(), args.resourcePath)}\`. ` + - `\`HydrateFallback\` is only permitted on the root route in SPA Mode. ` + - `See https://reactrouter.com/how-to/spa for more information.` - ); - } - } - - const defaultExportMatch = code.match( - /\n\s{0,}([\w\d_]+)\sas default,?/ - ); - if ( - defaultExportMatch && - typeof defaultExportMatch.index === 'number' - ) { - code = - code.slice(0, defaultExportMatch.index) + - code.slice(defaultExportMatch.index + defaultExportMatch[0].length); - code += `\nexport default ${defaultExportMatch[1]};`; - } - - const ast = parse(code, { sourceType: 'module' }); - if (args.environment.name === 'web') { - const mutableServerOnlyRouteExports = [...SERVER_ONLY_ROUTE_EXPORTS]; - removeExports(ast, mutableServerOnlyRouteExports); - } - transformRoute(ast); - if (args.environment.name === 'web') { - removeUnusedImports(ast); - } - - return generate(ast, { - sourceMaps: true, - filename: args.resource, - sourceFileName: args.resourcePath, - }); - } - ); + registerBuildOutputTransforms({ + api, + resolvedServerOutput, + performanceProfiler, + getLatestServerManifest: () => latestServerManifest, + getLatestServerManifestByBundleId: bundleId => + latestServerManifestsByBundleId[bundleId], + routes, + pluginOptions, + getClientStats: () => clientStats, + appDirectory, + getAssetPrefix: () => assetPrefix, + routeChunkOptions, + routeTransformExecutor, + routeByFilePath, + routeChunkConfig, + isBuild, + splitRouteModules: Boolean(splitRouteModules), + ssr, + isSpaMode, + rootRoutePath, + }); }, }); diff --git a/src/lazy-compilation.ts b/src/lazy-compilation.ts new file mode 100644 index 0000000..2fd8649 --- /dev/null +++ b/src/lazy-compilation.ts @@ -0,0 +1,93 @@ +import { BUILD_CLIENT_ROUTE_QUERY_STRING } from './constants.js'; +import type { PluginOptions } from './types.js'; + +type LazyCompilationOptions = Exclude< + NonNullable, + boolean +>; + +type LazyCompilationModule = { + request?: string; + userRequest?: string; + rawRequest?: string; + resource?: string; + identifier?: () => string; + nameForCondition?: () => string | null; +}; + +const normalizeSlashes = (value: string): string => value.replace(/\\/g, '/'); + +const getLazyCompilationModuleValues = ( + module: LazyCompilationModule +): string[] => { + const values = [ + module.request, + module.userRequest, + module.rawRequest, + module.resource, + module.identifier?.(), + module.nameForCondition?.(), + ]; + return values.filter((value): value is string => Boolean(value)); +}; + +const matchesLazyCompilationTest = ( + test: LazyCompilationOptions['test'] | undefined, + module: LazyCompilationModule +): boolean => { + if (!test) { + return true; + } + if (typeof test === 'function') { + return test(module as Parameters[0]); + } + return getLazyCompilationModuleValues(module).some(value => { + test.lastIndex = 0; + return test.test(value); + }); +}; + +const isReactRouterHydrationModule = ( + module: LazyCompilationModule, + entryClientPath: string +): boolean => { + const eagerPatterns = [ + normalizeSlashes(entryClientPath), + 'virtual/react-router/browser-manifest', + BUILD_CLIENT_ROUTE_QUERY_STRING, + '?react-router-route', + ]; + + return getLazyCompilationModuleValues(module) + .map(normalizeSlashes) + .some(value => eagerPatterns.some(pattern => value.includes(pattern))); +}; + +export const guardReactRouterLazyCompilation = ({ + lazyCompilation, + entryClientPath, +}: { + lazyCompilation: PluginOptions['lazyCompilation'] | undefined; + entryClientPath: string; +}): PluginOptions['lazyCompilation'] | undefined => { + if (lazyCompilation === undefined || lazyCompilation === false) { + return lazyCompilation; + } + + const options: LazyCompilationOptions = + lazyCompilation === true + ? { entries: true, imports: true } + : lazyCompilation; + const userTest = options.test; + + return { + ...options, + test(module) { + const lazyModule = module as LazyCompilationModule; + if (isReactRouterHydrationModule(lazyModule, entryClientPath)) { + return false; + } + return matchesLazyCompilationTest(userTest, lazyModule); + }, + }; +}; diff --git a/src/manifest.ts b/src/manifest.ts index 961757a..3a0cb42 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -1,27 +1,44 @@ import { createHash } from 'node:crypto'; -import { readFile } from 'node:fs/promises'; import { dirname, isAbsolute, relative, resolve } from 'pathe'; import type { Route, PluginOptions, RouteManifestItem } from './types.js'; import type { RouteConfigEntry } from '@react-router/dev/routes'; -import type { Rspack } from '@rsbuild/core'; import { combineURLs, createRouteId } from './plugin-utils.js'; import { SERVER_EXPORTS, CLIENT_EXPORTS } from './constants.js'; import { + buildManifestChunkValidity, + createEmptyRouteChunkByExportName, detectRouteChunksIfEnabled, getRouteChunkEntryName, + routeChunkExportNames, validateRouteChunks, type RouteChunkCache, type RouteChunkConfig, } from './route-chunks.js'; -import { getExportNames, transformToEsm } from './export-utils.js'; +import { getRouteModuleAnalysis } from './export-utils.js'; +import { getDefaultConcurrency, mapWithConcurrency } from './concurrency.js'; + +const ROUTE_ANALYSIS_CONCURRENCY = Math.max( + 1, + Math.min(16, getDefaultConcurrency() || 1) +); -// Helper functions export function configRoutesToRouteManifest( appDirectory: string, routes: RouteConfigEntry[], rootId = 'root' ): Record { - const routeManifest: Record = {}; + return Object.fromEntries( + configRoutesToRouteManifestEntries(appDirectory, routes, rootId) + ); +} + +export function configRoutesToRouteManifestEntries( + appDirectory: string, + routes: RouteConfigEntry[], + rootId = 'root' +): Array<[string, Route]> { + const routeManifestEntries: Array<[string, Route]> = []; + const routeIds = new Set(); function walk(route: RouteConfigEntry, parentId: string) { const id = route.id || createRouteId(route.file); @@ -36,12 +53,13 @@ export function configRoutesToRouteManifest( caseSensitive: route.caseSensitive, }; - if (Object.prototype.hasOwnProperty.call(routeManifest, id)) { + if (routeIds.has(id)) { throw new Error( `Unable to define routes with duplicate route id: "${id}"` ); } - routeManifest[id] = manifestItem; + routeIds.add(id); + routeManifestEntries.push([id, manifestItem]); if (route.children) { for (const child of route.children) { @@ -54,7 +72,7 @@ export function configRoutesToRouteManifest( walk(route, rootId); } - return routeManifest; + return routeManifestEntries; } type RouteChunkManifestOptions = { @@ -64,6 +82,136 @@ type RouteChunkManifestOptions = { cache?: RouteChunkCache; }; +export type ReactRouterManifestForDev = { + version: string; + url: string; + hmr?: { + runtime: string; + }; + entry: { + module: string; + imports: string[]; + css: string[]; + }; + sri?: Record; + routes: Record; +}; + +export type ReactRouterManifestStats = { + assetsByChunkName?: Record; + entrypointFilesByName?: Record; +}; + +type ReactRouterManifestStatsChunk = { + files?: Iterable; +}; + +type ReactRouterManifestStatsEntrypoint = { + getFiles?: () => Iterable; +}; + +type ReactRouterManifestStatsCompilation = { + namedChunks: Iterable<[string, ReactRouterManifestStatsChunk]>; + entrypoints?: Iterable<[string, ReactRouterManifestStatsEntrypoint]>; +}; + +type ReactRouterManifestStatsNamedChunks = + ReactRouterManifestStatsCompilation['namedChunks'] & { + get?: (chunkName: string) => ReactRouterManifestStatsChunk | undefined; + }; + +type ReactRouterManifestStatsEntrypoints = NonNullable< + ReactRouterManifestStatsCompilation['entrypoints'] +> & { + get?: ( + entrypointName: string + ) => ReactRouterManifestStatsEntrypoint | undefined; +}; + +const orderChunkFiles = (chunkName: string, files: string[]): string[] => { + const ownChunkAsset = `${chunkName}.js`; + const ownFileIndex = files.findIndex(file => file.endsWith(ownChunkAsset)); + if (ownFileIndex <= 0) { + return files; + } + + return [ + files[ownFileIndex], + ...files.slice(0, ownFileIndex), + ...files.slice(ownFileIndex + 1), + ]; +}; + +export const createReactRouterManifestStats = ( + compilation: ReactRouterManifestStatsCompilation | undefined, + chunkNames?: ReadonlySet +): ReactRouterManifestStats | undefined => { + if (!compilation) { + return undefined; + } + + const assetsByChunkName: Record = {}; + const entrypointFilesByName: Record = {}; + const namedChunks = + compilation.namedChunks as ReactRouterManifestStatsNamedChunks; + const entrypoints = compilation.entrypoints as + | ReactRouterManifestStatsEntrypoints + | undefined; + + if (chunkNames && typeof namedChunks.get === 'function') { + for (const chunkName of chunkNames) { + const chunk = namedChunks.get(chunkName); + if (!chunk) { + continue; + } + const files = Array.from(chunk.files ?? []); + assetsByChunkName[chunkName] = orderChunkFiles(chunkName, files); + } + } else { + for (const [chunkName, chunk] of namedChunks) { + if (chunkNames && !chunkNames.has(chunkName)) { + continue; + } + const files = Array.from(chunk.files ?? []); + assetsByChunkName[chunkName] = orderChunkFiles(chunkName, files); + } + } + + if (entrypoints) { + if (chunkNames && typeof entrypoints.get === 'function') { + for (const entrypointName of chunkNames) { + const entrypoint = entrypoints.get(entrypointName); + if (!entrypoint) { + continue; + } + entrypointFilesByName[entrypointName] = Array.from( + entrypoint.getFiles?.() ?? [] + ); + } + } else { + for (const [entrypointName, entrypoint] of entrypoints) { + if (chunkNames && !chunkNames.has(entrypointName)) { + continue; + } + entrypointFilesByName[entrypointName] = Array.from( + entrypoint.getFiles?.() ?? [] + ); + } + } + } + + return Object.keys(entrypointFilesByName).length > 0 + ? { assetsByChunkName, entrypointFilesByName } + : { assetsByChunkName }; +}; + +export type RouteManifestModuleExports = Record; + +export type ReactRouterManifestGenerationResult = { + manifest: ReactRouterManifestForDev; + moduleExportsByRouteId: RouteManifestModuleExports; +}; + const DEFAULT_MANIFEST_DIR = 'static/js'; const getManifestDirFromEntryAsset = (entryModulePath?: string): string => { @@ -108,28 +256,31 @@ const getRouteEntryName = (route: Route): string => { return extensionIndex >= 0 ? route.file.slice(0, extensionIndex) : route.file; }; -export async function getReactRouterManifestForDev( +export const getReactRouterManifestChunkNames = ( + routes: Record, + splitRouteModules: boolean | 'enforce' = false +): Set => { + const chunkNames = new Set(['entry.client']); + for (const route of Object.values(routes)) { + chunkNames.add(getRouteEntryName(route)); + if (!splitRouteModules || route.id === 'root') { + continue; + } + for (const exportName of routeChunkExportNames) { + chunkNames.add(getRouteChunkEntryName(route.id, exportName)); + } + } + return chunkNames; +}; + +export async function generateReactRouterManifestForDev( routes: Record, - //@ts-ignore - options: PluginOptions, - clientStats: Rspack.StatsCompilation | undefined, + _options: PluginOptions, + clientStats: ReactRouterManifestStats | undefined, context: string, assetPrefix = '/', routeChunkOptions?: RouteChunkManifestOptions -): Promise<{ - version: string; - url: string; - hmr?: { - runtime: string; - }; - entry: { - module: string; - imports: string[]; - css: string[]; - }; - sri?: Record; - routes: Record; -}> { +): Promise { const result: Record = {}; const splitRouteModules = routeChunkOptions?.splitRouteModules ?? false; const enforceSplitRouteModules = splitRouteModules === 'enforce'; @@ -148,11 +299,24 @@ export async function getReactRouterManifestForDev( if (!assets) { return [`${DEFAULT_MANIFEST_DIR}/${chunkName}.js`]; } - const normalizedAssets = Array.isArray(assets) ? assets : [assets]; - if (!normalizedAssets.some(asset => asset.endsWith('.js'))) { - return [`${DEFAULT_MANIFEST_DIR}/${chunkName}.js`, ...normalizedAssets]; + const entrypointCssAssets = + clientStats?.entrypointFilesByName?.[chunkName]?.filter(asset => + asset.endsWith('.css') + ) ?? []; + const cssAssets = [ + ...assets.filter(asset => asset.endsWith('.css')), + ...entrypointCssAssets, + ].filter((asset, index, all) => all.indexOf(asset) === index); + const nonCssAssets = assets.filter(asset => !asset.endsWith('.css')); + + if (!nonCssAssets.some(asset => asset.endsWith('.js'))) { + return [ + `${DEFAULT_MANIFEST_DIR}/${chunkName}.js`, + ...nonCssAssets, + ...cssAssets, + ]; } - return normalizedAssets; + return [...nonCssAssets, ...cssAssets]; }; const getModulePathForChunk = (chunkName: string): string | undefined => { @@ -161,116 +325,118 @@ export async function getReactRouterManifestForDev( return jsAssets[0] ? combineURLs(assetPrefix, jsAssets[0]) : undefined; }; - for (const [key, route] of Object.entries(routes)) { - const routeEntryName = getRouteEntryName(route); - const assets = getAssetsForChunk(routeEntryName); - const jsAssets = assets.filter(asset => asset.endsWith('.js')) || []; - let cssAssets = assets.filter(asset => asset.endsWith('.css')) || []; - // Read and analyze the route file to check for exports - const routeFilePath = resolve(context, route.file); - let exports = new Set(); - let hasRouteChunkByExportName: Record< - 'clientAction' | 'clientLoader' | 'clientMiddleware' | 'HydrateFallback', - boolean - > = { - clientAction: false, - clientLoader: false, - clientMiddleware: false, - HydrateFallback: false, - }; + const manifestEntries = await mapWithConcurrency( + Object.entries(routes), + ROUTE_ANALYSIS_CONCURRENCY, + async ([key, route]) => { + const routeEntryName = getRouteEntryName(route); + const assets = getAssetsForChunk(routeEntryName); + const jsAssets = assets.filter(asset => asset.endsWith('.js')); + let cssAssets = assets.filter(asset => asset.endsWith('.css')); + const routeFilePath = resolve(context, route.file); + let exports = new Set(); + let routeModuleExports: readonly string[] = []; + let hasRouteChunkByExportName: ReturnType< + typeof createEmptyRouteChunkByExportName + > | null = null; - try { - const source = await readFile(routeFilePath, 'utf8'); - if ( - !isBuild && - cssAssets.length === 0 && - /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/.test(source) - ) { - cssAssets = [ - `${DEFAULT_MANIFEST_DIR.replace('/js', '/css')}/${routeEntryName}.css`, - ]; + try { + const { code, exports: exportNames } = + await getRouteModuleAnalysis(routeFilePath); + if ( + !isBuild && + cssAssets.length === 0 && + /\.(?:css|less|sass|scss)(?:\?[^'"`]+)?['"`]/.test(code) + ) { + cssAssets = [ + `${DEFAULT_MANIFEST_DIR.replace('/js', '/css')}/${routeEntryName}.css`, + ]; + } + routeModuleExports = exportNames; + exports = new Set(exportNames); + + if (isBuild && routeChunkConfig) { + const { hasRouteChunkByExportName: chunkInfo } = + await detectRouteChunksIfEnabled( + routeChunkOptions?.cache, + routeChunkConfig, + routeFilePath, + code + ); + hasRouteChunkByExportName = chunkInfo; + } + } catch (error) { + if (isBuild) { + throw error; + } + console.error(`Failed to analyze route file ${routeFilePath}:`, error); } - const code = await transformToEsm(source, routeFilePath); - exports = new Set(await getExportNames(code)); - - if (isBuild && routeChunkConfig) { - const { hasRouteChunkByExportName: chunkInfo } = - await detectRouteChunksIfEnabled( - routeChunkOptions?.cache, - routeChunkConfig, - routeFilePath, - code - ); - hasRouteChunkByExportName = chunkInfo; + + const hasClientAction = exports.has(CLIENT_EXPORTS.clientAction); + const hasClientLoader = exports.has(CLIENT_EXPORTS.clientLoader); + const hasClientMiddleware = exports.has(CLIENT_EXPORTS.clientMiddleware); + const hasDefaultExport = exports.has('default'); + const routeChunkMap = hasRouteChunkByExportName; + + if (isBuild && enforceSplitRouteModules && routeChunkConfig) { + validateRouteChunks({ + config: routeChunkConfig, + id: routeFilePath, + valid: buildManifestChunkValidity( + exports, + routeChunkMap ?? createEmptyRouteChunkByExportName() + ), + }); } - } catch (error) { - console.error(`Failed to analyze route file ${routeFilePath}:`, error); - } - const hasClientAction = exports.has(CLIENT_EXPORTS.clientAction); - const hasClientLoader = exports.has(CLIENT_EXPORTS.clientLoader); - const hasClientMiddleware = exports.has(CLIENT_EXPORTS.clientMiddleware); - const hasHydrateFallback = exports.has(CLIENT_EXPORTS.HydrateFallback); - const hasDefaultExport = exports.has('default'); - - if (isBuild && enforceSplitRouteModules && routeChunkConfig) { - validateRouteChunks({ - config: routeChunkConfig, - id: routeFilePath, - valid: { - clientAction: - !hasClientAction || hasRouteChunkByExportName.clientAction, - clientLoader: - !hasClientLoader || hasRouteChunkByExportName.clientLoader, - clientMiddleware: - !hasClientMiddleware || hasRouteChunkByExportName.clientMiddleware, - HydrateFallback: - !hasHydrateFallback || hasRouteChunkByExportName.HydrateFallback, + return [ + key, + { + id: route.id, + parentId: route.parentId, + path: route.path, + index: route.index, + caseSensitive: route.caseSensitive, + module: combineURLs(assetPrefix, jsAssets[0] || ''), + clientActionModule: routeChunkMap?.clientAction + ? getModulePathForChunk( + getRouteChunkEntryName(route.id, 'clientAction') + ) + : undefined, + clientLoaderModule: routeChunkMap?.clientLoader + ? getModulePathForChunk( + getRouteChunkEntryName(route.id, 'clientLoader') + ) + : undefined, + clientMiddlewareModule: routeChunkMap?.clientMiddleware + ? getModulePathForChunk( + getRouteChunkEntryName(route.id, 'clientMiddleware') + ) + : undefined, + hydrateFallbackModule: routeChunkMap?.HydrateFallback + ? getModulePathForChunk( + getRouteChunkEntryName(route.id, 'HydrateFallback') + ) + : undefined, + hasAction: exports.has(SERVER_EXPORTS.action), + hasLoader: exports.has(SERVER_EXPORTS.loader), + hasClientAction, + hasClientLoader, + hasClientMiddleware, + hasDefaultExport, + hasErrorBoundary: exports.has(CLIENT_EXPORTS.ErrorBoundary), + imports: jsAssets.map(asset => combineURLs(assetPrefix, asset)), + css: cssAssets.map(asset => combineURLs(assetPrefix, asset)), }, - }); + routeModuleExports, + ] as const; } + ); - result[key] = { - id: route.id, - parentId: route.parentId, - path: route.path, - index: route.index, - caseSensitive: route.caseSensitive, - module: combineURLs(assetPrefix, jsAssets[0] || ''), - clientActionModule: - isBuild && hasRouteChunkByExportName.clientAction - ? getModulePathForChunk( - getRouteChunkEntryName(route.id, 'clientAction') - ) - : undefined, - clientLoaderModule: - isBuild && hasRouteChunkByExportName.clientLoader - ? getModulePathForChunk( - getRouteChunkEntryName(route.id, 'clientLoader') - ) - : undefined, - clientMiddlewareModule: - isBuild && hasRouteChunkByExportName.clientMiddleware - ? getModulePathForChunk( - getRouteChunkEntryName(route.id, 'clientMiddleware') - ) - : undefined, - hydrateFallbackModule: - isBuild && hasRouteChunkByExportName.HydrateFallback - ? getModulePathForChunk( - getRouteChunkEntryName(route.id, 'HydrateFallback') - ) - : undefined, - hasAction: exports.has(SERVER_EXPORTS.action), - hasLoader: exports.has(SERVER_EXPORTS.loader), - hasClientAction, - hasClientLoader, - hasClientMiddleware, - hasDefaultExport, - hasErrorBoundary: exports.has(CLIENT_EXPORTS.ErrorBoundary), - imports: jsAssets.map(asset => combineURLs(assetPrefix, asset)), - css: cssAssets.map(asset => combineURLs(assetPrefix, asset)), - }; + const routeModuleExportsByRouteId: RouteManifestModuleExports = {}; + for (const [key, routeManifestItem, routeModuleExports] of manifestEntries) { + result[key] = routeManifestItem; + routeModuleExportsByRouteId[key] = routeModuleExports; } const entryAssets = getAssetsForChunk('entry.client'); @@ -292,7 +458,7 @@ export async function getReactRouterManifestForDev( entryModulePath: entryJsAssets[0], }); - return { + const manifest = { version, url: combineURLs(assetPrefix, manifestPath), hmr: undefined, @@ -300,4 +466,15 @@ export async function getReactRouterManifestForDev( sri: undefined, routes: result, }; + + return { + manifest, + moduleExportsByRouteId: routeModuleExportsByRouteId, + }; +} + +export async function getReactRouterManifestForDev( + ...args: Parameters +): Promise { + return (await generateReactRouterManifestForDev(...args)).manifest; } diff --git a/src/modify-browser-manifest.ts b/src/modify-browser-manifest.ts index fd246f6..05eea40 100644 --- a/src/modify-browser-manifest.ts +++ b/src/modify-browser-manifest.ts @@ -1,134 +1,183 @@ import { createHash } from 'node:crypto'; import type { Route, PluginOptions } from './types.js'; -import { rspack } from '@rsbuild/core'; -import type { Rspack } from '@rsbuild/core'; +import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core'; import { + createReactRouterManifestStats, + generateReactRouterManifestForDev, + getReactRouterManifestChunkNames, getReactRouterManifestForDev, getReactRouterManifestPath, } from './manifest.js'; import { combineURLs } from './plugin-utils.js'; import jsesc from 'jsesc'; -/** - * Creates a Webpack/Rspack plugin that modifies the browser manifest - * @param routes - The routes configuration - * @param pluginOptions - The plugin options - * @param appDirectory - The application directory - * @returns A webpack/rspack plugin - */ -export function createModifyBrowserManifestPlugin( +type ModifyBrowserManifestOptions = { + subResourceIntegrity?: boolean; + future?: { unstable_subResourceIntegrity?: boolean }; + manifestChunkNames?: ReadonlySet; + onManifest?: ( + manifest: Awaited>, + sri: Record | undefined, + moduleExportsByRouteId: Awaited< + ReturnType + >['moduleExportsByRouteId'], + context: { + compilation: Rspack.Compilation; + manifestStats: ReturnType; + } + ) => void; +}; + +type ProcessAssetsApi = Pick; +type AssetPrefixInput = string | (() => string); +type ReactRouterManifest = Awaited< + ReturnType +>; +type RouteManifestModuleExports = Awaited< + ReturnType +>['moduleExportsByRouteId']; +type GeneratedManifest = { + manifest: ReactRouterManifest; + moduleExportsByRouteId: RouteManifestModuleExports; + manifestStats: ReturnType; + assetPrefix: string; +}; + +const BROWSER_MANIFEST_ASSET = + 'static/js/virtual/react-router/browser-manifest.js'; + +const createSubresourceIntegrity = ( + compilation: Rspack.Compilation, + assetPrefix: string +) => { + const sri: Record = {}; + for (const asset of compilation.getAssets()) { + if (!asset.name.endsWith('.js')) { + continue; + } + const source = asset.source.source().toString(); + const hash = createHash('sha384').update(source).digest('base64'); + sri[combineURLs(assetPrefix, asset.name)] = `sha384-${hash}`; + } + return sri; +}; + +export function registerModifyBrowserManifestAssets( + api: ProcessAssetsApi, routes: Record, pluginOptions: PluginOptions, appDirectory: string, - assetPrefix = '/', + assetPrefix: AssetPrefixInput = '/', routeChunkOptions?: Parameters[5], - options?: { - subResourceIntegrity?: boolean; - future?: { unstable_subResourceIntegrity?: boolean }; - onManifest?: ( - manifest: Awaited>, - sri: Record | undefined - ) => void; - } -) { - return { - apply(compiler: Rspack.Compiler): void { - compiler.hooks.emit.tapAsync( - 'ModifyBrowserManifest', - async (compilation: Rspack.Compilation, callback) => { - const stats = compilation.getStats().toJson(); - const manifest = await getReactRouterManifestForDev( - routes, - pluginOptions, - stats, - appDirectory, - assetPrefix, - routeChunkOptions - ); + options?: ModifyBrowserManifestOptions +): void { + const getAssetPrefix = + typeof assetPrefix === 'function' ? assetPrefix : () => assetPrefix; + const manifestChunkNames = + options?.manifestChunkNames ?? + getReactRouterManifestChunkNames( + routes, + routeChunkOptions?.splitRouteModules + ); + const finalizeSri = + routeChunkOptions?.isBuild && + (options?.subResourceIntegrity ?? + options?.future?.unstable_subResourceIntegrity); + const generatedManifests = finalizeSri + ? new WeakMap() + : undefined; - const virtualManifestPath = - 'static/js/virtual/react-router/browser-manifest.js'; - if (compilation.assets[virtualManifestPath]) { - const originalSource = compilation.assets[virtualManifestPath] - .source() - .toString(); - const newSource = originalSource.replace( - /["'`]PLACEHOLDER["'`]/, - jsesc(manifest, { es6: true }) - ); - compilation.assets[virtualManifestPath] = { - source: () => newSource, - size: () => newSource.length, - map: () => ({ - version: 3, - sources: [virtualManifestPath], - names: [], - mappings: '', - file: virtualManifestPath, - sourcesContent: [newSource], - }), - sourceAndMap: () => ({ - source: newSource, - map: { - version: 3, - sources: [virtualManifestPath], - names: [], - mappings: '', - file: virtualManifestPath, - sourcesContent: [newSource], - }, - }), - updateHash: hash => hash.update(newSource), - buffer: () => Buffer.from(newSource), - }; - } + api.processAssets( + { stage: 'additions', environments: ['web'] }, + async ({ assets, sources, compilation }) => { + const currentAssetPrefix = getAssetPrefix(); + const stats = createReactRouterManifestStats( + compilation, + manifestChunkNames + ); + const { manifest, moduleExportsByRouteId } = + await generateReactRouterManifestForDev( + routes, + pluginOptions, + stats, + appDirectory, + currentAssetPrefix, + routeChunkOptions + ); - if (routeChunkOptions?.isBuild) { - const entryAssets = stats?.assetsByChunkName?.['entry.client']; - const entryJsAssets = - entryAssets?.filter(asset => asset.endsWith('.js')) || []; - const manifestPath = getReactRouterManifestPath({ - version: manifest.version, - isBuild: true, - entryModulePath: entryJsAssets[0], - }); - const manifestSource = `window.__reactRouterManifest=${jsesc( - manifest, - { es6: true } - )};`; - compilation.assets[manifestPath] = new rspack.sources.RawSource( - manifestSource - ); - } + const browserManifestAsset = assets[BROWSER_MANIFEST_ASSET]; + if (browserManifestAsset) { + const originalSource = browserManifestAsset.source().toString(); + const newSource = originalSource.replace( + /["'`]PLACEHOLDER["'`]/, + jsesc(manifest, { es6: true }) + ); + compilation.updateAsset( + BROWSER_MANIFEST_ASSET, + new sources.RawSource(newSource) + ); + } - let sri: Record | undefined; - if ( - routeChunkOptions?.isBuild && - (options?.subResourceIntegrity ?? - options?.future?.unstable_subResourceIntegrity) - ) { - const assets = - typeof compilation.getAssets === 'function' - ? compilation.getAssets() - : Object.entries(compilation.assets).map(([name, asset]) => ({ - name, - source: asset, - })); - sri = {}; - for (const asset of assets) { - if (!asset.name.endsWith('.js')) { - continue; - } - const source = asset.source.source().toString(); - const hash = createHash('sha384').update(source).digest('base64'); - sri[combineURLs(assetPrefix, asset.name)] = `sha384-${hash}`; - } - } + if (routeChunkOptions?.isBuild) { + const entryAssets = stats?.assetsByChunkName?.['entry.client']; + const entryJsAssets = + entryAssets?.filter(asset => asset.endsWith('.js')) || []; + const manifestPath = getReactRouterManifestPath({ + version: manifest.version, + isBuild: true, + entryModulePath: entryJsAssets[0], + }); + const manifestSource = `window.__reactRouterManifest=${jsesc(manifest, { + es6: true, + })};`; + const source = new sources.RawSource(manifestSource); + if (compilation.getAsset(manifestPath)) { + compilation.updateAsset(manifestPath, source); + } else { + compilation.emitAsset(manifestPath, source); + } + } + + if (generatedManifests) { + generatedManifests.set(compilation, { + manifest, + moduleExportsByRouteId, + manifestStats: stats, + assetPrefix: currentAssetPrefix, + }); + return; + } + + options?.onManifest?.(manifest, undefined, moduleExportsByRouteId, { + compilation, + manifestStats: stats, + }); + } + ); - options?.onManifest?.(manifest, sri); - callback(); + if (generatedManifests) { + api.processAssets( + { stage: 'report', environments: ['web'] }, + ({ compilation }) => { + const generatedManifest = generatedManifests.get(compilation); + if (!generatedManifest) { + return; } - ); - }, - }; + + generatedManifests.delete(compilation); + options?.onManifest?.( + generatedManifest.manifest, + createSubresourceIntegrity( + compilation, + generatedManifest.assetPrefix + ), + generatedManifest.moduleExportsByRouteId, + { + compilation, + manifestStats: generatedManifest.manifestStats, + } + ); + } + ); + } } diff --git a/src/parallel-route-transform-protocol.ts b/src/parallel-route-transform-protocol.ts new file mode 100644 index 0000000..2492922 --- /dev/null +++ b/src/parallel-route-transform-protocol.ts @@ -0,0 +1,35 @@ +import type { + RouteTransformResult, + RouteTransformTask, +} from './route-transform-tasks.js'; + +type WithoutRequiredSource = Task extends RouteTransformTask + ? Omit & { code?: string } + : never; + +export type CachedRouteTransformTask = + WithoutRequiredSource; + +export type WorkerRequest = { + id: number; + task: RouteTransformTask | CachedRouteTransformTask; + sourceCacheKey?: string; +}; + +export type WorkerErrorPayload = { + name?: string; + message: string; + stack?: string; +}; + +export type WorkerResponse = + | { + id: number; + ok: true; + result: RouteTransformResult; + } + | { + id: number; + ok: false; + error: WorkerErrorPayload; + }; diff --git a/src/parallel-route-transform-worker.ts b/src/parallel-route-transform-worker.ts new file mode 100644 index 0000000..36e85e2 --- /dev/null +++ b/src/parallel-route-transform-worker.ts @@ -0,0 +1,82 @@ +import { parentPort } from 'node:worker_threads'; +import { setBoundedCacheEntry } from './bounded-cache.js'; +import { + executeRouteTransformTask, + type RouteTransformTask, +} from './route-transform-tasks.js'; +import type { + WorkerErrorPayload, + WorkerRequest, + WorkerResponse, +} from './parallel-route-transform-protocol.js'; + +const serializeError = (error: unknown): WorkerErrorPayload => { + if (error instanceof Error) { + return { + name: error.name, + message: error.message, + stack: error.stack, + }; + } + return { + message: String(error), + }; +}; + +if (!parentPort) { + throw new Error('parallel route transform worker requires parentPort'); +} + +const MAX_SOURCE_CACHE_ENTRIES = 2048; +const sourceCache = new Map(); + +const hydrateTaskSource = ({ + task, + sourceCacheKey, +}: Pick): RouteTransformTask => { + if (!sourceCacheKey) { + return task as RouteTransformTask; + } + + if (typeof task.code === 'string') { + setBoundedCacheEntry( + sourceCache, + sourceCacheKey, + task.code, + MAX_SOURCE_CACHE_ENTRIES + ); + return task as RouteTransformTask; + } + + const code = sourceCache.get(sourceCacheKey); + if (code === undefined) { + throw new Error( + `Missing cached route transform source for ${sourceCacheKey}.` + ); + } + return { + ...task, + code, + } as RouteTransformTask; +}; + +parentPort.on( + 'message', + async ({ id, task, sourceCacheKey }: WorkerRequest) => { + try { + const hydratedTask = hydrateTaskSource({ task, sourceCacheKey }); + const result = await executeRouteTransformTask(hydratedTask); + parentPort?.postMessage({ + id, + ok: true, + result, + } satisfies WorkerResponse); + } catch (error) { + parentPort?.postMessage({ + id, + ok: false, + error: serializeError(error), + } satisfies WorkerResponse); + } + } +); diff --git a/src/parallel-route-transforms.ts b/src/parallel-route-transforms.ts new file mode 100644 index 0000000..3dd61ff --- /dev/null +++ b/src/parallel-route-transforms.ts @@ -0,0 +1,391 @@ +import { Worker } from 'node:worker_threads'; +import { setBoundedCacheEntry } from './bounded-cache.js'; +import { + getAvailableCpuCount, + getDefaultConcurrency, +} from './concurrency.js'; +import { + executeRouteTransformTask, + type RouteTransformResult, + type RouteTransformTask, + type RouteTransformTaskOptions, +} from './route-transform-tasks.js'; +import type { PluginOptions } from './types.js'; +import type { + WorkerErrorPayload, + WorkerRequest, + WorkerResponse, +} from './parallel-route-transform-protocol.js'; + +export type ParallelTransformsConfig = + NonNullable extends infer Config + ? Exclude + : never; + +export type RouteTransformExecutorOptions = RouteTransformTaskOptions & { + parallelTransforms?: PluginOptions['parallelTransforms']; + splitRouteModules?: boolean; +}; + +export type RouteTransformExecutor = { + run: (task: RouteTransformTask) => Promise; + close: () => Promise; +}; + +type PendingTask = { + resolve: (result: RouteTransformResult) => void; + reject: (error: Error) => void; +}; + +type RouteTransformWorker = { + on(event: 'message', handler: (response: WorkerResponse) => void): unknown; + on(event: 'error', handler: (error: Error) => void): unknown; + on(event: 'exit', handler: (code: number) => void): unknown; + postMessage(message: WorkerRequest): void; + terminate(): Promise | number; +}; + +type RouteTransformWorkerFactory = () => RouteTransformWorker; + +type WorkerState = { + worker: RouteTransformWorker; + pending: Map; + sourceCache: Map; +}; + +class WorkerStartupError extends Error { + constructor(message: string) { + super(message); + this.name = 'WorkerStartupError'; + } +} + +const MAX_WORKER_SOURCE_CACHE_ENTRIES = 2048; +const DEFAULT_WORKER_COUNT_LIMIT = 4; +const SMALL_MACHINE_WORKER_COUNT_LIMIT = 1; +const SMALL_MACHINE_CPU_COUNT = 4; + +export const getDefaultWorkerCount = (cpuCount?: number): number => { + const resolvedCpuCount = cpuCount ?? getAvailableCpuCount(); + const workerCount = getDefaultConcurrency(resolvedCpuCount); + if (workerCount < 1) { + return 0; + } + const workerLimit = + resolvedCpuCount <= SMALL_MACHINE_CPU_COUNT + ? SMALL_MACHINE_WORKER_COUNT_LIMIT + : DEFAULT_WORKER_COUNT_LIMIT; + return Math.min(workerCount, workerLimit); +}; + +const getConfiguredWorkerCount = ( + parallelTransforms: ParallelTransformsConfig +): number => { + if (!Number.isInteger(parallelTransforms) || parallelTransforms < 1) { + throw new Error( + '[react-router] parallelTransforms must be false or a positive integer.' + ); + } + return parallelTransforms; +}; + +const hashString = (value: string): number => { + let hash = 0; + for (let index = 0; index < value.length; index += 1) { + hash = (hash * 31 + value.charCodeAt(index)) >>> 0; + } + return hash; +}; + +const deserializeWorkerError = (error: WorkerErrorPayload): Error => { + const result = new Error(error.message); + result.name = error.name ?? 'Error'; + if (error.stack) { + result.stack = error.stack; + } + return result; +}; + +const createWorkerUrl = (): URL => + new URL('./parallel-route-transform-worker.js', import.meta.url); + +const createDefaultWorker = (): RouteTransformWorker => + new Worker(createWorkerUrl()); + +const isWorkerStartupError = (error: unknown): error is WorkerStartupError => + error instanceof WorkerStartupError; + +class ParallelRouteTransformExecutor implements RouteTransformExecutor { + #closed = false; + #closePromise: Promise | undefined; + #workersDisabled = false; + #nextId = 1; + #nextRouteModuleWorkerIndex = 0; + #nextSplitRouteAnalysisWorkerIndex = 0; + #splitRouteAnalysisWorkers = new Map(); + #workers: WorkerState[] | undefined; + + constructor( + private readonly workerCount: number, + private readonly options: RouteTransformTaskOptions, + private readonly balanceRouteModuleTransforms: boolean, + private readonly createWorker: RouteTransformWorkerFactory + ) {} + + async run(task: RouteTransformTask): Promise { + if (this.#closed) { + return executeRouteTransformTask(task, this.options); + } + if (task.kind === 'clientOnlyStub' && task.resolveExportAllModule) { + return executeRouteTransformTask(task, this.options); + } + + try { + return await this.#runInWorker(task); + } catch (error) { + if (isWorkerStartupError(error)) { + return executeRouteTransformTask(task, this.options); + } + throw error; + } + } + + close(): Promise { + if (this.#closePromise) { + return this.#closePromise; + } + this.#closed = true; + const workers = this.#workers ?? []; + this.#workers = []; + this.#closePromise = Promise.all( + workers.map(async state => { + for (const pending of state.pending.values()) { + pending.reject(new Error('Route transform worker closed.')); + } + state.pending.clear(); + await state.worker.terminate(); + }) + ).then(() => undefined); + return this.#closePromise; + } + + #disableWorkers(error: WorkerStartupError): void { + if (this.#workersDisabled || this.#closed) { + return; + } + this.#workersDisabled = true; + const workers = this.#workers ?? []; + this.#workers = []; + for (const state of workers) { + for (const pending of state.pending.values()) { + pending.reject(error); + } + state.pending.clear(); + void state.worker.terminate(); + } + } + + #createWorkerState(): WorkerState { + const worker = this.createWorker(); + const state: WorkerState = { + worker, + pending: new Map(), + sourceCache: new Map(), + }; + + worker.on('message', (response: WorkerResponse) => { + const pending = state.pending.get(response.id); + if (!pending) { + return; + } + state.pending.delete(response.id); + if (response.ok) { + pending.resolve(response.result); + } else { + pending.reject(deserializeWorkerError(response.error)); + } + }); + + worker.on('error', (error: Error) => { + const startupError = new WorkerStartupError(error.message); + startupError.stack = error.stack; + this.#disableWorkers(startupError); + }); + + worker.on('exit', code => { + if (this.#closed || this.#workersDisabled) { + return; + } + const startupError = new WorkerStartupError( + `Route transform worker exited with code ${code}.` + ); + this.#disableWorkers(startupError); + }); + + return state; + } + + #getWorkers(): WorkerState[] { + if (this.#closed || this.#workersDisabled) { + return []; + } + if (this.#workers) { + return this.#workers; + } + const workers: WorkerState[] = []; + try { + for (let index = 0; index < this.workerCount; index += 1) { + workers.push(this.#createWorkerState()); + } + } catch (error) { + for (const state of workers) { + void state.worker.terminate(); + } + this.#workers = []; + throw error; + } + this.#workers = workers; + return workers; + } + + #runInWorker(task: RouteTransformTask): Promise { + const workers = this.#getWorkers(); + const workerIndex = this.#getWorkerIndex(task, workers.length); + const state = workers[workerIndex]; + if (!state) { + return executeRouteTransformTask(task, this.options); + } + + const id = this.#nextId++; + const sourceCacheKey = task.resourcePath; + const requestTask = this.#createWorkerRequestTask( + state, + task, + sourceCacheKey + ); + return new Promise((resolve, reject) => { + state.pending.set(id, { resolve, reject }); + try { + state.worker.postMessage({ + id, + task: requestTask, + sourceCacheKey, + } satisfies WorkerRequest); + } catch (error) { + state.pending.delete(id); + // The worker may not have received the source update. Force the next + // request for this module to send its full source again. + state.sourceCache.delete(sourceCacheKey); + reject(error instanceof Error ? error : new Error(String(error))); + } + }); + } + + #createWorkerRequestTask( + state: WorkerState, + task: RouteTransformTask, + sourceCacheKey: string + ): WorkerRequest['task'] { + const cachedSource = state.sourceCache.get(sourceCacheKey); + if (cachedSource === task.code) { + const { code: _code, ...cachedTask } = task; + return cachedTask; + } + + setBoundedCacheEntry( + state.sourceCache, + sourceCacheKey, + task.code, + MAX_WORKER_SOURCE_CACHE_ENTRIES + ); + return task; + } + + #getWorkerIndex(task: RouteTransformTask, workerCount: number): number { + const safeWorkerCount = Math.max(1, workerCount); + if ( + this.balanceRouteModuleTransforms && + (task.kind === 'routeClientEntry' || + task.kind === 'routeChunk' || + task.kind === 'splitRouteExports') + ) { + const existingWorkerIndex = this.#splitRouteAnalysisWorkers.get( + task.resourcePath + ); + if (existingWorkerIndex !== undefined) { + return existingWorkerIndex % safeWorkerCount; + } + const workerIndex = + this.#nextSplitRouteAnalysisWorkerIndex % safeWorkerCount; + this.#nextSplitRouteAnalysisWorkerIndex += 1; + this.#splitRouteAnalysisWorkers.set(task.resourcePath, workerIndex); + return workerIndex; + } + if ( + this.balanceRouteModuleTransforms && + task.kind === 'routeModule' && + !(task.environmentName === 'web' && !task.ssr && task.isSpaMode) + ) { + const workerIndex = this.#nextRouteModuleWorkerIndex % safeWorkerCount; + this.#nextRouteModuleWorkerIndex += 1; + return workerIndex; + } + return hashString(task.resourcePath) % safeWorkerCount; + } +} + +export const createRouteTransformExecutor = ({ + parallelTransforms, + routeChunkCache, + splitRouteModules, +}: RouteTransformExecutorOptions = {}): RouteTransformExecutor => { + return createRouteTransformExecutorWithWorkerFactory( + { + parallelTransforms, + routeChunkCache, + splitRouteModules, + }, + createDefaultWorker + ); +}; + +const createRouteTransformExecutorWithWorkerFactory = ( + { + parallelTransforms, + routeChunkCache, + splitRouteModules, + }: RouteTransformExecutorOptions = {}, + createWorker: RouteTransformWorkerFactory +): RouteTransformExecutor => { + const options = { routeChunkCache }; + if (parallelTransforms === false) { + return { + run: task => executeRouteTransformTask(task, options), + close: async () => {}, + }; + } + + const workerCount = + parallelTransforms === undefined + ? getDefaultWorkerCount() + : getConfiguredWorkerCount(parallelTransforms); + if (workerCount < 1) { + return { + run: task => executeRouteTransformTask(task, options), + close: async () => {}, + }; + } + + return new ParallelRouteTransformExecutor( + workerCount, + options, + Boolean(splitRouteModules), + createWorker + ); +}; + +export const createRouteTransformExecutorForTesting = ( + options: RouteTransformExecutorOptions, + createWorker: RouteTransformWorkerFactory +): RouteTransformExecutor => + createRouteTransformExecutorWithWorkerFactory(options, createWorker); diff --git a/src/performance.ts b/src/performance.ts new file mode 100644 index 0000000..f15c8e2 --- /dev/null +++ b/src/performance.ts @@ -0,0 +1,254 @@ +type OperationTiming = { + count: number; + // Total sums every recorded duration, so parallel work can make it larger + // than elapsed wall-clock time. Use wallMs for non-overlapping elapsed time. + totalMs: number; + wallMs: number; + maxMs: number; + slowest: Array<{ + durationMs: number; + resource: string; + }>; +}; + +type OperationInterval = { startMs: number; endMs: number }; + +type MutableOperationTiming = Omit & { + slowest: Array<{ + durationMs: number; + resource: string; + }>; + intervals: OperationInterval[]; +}; + +type EnvironmentTimings = Map; + +const MAX_SLOWEST_ENTRIES = 5; + +const insertSlowestEntry = ( + slowest: MutableOperationTiming['slowest'], + entry: MutableOperationTiming['slowest'][number] +) => { + if ( + slowest.length === MAX_SLOWEST_ENTRIES && + entry.durationMs <= slowest[slowest.length - 1].durationMs + ) { + return; + } + + let insertIndex = slowest.length; + while ( + insertIndex > 0 && + entry.durationMs > slowest[insertIndex - 1].durationMs + ) { + insertIndex -= 1; + } + slowest.splice(insertIndex, 0, entry); + if (slowest.length > MAX_SLOWEST_ENTRIES) { + slowest.pop(); + } +}; + +export const roundMs = (value: number): number => Math.round(value * 10) / 10; + +export type ReactRouterPerformanceReport = { + environment: string; + compilerLifecycleMs?: number; + operations: Record; +}; + +export type ReactRouterPerformanceProfiler = { + record( + environment: string | undefined, + operation: string, + resource: string, + callback: () => Promise + ): Promise; + recordSync( + environment: string | undefined, + operation: string, + resource: string, + callback: () => T + ): T; + flush( + environment: string | undefined, + details?: Pick + ): void; +}; + +export const createReactRouterPerformanceProfiler = ({ + enabled, + log, +}: { + enabled: boolean; + log: (message: string) => void; +}): ReactRouterPerformanceProfiler => { + const timingsByEnvironment = new Map(); + + const getOperationTiming = ( + environment: string, + operation: string + ): MutableOperationTiming => { + let timings = timingsByEnvironment.get(environment); + if (!timings) { + timings = new Map(); + timingsByEnvironment.set(environment, timings); + } + + let timing = timings.get(operation); + if (!timing) { + timing = { + count: 0, + totalMs: 0, + maxMs: 0, + slowest: [], + intervals: [], + }; + timings.set(operation, timing); + } + return timing; + }; + + const computeWallMs = (intervals: OperationInterval[]) => { + if (intervals.length === 0) { + return 0; + } + + const sortedIntervals = [...intervals].sort( + (a, b) => a.startMs - b.startMs || a.endMs - b.endMs + ); + let mergedStart = sortedIntervals[0].startMs; + let mergedEnd = sortedIntervals[0].endMs; + let wallMs = 0; + + for (const interval of sortedIntervals.slice(1)) { + if (interval.startMs <= mergedEnd) { + mergedEnd = Math.max(mergedEnd, interval.endMs); + continue; + } + + wallMs += mergedEnd - mergedStart; + mergedStart = interval.startMs; + mergedEnd = interval.endMs; + } + + wallMs += mergedEnd - mergedStart; + return roundMs(wallMs); + }; + + const toOperationTiming = ( + timing: MutableOperationTiming + ): OperationTiming => ({ + count: timing.count, + totalMs: roundMs(timing.totalMs), + wallMs: computeWallMs(timing.intervals), + maxMs: roundMs(timing.maxMs), + slowest: timing.slowest.map(entry => ({ + durationMs: roundMs(entry.durationMs), + resource: entry.resource, + })), + }); + + const recordDuration = ( + environment: string, + operation: string, + resource: string, + startMs: number, + endMs: number + ) => { + const duration = endMs - startMs; + const timing = getOperationTiming(environment, operation); + timing.count += 1; + timing.totalMs += duration; + timing.maxMs = Math.max(timing.maxMs, duration); + timing.intervals.push({ startMs, endMs }); + insertSlowestEntry(timing.slowest, { + durationMs: duration, + resource, + }); + }; + + return { + record(environment, operation, resource, callback) { + if (!enabled) { + try { + return Promise.resolve(callback()); + } catch (error) { + return Promise.reject(error); + } + } + + const resolvedEnvironment = environment ?? 'unknown'; + const start = performance.now(); + try { + return callback().then( + result => { + const end = performance.now(); + recordDuration( + resolvedEnvironment, + operation, + resource, + start, + end + ); + return result; + }, + error => { + const end = performance.now(); + recordDuration( + resolvedEnvironment, + operation, + resource, + start, + end + ); + throw error; + } + ); + } catch (error) { + const end = performance.now(); + recordDuration(resolvedEnvironment, operation, resource, start, end); + return Promise.reject(error); + } + }, + recordSync(environment, operation, resource, callback) { + if (!enabled) { + return callback(); + } + + const resolvedEnvironment = environment ?? 'unknown'; + const start = performance.now(); + try { + return callback(); + } finally { + const end = performance.now(); + recordDuration(resolvedEnvironment, operation, resource, start, end); + } + }, + flush(environment, details = {}) { + if (!enabled) { + return; + } + + const resolvedEnvironment = environment ?? 'unknown'; + const timings = timingsByEnvironment.get(resolvedEnvironment); + if (!timings || timings.size === 0) { + return; + } + + const operations = Object.fromEntries( + [...timings.entries()].map(([operation, timing]) => [ + operation, + toOperationTiming(timing), + ]) + ); + const report: ReactRouterPerformanceReport = { + environment: resolvedEnvironment, + ...details, + operations, + }; + log(`[react-router:performance] ${JSON.stringify(report)}`); + timingsByEnvironment.delete(resolvedEnvironment); + }, + }; +}; diff --git a/src/plugin-utils.ts b/src/plugin-utils.ts index 519c18a..5156198 100644 --- a/src/plugin-utils.ts +++ b/src/plugin-utils.ts @@ -1,101 +1,6 @@ -import { - deadCodeElimination, - findReferencedIdentifiers, -} from 'babel-dead-code-elimination'; import { normalize } from 'pathe'; import { existsSync } from 'node:fs'; -import type { Babel, NodePath, ParseResult } from './babel.js'; -import { t, traverse } from './babel.js'; -import { NAMED_COMPONENT_EXPORTS, JS_EXTENSIONS } from './constants.js'; - -export function validateDestructuredExports( - id: Babel.ArrayPattern | Babel.ObjectPattern, - exportsToRemove: string[] -): void { - if (id.type === 'ArrayPattern') { - for (const element of id.elements) { - if (!element) { - continue; - } - - // [ foo ] - if ( - element.type === 'Identifier' && - exportsToRemove.includes(element.name) - ) { - throw invalidDestructureError(element.name); - } - - // [ ...foo ] - if ( - element.type === 'RestElement' && - element.argument.type === 'Identifier' && - exportsToRemove.includes(element.argument.name) - ) { - throw invalidDestructureError(element.argument.name); - } - - // [ [...] ] - // [ {...} ] - if (element.type === 'ArrayPattern' || element.type === 'ObjectPattern') { - validateDestructuredExports(element, exportsToRemove); - } - } - } - - if (id.type === 'ObjectPattern') { - for (const property of id.properties) { - if (!property) { - continue; - } - - if ( - property.type === 'ObjectProperty' && - property.key.type === 'Identifier' - ) { - // { foo } - if ( - property.value.type === 'Identifier' && - exportsToRemove.includes(property.value.name) - ) { - throw invalidDestructureError(property.value.name); - } - - // { foo: [...] } - // { foo: {...} } - if ( - property.value.type === 'ArrayPattern' || - property.value.type === 'ObjectPattern' - ) { - validateDestructuredExports(property.value, exportsToRemove); - } - } - - // { ...foo } - if ( - property.type === 'RestElement' && - property.argument.type === 'Identifier' && - exportsToRemove.includes(property.argument.name) - ) { - throw invalidDestructureError(property.argument.name); - } - } - } -} - -export function invalidDestructureError(name: string): Error { - return new Error(`Cannot remove destructured export "${name}"`); -} - -export function toFunctionExpression(decl: Babel.FunctionDeclaration): any { - return t.functionExpression( - decl.id, - decl.params, - decl.body, - decl.generator, - decl.async - ); -} +import { JS_EXTENSIONS } from './constants.js'; export function combineURLs(baseURL: string, relativeURL: string): string { return relativeURL @@ -118,11 +23,6 @@ export function createRouteId(file: string): string { return normalize(stripFileExtension(file)); } -/** - * Find a file with any of the supported JavaScript extensions - * @param basePath - The base path without extension - * @returns The file path with extension if found, or a default path - */ export function findEntryFile(basePath: string): string { for (const ext of JS_EXTENSIONS) { const filePath = `${basePath}${ext}`; @@ -130,7 +30,7 @@ export function findEntryFile(basePath: string): string { return filePath; } } - return `${basePath}.tsx`; // Default to .tsx if no file exists + return `${basePath}.tsx`; } export function generateWithProps() { @@ -173,284 +73,10 @@ export function generateWithProps() { `; } -export const removeExports = ( - ast: ParseResult, - exportsToRemove: string[] -): void => { - const previouslyReferencedIdentifiers = findReferencedIdentifiers(ast); - let exportsFiltered = false; - const markedForRemoval = new Set>(); - // Keep track of identifiers referenced by removed exports, - // e.g. export { localName as exportName }, export default function localName - const removedExportLocalNames = new Set(); - - traverse(ast, { - ExportDeclaration(path: NodePath) { - // export { foo }; - // export { bar } from "./module"; - if (path.node.type === 'ExportNamedDeclaration') { - if (path.node.specifiers.length) { - //@ts-ignore - path.node.specifiers = path.node.specifiers.filter( - ( - specifier: - | Babel.ExportSpecifier - | Babel.ExportDefaultSpecifier - | Babel.ExportNamespaceSpecifier - ) => { - // Filter out individual specifiers - if ( - specifier.type === 'ExportSpecifier' && - specifier.exported.type === 'Identifier' - ) { - if (exportsToRemove.includes(specifier.exported.name)) { - exportsFiltered = true; - // Track the local identifier if it's different from the exported name - if ( - specifier.local && - specifier.local.type === 'Identifier' && - specifier.local.name !== specifier.exported.name - ) { - removedExportLocalNames.add(specifier.local.name); - } - return false; - } - } - return true; - } - ); - // Remove the entire export statement if all specifiers were removed - if (path.node.specifiers.length === 0) { - markedForRemoval.add(path); - } - } - - // export const foo = ...; - // export const [ foo ] = ...; - if (path.node.declaration?.type === 'VariableDeclaration') { - const declaration = path.node.declaration; - declaration.declarations = declaration.declarations.filter( - (declaration: Babel.VariableDeclarator) => { - // export const foo = ...; - // export const foo = ..., bar = ...; - if ( - declaration.id.type === 'Identifier' && - exportsToRemove.includes(declaration.id.name) - ) { - // Filter out individual variables - exportsFiltered = true; - return false; - } - - // export const [ foo ] = ...; - // export const { foo } = ...; - if ( - declaration.id.type === 'ArrayPattern' || - declaration.id.type === 'ObjectPattern' - ) { - // NOTE: These exports cannot be safely removed, so instead we - // validate them to ensure that any exports that are intended to - // be removed are not present - validateDestructuredExports(declaration.id, exportsToRemove); - } - - return true; - } - ); - // Remove the entire export statement if all variables were removed - if (declaration.declarations.length === 0) { - markedForRemoval.add(path); - } - } - - // export function foo() {} - if (path.node.declaration?.type === 'FunctionDeclaration') { - const id = path.node.declaration.id; - if (id && exportsToRemove.includes(id.name)) { - markedForRemoval.add(path); - } - } - - // export class Foo() {} - if (path.node.declaration?.type === 'ClassDeclaration') { - const id = path.node.declaration.id; - if (id && exportsToRemove.includes(id.name)) { - markedForRemoval.add(path); - } - } - } - - // export default ...; - if ( - path.node.type === 'ExportDefaultDeclaration' && - exportsToRemove.includes('default') - ) { - markedForRemoval.add(path); - // Track the identifier being exported as default - if (path.node.declaration) { - if (path.node.declaration.type === 'Identifier') { - removedExportLocalNames.add(path.node.declaration.name); - } else if ( - (path.node.declaration.type === 'FunctionDeclaration' || - path.node.declaration.type === 'ClassDeclaration') && - path.node.declaration.id - ) { - removedExportLocalNames.add(path.node.declaration.id.name); - } - } - } - }, - }); - - // Remove top-level property assignments to removed exports. Handles - // `clientLoader.hydrate = true`, `Component.displayName = "..."`, etc. - traverse(ast, { - ExpressionStatement(path: NodePath) { - // Only handle top-level statements - if (!path.parentPath.isProgram()) { - return; - } - - const expr = path.node.expression; - if (expr.type !== 'AssignmentExpression') { - return; - } - - const left = expr.left; - if ( - left.type === 'MemberExpression' && - left.object.type === 'Identifier' && - (exportsToRemove.includes(left.object.name) || - removedExportLocalNames.has(left.object.name)) - ) { - markedForRemoval.add(path as any); - } - }, - }); - - if (markedForRemoval.size > 0 || exportsFiltered) { - for (const path of markedForRemoval) { - path.remove(); - } - - // Run dead code elimination on any newly unreferenced identifiers - deadCodeElimination(ast, previouslyReferencedIdentifiers); - } -}; - -export const removeUnusedImports = (ast: ParseResult): void => { - let scopeCrawled = false; - traverse(ast, { - Program(path: NodePath) { - if (!scopeCrawled) { - path.scope.crawl(); - scopeCrawled = true; - } - }, - ImportDeclaration(path: NodePath) { - if (path.node.specifiers.length === 0) { - return; - } - - const specifierPaths = path.get('specifiers') as NodePath< - | Babel.ImportSpecifier - | Babel.ImportDefaultSpecifier - | Babel.ImportNamespaceSpecifier - >[]; - - for (const specifierPath of specifierPaths) { - const local = specifierPath.node.local; - const binding = local ? path.scope.getBinding(local.name) : null; - if (!binding || !binding.referenced) { - specifierPath.remove(); - } - } - - if (path.node.specifiers.length === 0) { - path.remove(); - } - }, - }); -}; - -export const transformRoute = (ast: ParseResult): void => { - const hocs: Array<[string, Babel.Identifier]> = []; - function getHocUid(path: NodePath, hocName: string) { - const uid = path.scope.generateUidIdentifier(hocName); - hocs.push([hocName, uid]); - return uid; - } - - traverse(ast, { - ExportDeclaration(path: NodePath) { - if (path.isExportDefaultDeclaration()) { - const declaration = path.get('declaration'); - // prettier-ignore - const expr = - declaration.isExpression() ? declaration.node : - declaration.isFunctionDeclaration() ? toFunctionExpression(declaration.node) : - undefined - if (expr) { - const uid = getHocUid(path, 'withComponentProps'); - declaration.replaceWith(t.callExpression(uid, [expr]) as any); - } - return; - } - - if (path.isExportNamedDeclaration()) { - const decl = path.get('declaration'); - - if (decl.isVariableDeclaration()) { - // biome-ignore lint/complexity/noForEach: - decl.get('declarations').forEach((varDeclarator: NodePath) => { - const id = varDeclarator.get('id') as any; - const init = varDeclarator.get('init') as any; - const expr = init.node as any; - if (!expr) return; - if (!id.isIdentifier()) return; - const { name } = id.node; - if (!isNamedComponentExport(name)) return; - - const uid = getHocUid(path, `with${name}Props`); - init.replaceWith(t.callExpression(uid, [expr])); - }); - return; - } - - if (decl.isFunctionDeclaration()) { - const { id } = decl.node; - if (!id) return; - const { name } = id; - if (!isNamedComponentExport(name)) return; - - const uid = getHocUid(path, `with${name}Props`); - decl.replaceWith( - t.variableDeclaration('const', [ - t.variableDeclarator( - t.identifier(name), - t.callExpression(uid, [toFunctionExpression(decl.node)]) - ), - ]) as any - ); - } - } - }, - }); - - if (hocs.length > 0) { - ast.program.body.unshift( - t.importDeclaration( - hocs.map(([name, identifier]) => - t.importSpecifier(identifier, t.identifier(name)) - ), - t.stringLiteral('virtual/react-router/with-props') - ) as any - ); - } -}; - -function isNamedComponentExport( - name: string -): name is (typeof NAMED_COMPONENT_EXPORTS)[number] { - return (NAMED_COMPONENT_EXPORTS as readonly string[]).includes(name); -} +export { + invalidDestructureError, + removeExports, + removeUnusedImports, + validateDestructuredExports, +} from './route-export-pruning.js'; +export { transformRoute } from './route-component-transform.js'; diff --git a/src/prerender-build.ts b/src/prerender-build.ts new file mode 100644 index 0000000..098782b --- /dev/null +++ b/src/prerender-build.ts @@ -0,0 +1,623 @@ +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { pathToFileURL } from 'node:url'; +import fsExtra from 'fs-extra'; +import type { RsbuildPluginAPI } from '@rsbuild/core'; +import { + createRequestHandler, + matchRoutes, + type ServerBuild, +} from 'react-router'; +import { dirname, relative, resolve } from 'pathe'; +import { PLUGIN_NAME } from './constants.js'; +import { getBuildManifest } from './build-manifest.js'; +import { + generateReactRouterManifestForDev, + getReactRouterManifestForDev, + type ReactRouterManifestStats, + type RouteManifestModuleExports, +} from './manifest.js'; +import { + createPrerenderRoutes, + getPrerenderConcurrency, + getSsrFalsePrerenderExportErrors, + normalizePrerenderMatchPath, + withBuildRequest, +} from './prerender.js'; +import type { + Config, + ResolvedReactRouterConfig, +} from './react-router-config.js'; +import { resolveServerBuildModule } from './server-utils.js'; +import type { PluginOptions, Route } from './types.js'; + +type ReactRouterManifest = Awaited< + ReturnType +>; + +type BuildRouteModule = { + loader?: unknown; + default?: unknown; + ErrorBoundary?: unknown; +}; + +type PrerenderServerBuild = ServerBuild & { + routes: Record; + assets?: { + routes?: Record; + }; + prerender?: string[]; +}; + +type PrerenderBuildApi = Pick< + RsbuildPluginAPI, + 'logger' | 'getNormalizedConfig' +>; + +type RunReactRouterPrerenderBuildOptions = { + api: PrerenderBuildApi; + hasWebEnvironment: boolean; + buildDirectory: string; + serverBuildFile?: string; + ssr: boolean; + isPrerenderEnabled: boolean; + prerenderConfig: Config['prerender']; + prerenderPaths: string[]; + basename: string; + future: ResolvedReactRouterConfig['future']; + routes: Record; + latestBrowserManifest: ReactRouterManifest | null; + latestBrowserManifestModuleExports: RouteManifestModuleExports; + clientStats: ReactRouterManifestStats | undefined; + pluginOptions: PluginOptions; + appDirectory: string; + assetPrefix: string; + routeChunkOptions: Parameters[5]; + buildManifest: Awaited>; + resolvedConfigWithRoutes: ResolvedReactRouterConfig; + buildEnd: Config['buildEnd']; +}; + +const redirectStatusCodes = new Set([301, 302, 303, 307, 308]); + +const getServerBuildPath = async ({ + buildDirectory, + serverBuildFile, +}: { + buildDirectory: string; + serverBuildFile?: string; +}): Promise<{ serverBuildDir: string; serverBuildPath: string }> => { + const serverBuildDir = resolve(buildDirectory, 'server'); + const defaultServerBuildFile = 'static/js/app.js'; + const configuredServerBuildFile = serverBuildFile || 'index.js'; + const configuredServerBuildPath = resolve( + serverBuildDir, + configuredServerBuildFile + ); + const defaultServerBuildPath = resolve( + serverBuildDir, + defaultServerBuildFile + ); + + if ( + configuredServerBuildFile !== defaultServerBuildFile && + existsSync(defaultServerBuildPath) && + !existsSync(configuredServerBuildPath) + ) { + await mkdir(dirname(configuredServerBuildPath), { recursive: true }); + await fsExtra.copy(defaultServerBuildPath, configuredServerBuildPath); + } + + return { + serverBuildDir, + serverBuildPath: existsSync(configuredServerBuildPath) + ? configuredServerBuildPath + : defaultServerBuildPath, + }; +}; + +const createDataRequestPath = ( + prerenderPath: string, + trailingSlashAwareDataRequests: boolean +): string => { + if (trailingSlashAwareDataRequests) { + return prerenderPath.endsWith('/') + ? `${prerenderPath}_.data` + : `${prerenderPath}.data`; + } + + return prerenderPath === '/' + ? '/_root.data' + : `${prerenderPath.replace(/\/$/, '')}.data`; +}; + +const prerenderData = async ({ + handler, + prerenderPath, + onlyRoutes, + clientBuildDir, + basename, + trailingSlashAwareDataRequests, + api, + requestInit, +}: { + handler: (request: Request) => Promise; + prerenderPath: string; + onlyRoutes: string[] | null; + clientBuildDir: string; + basename: string; + trailingSlashAwareDataRequests: boolean; + api: PrerenderBuildApi; + requestInit?: RequestInit; +}): Promise => { + const dataRequestPath = createDataRequestPath( + prerenderPath, + trailingSlashAwareDataRequests + ); + const normalizedPath = `${basename}${dataRequestPath}`.replace(/\/\/+/g, '/'); + const url = new URL(`http://localhost${normalizedPath}`); + if (onlyRoutes?.length) { + url.searchParams.set('_routes', onlyRoutes.join(',')); + } + + return withBuildRequest(url, requestInit, async request => { + const response = await handler(request); + const data = await response.text(); + + if (response.status !== 200 && response.status !== 202) { + throw new Error( + `Prerender (data): Received a ${response.status} status code from ` + + `\`entry.server.tsx\` while prerendering the \`${prerenderPath}\` path.\n` + + `${normalizedPath}` + ); + } + + const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/')); + await mkdir(dirname(outputPath), { recursive: true }); + await writeFile(outputPath, data); + api.logger.info( + `Prerender (data): ${prerenderPath} -> ${relative( + process.cwd(), + outputPath + )}` + ); + return data; + }); +}; + +const prerenderRoute = async ({ + handler, + prerenderPath, + clientBuildDir, + basename, + api, + requestInit, +}: { + handler: (request: Request) => Promise; + prerenderPath: string; + clientBuildDir: string; + basename: string; + api: PrerenderBuildApi; + requestInit?: RequestInit; +}): Promise => { + const normalizedPath = `${basename}${prerenderPath}/`.replace(/\/\/+/g, '/'); + await withBuildRequest( + `http://localhost${normalizedPath}`, + requestInit, + async request => { + const response = await handler(request); + let html = await response.text(); + + if (redirectStatusCodes.has(response.status)) { + const location = response.headers.get('Location'); + const delay = response.status === 302 ? 2 : 0; + html = ` + +Redirecting to: ${location} + + + + +\t + Redirecting from ${normalizedPath} to ${location} + + +`; + } else if (response.status !== 200) { + throw new Error( + `Prerender (html): Received a ${response.status} status code from ` + + `\`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.\n` + + html + ); + } + + const outputPath = resolve( + clientBuildDir, + ...normalizedPath.split('/'), + 'index.html' + ); + await mkdir(dirname(outputPath), { recursive: true }); + await writeFile(outputPath, html); + api.logger.info( + `Prerender (html): ${prerenderPath} -> ${relative( + process.cwd(), + outputPath + )}` + ); + } + ); +}; + +const prerenderResourceRoute = async ({ + handler, + prerenderPath, + clientBuildDir, + basename, + api, + requestInit, +}: { + handler: (request: Request) => Promise; + prerenderPath: string; + clientBuildDir: string; + basename: string; + api: PrerenderBuildApi; + requestInit?: RequestInit; +}): Promise => { + const normalizedPath = `${basename}${prerenderPath}/` + .replace(/\/\/+/g, '/') + .replace(/\/$/g, ''); + await withBuildRequest( + `http://localhost${normalizedPath}`, + requestInit, + async request => { + const response = await handler(request); + const content = Buffer.from(await response.arrayBuffer()); + + if (response.status !== 200) { + throw new Error( + `Prerender (resource): Received a ${response.status} status code from ` + + `\`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.\n` + + content.toString('utf8') + ); + } + + const outputPath = resolve(clientBuildDir, ...normalizedPath.split('/')); + await mkdir(dirname(outputPath), { recursive: true }); + await writeFile(outputPath, content); + api.logger.info( + `Prerender (resource): ${prerenderPath} -> ${relative( + process.cwd(), + outputPath + )}` + ); + } + ); +}; + +const handleSpaMode = async ({ + handler, + build, + clientBuildDir, + basename, + api, +}: { + handler: (request: Request) => Promise; + build: PrerenderServerBuild; + clientBuildDir: string; + basename: string; + api: PrerenderBuildApi; +}): Promise => { + await withBuildRequest( + `http://localhost${basename}`, + { + headers: { + 'X-React-Router-SPA-Mode': 'yes', + }, + }, + async request => { + const response = await handler(request); + const html = await response.text(); + const isPrerenderSpaFallback = build.prerender?.includes('/'); + const filename = isPrerenderSpaFallback + ? '__spa-fallback.html' + : 'index.html'; + + if (response.status !== 200) { + if (isPrerenderSpaFallback) { + throw new Error( + `Prerender: Received a ${response.status} status code from ` + + `\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` + + html + ); + } + throw new Error( + `SPA Mode: Received a ${response.status} status code from ` + + `\`entry.server.tsx\` while prerendering your \`${filename}\` file.\n` + + html + ); + } + + if ( + !html.includes('window.__reactRouterContext =') || + !html.includes('window.__reactRouterRouteModules =') + ) { + throw new Error( + 'SPA Mode: Did you forget to include `` in your root route? ' + + 'Your pre-rendered HTML cannot hydrate without ``.' + ); + } + + const outputPath = resolve(clientBuildDir, filename); + await writeFile(outputPath, html); + const prettyPath = relative(process.cwd(), outputPath); + if (build.prerender?.length) { + api.logger.info(`Prerender (html): SPA Fallback -> ${prettyPath}`); + } else { + api.logger.info(`SPA Mode: Generated ${prettyPath}`); + } + } + ); +}; + +const assertValidSsrFalsePrerenderExports = ({ + routes, + manifestRoutes, + routeExports, + prerenderPaths, + api, +}: { + routes: Record; + manifestRoutes: ReactRouterManifest['routes']; + routeExports: RouteManifestModuleExports; + prerenderPaths: string[]; + api: PrerenderBuildApi; +}) => { + const errors = getSsrFalsePrerenderExportErrors({ + routes, + manifestRoutes, + routeExports, + prerenderPaths, + }); + if (errors.length > 0) { + api.logger.error(errors.join('\n')); + throw new Error( + 'Invalid route exports found when prerendering with `ssr:false`' + ); + } +}; + +const validatePrerenderPathMatches = ( + routes: Record, + prerenderPaths: string[] +): void => { + const routeTree = createPrerenderRoutes(routes); + for (const path of prerenderPaths) { + const matches = matchRoutes(routeTree, normalizePrerenderMatchPath(path)); + if (!matches) { + throw new Error( + `Unable to prerender path because it does not match any routes: ${path}` + ); + } + } +}; + +const runPrerenderPaths = async ({ + build, + requestHandler, + clientBuildDir, + options, +}: { + build: PrerenderServerBuild; + requestHandler: (request: Request) => Promise; + clientBuildDir: string; + options: RunReactRouterPrerenderBuildOptions; +}): Promise => { + const { api, basename, future, prerenderConfig, prerenderPaths } = options; + const buildRoutes = createPrerenderRoutes(build.routes); + const concurrency = getPrerenderConcurrency(prerenderConfig); + const pending = new Set>(); + + const enqueue = async (path: string) => { + const matches = matchRoutes(buildRoutes, normalizePrerenderMatchPath(path)); + if (!matches) { + return; + } + + const leafRoute = matches[matches.length - 1]?.route; + const routeId = leafRoute?.id; + const manifestRoute = routeId ? build.routes?.[routeId]?.module : null; + const isResourceRoute = + manifestRoute && !manifestRoute.default && !manifestRoute.ErrorBoundary; + + if (isResourceRoute) { + if (manifestRoute.loader && routeId) { + await prerenderData({ + handler: requestHandler, + prerenderPath: path, + onlyRoutes: [routeId], + clientBuildDir, + basename, + trailingSlashAwareDataRequests: + future.unstable_trailingSlashAwareDataRequests, + api, + }); + await prerenderResourceRoute({ + handler: requestHandler, + prerenderPath: path, + clientBuildDir, + basename, + api, + }); + } else { + api.logger.warn( + `⚠️ Skipping prerendering for resource route without a loader: ${routeId}` + ); + } + return; + } + + const hasLoaders = matches.some(match => { + const matchedRouteId = match.route.id; + if (!matchedRouteId) { + return false; + } + return build.assets?.routes?.[matchedRouteId]?.hasLoader; + }); + let data: string | undefined; + if (hasLoaders) { + data = await prerenderData({ + handler: requestHandler, + prerenderPath: path, + onlyRoutes: null, + clientBuildDir, + basename, + trailingSlashAwareDataRequests: + future.unstable_trailingSlashAwareDataRequests, + api, + }); + } + await prerenderRoute({ + handler: requestHandler, + prerenderPath: path, + clientBuildDir, + basename, + api, + requestInit: data + ? { + headers: { + 'X-React-Router-Prerender-Data': encodeURI(data), + }, + } + : undefined, + }); + }; + + for (const path of prerenderPaths) { + const task = enqueue(path); + pending.add(task); + task.finally(() => pending.delete(task)); + if (pending.size >= concurrency) { + await Promise.race(pending); + } + } + await Promise.all(pending); +}; + +export const runReactRouterPrerenderBuild = async ( + options: RunReactRouterPrerenderBuildOptions +): Promise => { + if (!options.hasWebEnvironment) { + return; + } + + const { + api, + buildDirectory, + serverBuildFile, + ssr, + isPrerenderEnabled, + prerenderPaths, + routes, + latestBrowserManifest, + latestBrowserManifestModuleExports, + clientStats, + pluginOptions, + appDirectory, + assetPrefix, + routeChunkOptions, + buildManifest, + resolvedConfigWithRoutes, + buildEnd, + basename, + } = options; + const { serverBuildDir, serverBuildPath } = await getServerBuildPath({ + buildDirectory, + serverBuildFile, + }); + const clientBuildDir = resolve(buildDirectory, 'client'); + + if (!existsSync(serverBuildPath)) { + console.warn( + `[${PLUGIN_NAME}] Server build not found at ${serverBuildPath}. ` + + 'Skipping prerendering.' + ); + return; + } + + await mkdir(clientBuildDir, { recursive: true }); + + if (!ssr || isPrerenderEnabled) { + process.env.IS_RR_BUILD_REQUEST = 'yes'; + const buildModule = await import(pathToFileURL(serverBuildPath).toString()); + const build = (await resolveServerBuildModule( + buildModule, + `Server build ${JSON.stringify(serverBuildPath)}` + )) as PrerenderServerBuild; + const requestHandler = createRequestHandler(build, 'production'); + + if (isPrerenderEnabled) { + if (!ssr) { + const generated = latestBrowserManifest + ? { + manifest: latestBrowserManifest, + moduleExportsByRouteId: latestBrowserManifestModuleExports, + } + : await generateReactRouterManifestForDev( + routes, + pluginOptions, + clientStats, + appDirectory, + assetPrefix, + routeChunkOptions + ); + assertValidSsrFalsePrerenderExports({ + routes, + manifestRoutes: generated.manifest.routes, + routeExports: generated.moduleExportsByRouteId, + prerenderPaths, + api, + }); + } + + validatePrerenderPathMatches(routes, prerenderPaths); + + if (prerenderPaths.length > 0) { + api.logger.info( + `Prerender (html): ${prerenderPaths.length} path(s)...` + ); + } + + await runPrerenderPaths({ + build, + requestHandler, + clientBuildDir, + options, + }); + } + + if (!ssr) { + await handleSpaMode({ + handler: requestHandler, + build, + clientBuildDir, + basename, + api, + }); + } + } + + if (!ssr) { + await fsExtra.remove(serverBuildDir); + api.logger.info( + `[${PLUGIN_NAME}] Removed server build (static deployment)` + ); + } + + if (buildEnd) { + await buildEnd({ + buildManifest, + reactRouterConfig: resolvedConfigWithRoutes, + viteConfig: api.getNormalizedConfig(), + }); + } +}; diff --git a/src/prerender.ts b/src/prerender.ts index f79d7f4..d8613d6 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -1,7 +1,10 @@ import type { Config } from './react-router-config.js'; import type { RouteConfigEntry } from '@react-router/dev/routes'; +import { matchRoutes } from 'react-router'; type ReactRouterPrerenderConfig = Config['prerender']; +type MatchRouteObject = + Parameters[0] extends Array ? R : never; type PrerenderPathsConfig = | boolean @@ -35,9 +38,152 @@ type StaticPrerenderPaths = { paramRoutes: string[]; }; +type SsrFalsePrerenderRoute = { + id: string; + parentId?: string; + path?: string; + index?: boolean; + hasLoader?: boolean; + hasClientLoader?: boolean; +}; + +type SsrFalsePrerenderExportOptions = { + routes: Record; + manifestRoutes: Record; + routeExports: Record; + prerenderPaths: string[]; +}; + const normalizePath = (value: string): string => value.replace(/\/\/+/g, '/').replace(/(.+)\/$/, '$1'); +const groupRoutesByParentId = ( + manifest: Record +): Record => { + const grouped: Record = {}; + Object.values(manifest).forEach(route => { + if (!route) { + return; + } + const parentId = route.parentId || ''; + grouped[parentId] ??= []; + grouped[parentId].push(route); + }); + return grouped; +}; + +export const createPrerenderRoutes = ( + manifest: Record, + parentId = '', + grouped: Record = groupRoutesByParentId(manifest) +): MatchRouteObject[] => { + return (grouped[parentId] || []).map(route => { + const common = { id: route.id, path: route.path }; + if (route.index) { + return { index: true, ...common } as MatchRouteObject; + } + return { + ...common, + children: createPrerenderRoutes(manifest, route.id, grouped), + } as MatchRouteObject; + }); +}; + +export const normalizePrerenderMatchPath = (path: string): string => + `/${path}/`.replace(/^\/\/+/, '/'); + +export const withBuildRequest = async ( + input: string | URL, + init: RequestInit | undefined, + handle: (request: Request) => Promise +): Promise => { + const controller = new AbortController(); + try { + return await handle( + new Request(input, { + ...init, + signal: controller.signal, + }) + ); + } finally { + controller.abort(); + } +}; + +export const getSsrFalsePrerenderExportErrors = ({ + routes, + manifestRoutes, + routeExports, + prerenderPaths, +}: SsrFalsePrerenderExportOptions): string[] => { + if (prerenderPaths.length === 0) { + return []; + } + + const prerenderRoutes = createPrerenderRoutes(routes); + const prerenderedRoutes = new Set(); + for (const path of prerenderPaths) { + const matches = matchRoutes( + prerenderRoutes, + normalizePrerenderMatchPath(path) + ); + if (!matches) { + throw new Error( + `Unable to prerender path because it does not match any routes: ${path}` + ); + } + matches.forEach(match => prerenderedRoutes.add(match.route.id as string)); + } + + const errors: string[] = []; + for (const [routeId, route] of Object.entries(manifestRoutes)) { + const exports = routeExports[routeId] ?? []; + const invalidApis: string[] = []; + + if (exports.includes('headers')) invalidApis.push('headers'); + if (exports.includes('action')) invalidApis.push('action'); + + if (invalidApis.length > 0) { + errors.push( + `Prerender: ${invalidApis.length} invalid route export(s) in ` + + `\`${routeId}\` when pre-rendering with \`ssr:false\`: ` + + `${invalidApis.map(api => `\`${api}\``).join(', ')}. ` + + `See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.` + ); + } + + if (!prerenderedRoutes.has(routeId)) { + if (exports.includes('loader')) { + errors.push( + `Prerender: 1 invalid route export in \`${routeId}\` when pre-rendering with ` + + `\`ssr:false\`: \`loader\`. ` + + `See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.` + ); + } + + let parentRoute = + route.parentId && manifestRoutes[route.parentId] + ? manifestRoutes[route.parentId] + : null; + while (parentRoute) { + if (parentRoute.hasLoader && !parentRoute.hasClientLoader) { + errors.push( + `Prerender: 1 invalid route export in \`${parentRoute.id}\` when ` + + `pre-rendering with \`ssr:false\`: \`loader\`. ` + + `See https://reactrouter.com/how-to/pre-rendering#invalid-exports for more information.` + ); + } + parentRoute = + parentRoute.parentId && manifestRoutes[parentRoute.parentId] + ? manifestRoutes[parentRoute.parentId] + : null; + } + } + } + + return errors; +}; + export const getStaticPrerenderPaths = ( routes: RouteConfigEntry[] ): StaticPrerenderPaths => { @@ -144,12 +290,17 @@ export const resolvePrerenderPaths = async ( return pathsConfig ?? []; }; -export const getPrerenderConcurrency = (prerender: PrerenderConfig): number => { +export const getPrerenderConcurrency = ( + prerender: PrerenderConfig, + _cpuCount?: number +): number => { const config = getPrerenderConfigObject(prerender); const value = getPrerenderConcurrencyConfig(config)?.value; if (typeof value === 'number' && Number.isInteger(value) && value > 0) { return value; } + // Match React Router's default. Parallel prerendering can fan out loaders and + // external requests, so it must remain explicitly opt-in. return 1; }; diff --git a/src/react-router-config.ts b/src/react-router-config.ts index f095876..eb8990e 100644 --- a/src/react-router-config.ts +++ b/src/react-router-config.ts @@ -70,7 +70,7 @@ const DEFAULT_CONFIG = { buildDirectory: 'build', serverBuildFile: 'index.js', serverModuleFormat: 'esm', - splitRouteModules: false, + splitRouteModules: true, subResourceIntegrity: false, ssr: true, future: { @@ -132,6 +132,7 @@ export const resolveReactRouterConfig = async ( ): Promise<{ resolved: ResolvedReactRouterConfig; presets: NonNullable; + hasConfiguredServerModuleFormat: boolean; }> => { const presets = await Promise.all( (reactRouterUserConfig.presets ?? []).map(async preset => { @@ -183,7 +184,6 @@ export const resolveReactRouterConfig = async ( DEFAULT_CONFIG.allowedActionOrigins, routes: DEFAULT_CONFIG.routes, unstable_routeConfig: DEFAULT_CONFIG.unstable_routeConfig, - serverBundles: DEFAULT_CONFIG.serverBundles, }; if (!resolved.ssr) { resolved = { @@ -195,5 +195,7 @@ export const resolveReactRouterConfig = async ( return { resolved, presets: reactRouterUserConfig.presets ?? [], + hasConfiguredServerModuleFormat: + userAndPresetConfigs.serverModuleFormat !== undefined, }; }; diff --git a/src/route-artifacts.ts b/src/route-artifacts.ts new file mode 100644 index 0000000..58dd4de --- /dev/null +++ b/src/route-artifacts.ts @@ -0,0 +1,155 @@ +import { + CLIENT_ROUTE_EXPORTS_SET, + SERVER_ONLY_ROUTE_EXPORTS_SET, +} from './constants.js'; +import { getExportNames } from './export-utils.js'; +import { + buildEnforceChunkValidity, + detectRouteChunksIfEnabled, + emptyRouteChunkSnippet, + getRouteChunkIfEnabled, + getRouteChunkNameFromModuleId, + shouldAnalyzeRouteChunks, + validateRouteChunks, + type RouteChunkCache, + type RouteChunkConfig, +} from './route-chunks.js'; + +export type RouteClientEntryArtifactOptions = { + code: string; + resourcePath: string; + environmentName?: string; + isBuild: boolean; + routeChunkCache?: RouteChunkCache; + routeChunkConfig: RouteChunkConfig; +}; + +type RouteClientEntryArtifact = { + code: string; +}; + +export type RouteChunkArtifactOptions = { + code: string; + resource: string; + resourcePath: string; + isBuild: boolean; + routeChunkCache?: RouteChunkCache; + routeChunkConfig: RouteChunkConfig; +}; + +type RouteChunkArtifact = { + code: string; + map: null; +}; + +export const buildRouteClientEntryCode = ({ + exportNames, + chunkedExports, + isServer, + resourcePath, +}: { + exportNames: readonly string[]; + chunkedExports: readonly string[]; + isServer: boolean; + resourcePath: string; +}): string => { + const chunkedExportSet = + chunkedExports.length > 0 ? new Set(chunkedExports) : undefined; + const reexports = exportNames + .filter(exp => { + if (chunkedExportSet?.has(exp)) { + return false; + } + return ( + CLIENT_ROUTE_EXPORTS_SET.has(exp) || + (isServer && SERVER_ONLY_ROUTE_EXPORTS_SET.has(exp)) + ); + }) + .sort(); + const target = `${resourcePath}?react-router-route`; + return `export { ${reexports.join(', ')} } from ${JSON.stringify(target)};`; +}; + +export const createRouteClientEntryArtifact = async ({ + code, + resourcePath, + environmentName, + isBuild, + routeChunkCache, + routeChunkConfig, +}: RouteClientEntryArtifactOptions): Promise => { + const isServer = environmentName === 'node'; + const mightHaveRouteChunks = + !isServer && + isBuild && + shouldAnalyzeRouteChunks(routeChunkConfig, resourcePath, code); + const routeChunkInfo = mightHaveRouteChunks + ? await detectRouteChunksIfEnabled( + routeChunkCache, + routeChunkConfig, + resourcePath, + code + ) + : null; + const exportNames = + routeChunkInfo?.exportNames ?? (await getExportNames(code)); + const chunkedExports = routeChunkInfo?.chunkedExports ?? []; + return { + code: buildRouteClientEntryCode({ + exportNames, + chunkedExports, + isServer, + resourcePath, + }), + }; +}; + +export const createRouteChunkArtifact = async ({ + code, + resource, + resourcePath, + isBuild, + routeChunkCache, + routeChunkConfig, +}: RouteChunkArtifactOptions): Promise => { + const splitRouteModules = routeChunkConfig.splitRouteModules; + if (!isBuild || !splitRouteModules) { + return { + code: emptyRouteChunkSnippet(), + map: null, + }; + } + + const chunkName = getRouteChunkNameFromModuleId(resource); + if (!chunkName) { + throw new Error(`Invalid route chunk name in "${resource}"`); + } + if (chunkName !== 'main' && !code.includes(chunkName)) { + return { + code: emptyRouteChunkSnippet(), + map: null, + }; + } + + const chunk = await getRouteChunkIfEnabled( + routeChunkCache, + routeChunkConfig, + resourcePath, + chunkName, + code + ); + + if (splitRouteModules === 'enforce' && chunkName === 'main' && chunk) { + const exportNames = await getExportNames(chunk); + validateRouteChunks({ + config: routeChunkConfig, + id: resourcePath, + valid: buildEnforceChunkValidity(exportNames), + }); + } + + return { + code: chunk ?? emptyRouteChunkSnippet(), + map: null, + }; +}; diff --git a/src/route-ast.ts b/src/route-ast.ts new file mode 100644 index 0000000..6d57b8c --- /dev/null +++ b/src/route-ast.ts @@ -0,0 +1,163 @@ +import type { ParseResult } from 'yuku-parser'; + +export type AnyNode = Record; + +export type ProgramNode = AnyNode & { + body: AnyNode[]; +}; + +export const getProgram = (ast: ParseResult | AnyNode): ProgramNode => + ((ast as ParseResult).program ?? ast) as ProgramNode; + +export const getPatternIdentifierNames = ( + pattern: AnyNode | null | undefined, + names: Set = new Set() +): Set => { + if (!pattern) { + return names; + } + if (pattern.type === 'Identifier') { + names.add(pattern.name); + return names; + } + if (pattern.type === 'RestElement') { + return getPatternIdentifierNames(pattern.argument, names); + } + if (pattern.type === 'AssignmentPattern') { + return getPatternIdentifierNames(pattern.left, names); + } + if (pattern.type === 'ArrayPattern') { + for (const element of pattern.elements ?? []) { + getPatternIdentifierNames(element, names); + } + return names; + } + if (pattern.type === 'ObjectPattern') { + for (const property of pattern.properties ?? []) { + if (property.type === 'RestElement') { + getPatternIdentifierNames(property.argument, names); + } else { + getPatternIdentifierNames(property.value, names); + } + } + } + return names; +}; + +export const getIdentifierNamesFromPattern = ( + pattern: AnyNode | null | undefined +): string[] => Array.from(getPatternIdentifierNames(pattern)); + +export const patternIncludesName = ( + pattern: AnyNode | null | undefined, + name: string +): boolean => getPatternIdentifierNames(pattern).has(name); + +export const getExportedName = ( + node: AnyNode | null | undefined +): string | null => { + const exported = node?.exported ?? node; + if (!exported) { + return null; + } + if (exported.type === 'Identifier') { + return exported.name; + } + if (exported.type === 'Literal' || exported.type === 'StringLiteral') { + return String(exported.value); + } + return null; +}; + +export const identifier = (name: string): AnyNode => ({ + type: 'Identifier', + start: 0, + end: 0, + name, + decorators: [], + optional: false, + typeAnnotation: null, +}); + +export const literal = (value: string): AnyNode => ({ + type: 'Literal', + start: 0, + end: 0, + value, + raw: JSON.stringify(value), +}); + +export const callExpression = (callee: AnyNode, args: AnyNode[]): AnyNode => ({ + type: 'CallExpression', + start: 0, + end: 0, + callee, + arguments: args, + optional: false, +}); + +export const importDeclaration = ( + specifiers: Array<{ local: string; imported: string }>, + source: string +): AnyNode => ({ + type: 'ImportDeclaration', + start: 0, + end: 0, + specifiers: specifiers.map(specifier => ({ + type: 'ImportSpecifier', + start: 0, + end: 0, + imported: identifier(specifier.imported), + local: identifier(specifier.local), + importKind: 'value', + })), + source: literal(source), + attributes: [], + phase: null, + importKind: 'value', +}); + +export const exportSpecifier = (local: string, exported: string): AnyNode => ({ + type: 'ExportSpecifier', + start: 0, + end: 0, + local: identifier(local), + exported: identifier(exported), + exportKind: 'value', +}); + +export const exportNamedDeclaration = (specifiers: AnyNode[]): AnyNode => ({ + type: 'ExportNamedDeclaration', + start: 0, + end: 0, + declaration: null, + specifiers, + source: null, + attributes: [], + exportKind: 'value', +}); + +export const variableDeclaration = (name: string, init: AnyNode): AnyNode => ({ + type: 'VariableDeclaration', + start: 0, + end: 0, + kind: 'const', + declare: false, + declarations: [ + { + type: 'VariableDeclarator', + start: 0, + end: 0, + id: identifier(name), + init, + definite: false, + }, + ], +}); + +export const removeFromArray = (array: T[], value: T): void => { + const index = array.indexOf(value); + if (index >= 0) { + array.splice(index, 1); + } +}; diff --git a/src/route-chunks.ts b/src/route-chunks.ts index 2339729..ce0be88 100644 --- a/src/route-chunks.ts +++ b/src/route-chunks.ts @@ -1,7 +1,14 @@ -import type { NodePath } from './babel.js'; -import { generate, parse, t, traverse } from './babel.js'; +import { + Analyzer, + type Module, + type Symbol as YukuSymbol, +} from 'yuku-analyzer'; +import { print } from 'yuku-codegen'; +import { walk } from 'yuku-parser'; import { normalize, relative, resolve } from 'pathe'; +type AnyNode = Record; + export type RouteChunkExportName = | 'clientAction' | 'clientLoader' @@ -16,7 +23,7 @@ export type RouteChunkConfig = { rootRouteFile: string; }; -export type RouteChunkCacheEntry = { +type RouteChunkCacheEntry = { value: T; version: string; }; @@ -24,6 +31,7 @@ export type RouteChunkCacheEntry = { export type RouteChunkCache = Map>; export type RouteChunkInfo = { + exportNames: string[]; hasRouteChunks: boolean; hasRouteChunkByExportName: Record; chunkedExports: RouteChunkExportName[]; @@ -41,6 +49,18 @@ export const routeChunkNames: RouteChunkName[] = [ ...routeChunkExportNames, ]; +export const mightContainRouteChunkExportName = (source: string): boolean => + routeChunkExportNames.some(exportName => source.includes(exportName)); + +const createRouteChunkExportMap = ( + getValue: (exportName: RouteChunkExportName) => boolean +): Record => + Object.fromEntries( + routeChunkExportNames.map(exportName => [exportName, getValue(exportName)]) + ) as Record; + +export const emptyRouteChunkSnippet = (): string => 'export {};'; + const routeChunkQueryStringPrefix = '?route-chunk='; const routeChunkQueryStrings: Record = { @@ -68,14 +88,11 @@ const invariant: (value: unknown, message: string) => asserts value = ( }; const getOrSetFromCache = ( - cache: RouteChunkCache | undefined, + cache: RouteChunkCache, key: string, version: string, getValue: () => T ): T => { - if (!cache) { - return getValue(); - } const entry = cache.get(key) as RouteChunkCacheEntry | undefined; if (entry?.version === version) { return entry.value; @@ -85,80 +102,101 @@ const getOrSetFromCache = ( return value; }; -const codeToAst = ( +const hasCachedValue = ( + cache: RouteChunkCache, + key: string, + version: string +): boolean => cache.get(key)?.version === version; + +type AnalyzedModule = { + module: Module; + // Dependency sets use these node identities. Consumers must shallow-copy + // any node whose children they narrow instead of mutating this cached AST. + program: AnyNode; +}; + +const analyzeCode = ( code: string, - cache: RouteChunkCache | undefined, + cache: RouteChunkCache, cacheKey: string -) => { - return structuredClone( - getOrSetFromCache(cache, `${cacheKey}::codeToAst`, code, () => - parse(code, { sourceType: 'module' }) - ) - ); +): AnalyzedModule => { + return getOrSetFromCache(cache, `${cacheKey}::analyzeCode`, code, () => { + const analyzer = new Analyzer(); + const module = analyzer.addFile(cacheKey, code, { + lang: 'tsx', + sourceType: 'module', + attachComments: true, + }); + const errors = module.diagnostics.filter( + diagnostic => diagnostic.severity === 'error' + ); + if (errors.length > 0) { + throw new Error(errors.map(error => error.message).join('\n')); + } + return { module, program: module.ast as AnyNode }; + }); }; -const assertNodePath: ( - path: NodePath | NodePath[] | null | undefined -) => asserts path is NodePath = path => { - invariant( - path && !Array.isArray(path), - `Expected a Path, but got ${Array.isArray(path) ? 'an array' : path}` - ); +type ExportDependencies = { + topLevelStatements: Set; + topLevelNonModuleStatements: Set; + importedIdentifierNames: Set; + exportedVariableDeclarators: Set; }; -const isNodePathWithNode = (path: unknown): path is NodePath => { - if (!path || typeof path !== 'object' || Array.isArray(path)) { - return false; - } - if (!('node' in path)) { - return false; +const getTopLevelStatementForNode = ( + module: Module, + node: AnyNode +): AnyNode => { + let current: AnyNode = node; + let parent = module.parentOf(current as never) as AnyNode | null; + while (parent && parent.type !== 'Program') { + current = parent; + parent = module.parentOf(current as never) as AnyNode | null; } - return Boolean((path as { node?: unknown }).node); -}; - -const assertNodePathIsStatement: ( - path: NodePath | NodePath[] | null | undefined -) => asserts path is NodePath = path => { - invariant( - path && !Array.isArray(path) && t.isStatement(path.node), - `Expected a Statement path, but got ${ - Array.isArray(path) ? 'an array' : path?.node?.type - }` - ); + invariant(parent?.type === 'Program', 'Expected node to be within Program'); + return current; }; -const assertNodePathIsVariableDeclarator: ( - path: NodePath | NodePath[] | null | undefined -) => asserts path is NodePath = path => { - invariant( - path && !Array.isArray(path) && t.isVariableDeclarator(path.node), - `Expected a VariableDeclarator path, but got ${ - Array.isArray(path) ? 'an array' : path?.node?.type - }` - ); +const getVariableDeclaratorForNode = ( + module: Module, + node: AnyNode +): AnyNode | null => { + let current: AnyNode | null = node; + while (current) { + if (current.type === 'VariableDeclarator') { + return current; + } + current = module.parentOf(current as never) as AnyNode | null; + } + return null; }; -const assertNodePathIsPattern: ( - path: NodePath | NodePath[] | null | undefined -) => asserts path is NodePath = path => { - invariant( - path && !Array.isArray(path) && t.isPattern(path.node), - `Expected a Pattern path, but got ${ - Array.isArray(path) ? 'an array' : path?.node?.type - }` - ); +const getExportedName = (exported: AnyNode): string => { + if (exported.type === 'Identifier') { + return exported.name; + } + return String(exported.value); }; -type ExportDependencies = { - topLevelStatements: Set; - topLevelNonModuleStatements: Set; - importedIdentifierNames: Set; - exportedVariableDeclarators: Set; +const setsIntersect = (set1: Set, set2: Set) => { + let smallerSet = set1; + let largerSet = set2; + if (set1.size > set2.size) { + smallerSet = set2; + largerSet = set1; + } + for (const element of smallerSet) { + if (largerSet.has(element)) { + return true; + } + } + return false; }; const getExportDependencies = ( code: string, - cache: RouteChunkCache | undefined, + cache: RouteChunkCache, cacheKey: string ): Map => { return getOrSetFromCache( @@ -166,368 +204,235 @@ const getExportDependencies = ( `${cacheKey}::getExportDependencies`, code, () => { + const { module } = analyzeCode(code, cache, cacheKey); const exportDependencies = new Map(); - const ast = codeToAst(code, cache, cacheKey); - - function handleExport( - exportName: string, - exportPath: NodePath, - identifiersPath: NodePath = exportPath - ) { - const identifiers = getDependentIdentifiersForPath(identifiersPath); - const topLevelStatements = new Set([ - exportPath.node as t.Statement, - ...getTopLevelStatementsForPaths(identifiers), - ]); - const topLevelNonModuleStatements = new Set( - Array.from(topLevelStatements).filter( - statement => - !t.isImportDeclaration(statement) && - !t.isExportDeclaration(statement) - ) - ); - const importedIdentifierNames = new Set(); - for (const identifier of identifiers) { - if ( - t.isIdentifier(identifier.node) && - identifier.parentPath?.parentPath?.isImportDeclaration() - ) { - importedIdentifierNames.add(identifier.node.name); - } + const topLevelStatementCache = new Map(); + const variableDeclaratorCache = new Map(); + const getCachedTopLevelStatementForNode = (node: AnyNode): AnyNode => { + const cached = topLevelStatementCache.get(node); + if (cached) { + return cached; } - const exportedVariableDeclarators = new Set(); - for (const identifier of identifiers) { - if (identifier.parentPath?.isVariableDeclarator()) { - const parentPath = identifier.parentPath; - if (parentPath.parentPath?.parentPath?.isExportNamedDeclaration()) { - exportedVariableDeclarators.add( - parentPath.node as t.VariableDeclarator - ); - continue; - } - } - const isWithinExportDestructuring = Boolean( - identifier.findParent(path => - Boolean( - path.isPattern() && - path.parentPath?.isVariableDeclarator() && - path.parentPath.parentPath?.parentPath?.isExportNamedDeclaration() - ) - ) - ); - if (isWithinExportDestructuring) { - let currentPath: NodePath | null = identifier; - while (currentPath) { - if ( - currentPath.parentPath?.isVariableDeclarator() && - currentPath.parentKey === 'id' - ) { - exportedVariableDeclarators.add( - currentPath.parentPath.node as t.VariableDeclarator - ); - break; - } - currentPath = currentPath.parentPath; - } - } + const statement = getTopLevelStatementForNode(module, node); + topLevelStatementCache.set(node, statement); + return statement; + }; + + const getCachedVariableDeclaratorForNode = ( + node: AnyNode + ): AnyNode | null => { + if (variableDeclaratorCache.has(node)) { + return variableDeclaratorCache.get(node) ?? null; } - exportDependencies.set(exportName, { - topLevelStatements, - topLevelNonModuleStatements, - importedIdentifierNames, - exportedVariableDeclarators, - }); - } + const declarator = getVariableDeclaratorForNode(module, node); + variableDeclaratorCache.set(node, declarator); + return declarator; + }; + + const addCachedTopLevelStatement = ( + dependencies: ExportDependencies, + node: AnyNode + ) => { + const statement = getCachedTopLevelStatementForNode(node); + dependencies.topLevelStatements.add(statement); + if ( + statement.type !== 'ImportDeclaration' && + !statement.type.startsWith('Export') + ) { + dependencies.topLevelNonModuleStatements.add(statement); + } + return statement; + }; - traverse(ast, { - ExportDeclaration(exportPath) { - const { node } = exportPath; - if (t.isExportAllDeclaration(node)) { - return; - } - if (t.isExportDefaultDeclaration(node)) { - handleExport('default', exportPath); + const handleExport = ( + exportName: string, + exportNode: AnyNode, + localSymbol: YukuSymbol | null + ) => { + const dependencies: ExportDependencies = { + topLevelStatements: new Set(), + topLevelNonModuleStatements: new Set(), + importedIdentifierNames: new Set(), + exportedVariableDeclarators: new Set(), + }; + const visitedSymbols = new Set(); + const scannedNodes = new Set(); + + const scanNode = (node: AnyNode) => { + if (scannedNodes.has(node)) { return; } - const { declaration } = node; - if (t.isVariableDeclaration(declaration)) { - const { declarations } = declaration; - for (let i = 0; i < declarations.length; i++) { - const declarator = declarations[i]; - if (t.isIdentifier(declarator.id)) { - const declaratorPath = exportPath.get( - `declaration.declarations.${i}` - ); - assertNodePathIsVariableDeclarator(declaratorPath); - handleExport(declarator.id.name, exportPath, declaratorPath); - continue; + scannedNodes.add(node); + walk(node as any, { + Identifier(node: AnyNode) { + const reference = module.referenceOf(node as never); + if (reference?.symbol) { + visitSymbol(reference.symbol); } - if (t.isPattern(declarator.id)) { - const exportedPatternPath = exportPath.get( - `declaration.declarations.${i}.id` - ); - assertNodePathIsPattern(exportedPatternPath); - const identifiers = - getIdentifiersForPatternPath(exportedPatternPath); - for (const identifier of identifiers) { - if (!t.isIdentifier(identifier.node)) { - continue; - } - handleExport(identifier.node.name, exportPath, identifier); - } - } - } + }, + }); + }; + + const visitSymbol = (symbol: YukuSymbol) => { + if (visitedSymbols.has(symbol)) { return; } - if ( - t.isFunctionDeclaration(declaration) || - t.isClassDeclaration(declaration) - ) { - invariant( - declaration.id, - 'Expected exported function or class declaration to have a name when not the default export' + visitedSymbols.add(symbol); + + for (const declaration of symbol.declarations as AnyNode[]) { + const statement = addCachedTopLevelStatement( + dependencies, + declaration ); - handleExport(declaration.id.name, exportPath); - return; - } - if (t.isExportNamedDeclaration(node)) { - for (const specifier of node.specifiers) { - if (t.isIdentifier(specifier.exported)) { - const name = specifier.exported.name; - const specifierPath = exportPath - .get('specifiers') - .find(path => path.node === specifier); - invariant( - specifierPath, - `Expected to find specifier path for ${name}` - ); - handleExport(name, exportPath, specifierPath); - } + if (statement.type === 'ImportDeclaration') { + dependencies.importedIdentifierNames.add(symbol.name); } - return; + const declarator = getCachedVariableDeclaratorForNode(declaration); + if ( + declarator && + getCachedTopLevelStatementForNode(declarator).type === + 'ExportNamedDeclaration' + ) { + dependencies.exportedVariableDeclarators.add(declarator); + } + scanNode(declarator ?? statement); } - throw new Error('Unknown export node type'); - }, - }); - return exportDependencies; - } - ); -}; + for (const reference of symbol.references as any[]) { + const statement = addCachedTopLevelStatement( + dependencies, + reference.node + ); + const declarator = getCachedVariableDeclaratorForNode( + reference.node + ); + scanNode(declarator ?? statement); + } + }; -const getDependentIdentifiersForPath = ( - path: NodePath, - state?: { visited: Set; identifiers: Set } -): Set => { - const { visited, identifiers } = state ?? { - visited: new Set(), - identifiers: new Set(), - }; - if (visited.has(path)) { - return identifiers; - } - visited.add(path); - path.traverse({ - Identifier(pathInner) { - if (identifiers.has(pathInner)) { - return; - } - identifiers.add(pathInner); - const binding = pathInner.scope.getBinding(pathInner.node.name); - if (!binding) { - return; - } - getDependentIdentifiersForPath(binding.path, { visited, identifiers }); - for (const reference of binding.referencePaths) { - if (reference.isExportNamedDeclaration()) { + addCachedTopLevelStatement(dependencies, exportNode); + + if (localSymbol) { + visitSymbol(localSymbol); + } else { + const statement = getCachedTopLevelStatementForNode(exportNode); + scanNode(statement); + } + + exportDependencies.set(exportName, dependencies); + }; + + for (const exp of module.exports as any[]) { + if (exp.typeOnly || exp.isStar || exp.isExportEquals) { continue; } - getDependentIdentifiersForPath(reference, { visited, identifiers }); + handleExport(exp.name, exp.node as AnyNode, exp.local ?? null); } - for (const constantViolation of binding.constantViolations) { - getDependentIdentifiersForPath(constantViolation, { - visited, - identifiers, - }); - } - }, - }); - const topLevelStatement = getTopLevelStatementPathForPath(path); - const withinImportStatement = topLevelStatement.isImportDeclaration(); - const withinExportStatement = topLevelStatement.isExportDeclaration(); - if (!withinImportStatement && !withinExportStatement) { - getDependentIdentifiersForPath(topLevelStatement, { visited, identifiers }); - } - if ( - withinExportStatement && - path.isIdentifier() && - (t.isPattern(path.parentPath.node) || - t.isPattern(path.parentPath.parentPath?.node)) - ) { - const variableDeclarator = path.findParent(p => p.isVariableDeclarator()); - if (variableDeclarator) { - assertNodePath(variableDeclarator); - getDependentIdentifiersForPath(variableDeclarator, { - visited, - identifiers, - }); - } - } - return identifiers; -}; -const getTopLevelStatementPathForPath = (path: NodePath) => { - const ancestry = path.getAncestry(); - const topLevelStatement = ancestry[ancestry.length - 2]; - assertNodePathIsStatement(topLevelStatement); - return topLevelStatement; -}; - -const getTopLevelStatementsForPaths = (paths: Set) => { - const topLevelStatements = new Set(); - for (const path of paths) { - const topLevelStatement = getTopLevelStatementPathForPath(path); - topLevelStatements.add(topLevelStatement.node as t.Statement); - } - return topLevelStatements; + return exportDependencies; + } + ); }; -const getIdentifiersForPatternPath = ( - patternPath: NodePath, - identifiers: Set = new Set() +const isExportChunkable = ( + exportName: string, + exportDependencies: Map ) => { - function walk(currentPath: NodePath) { - if (currentPath.isIdentifier()) { - identifiers.add(currentPath); - return; + const dependencies = exportDependencies.get(exportName); + if (!dependencies) { + return false; + } + for (const [currentExportName, currentDependencies] of exportDependencies) { + if (currentExportName === exportName) { + continue; } - if (currentPath.isObjectPattern()) { - const { properties } = currentPath.node; - for (let i = 0; i < properties.length; i++) { - const property = properties[i]; - if (t.isObjectProperty(property)) { - const valuePath = currentPath.get(`properties.${i}.value`); - if (isNodePathWithNode(valuePath)) { - walk(valuePath); - } - } else if (t.isRestElement(property)) { - const argumentPath = currentPath.get(`properties.${i}.argument`); - if (isNodePathWithNode(argumentPath)) { - walk(argumentPath); - } - } - } - } else if (currentPath.isArrayPattern()) { - const { elements } = currentPath.node; - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - if (element) { - const elementPath = currentPath.get(`elements.${i}`); - if (isNodePathWithNode(elementPath)) { - walk(elementPath); - } - } - } - } else if (currentPath.isRestElement()) { - const argumentPath = currentPath.get('argument'); - if (isNodePathWithNode(argumentPath)) { - walk(argumentPath); - } + if ( + setsIntersect( + currentDependencies.topLevelNonModuleStatements, + dependencies.topLevelNonModuleStatements + ) + ) { + return false; } } - walk(patternPath); - return identifiers; -}; - -const getExportedName = (exported: t.Identifier | t.StringLiteral) => { - return t.isIdentifier(exported) ? exported.name : exported.value; -}; - -const setsIntersect = (set1: Set, set2: Set) => { - let smallerSet = set1; - let largerSet = set2; - if (set1.size > set2.size) { - smallerSet = set2; - largerSet = set1; + if (dependencies.exportedVariableDeclarators.size > 1) { + return false; } - for (const element of smallerSet) { - if (largerSet.has(element)) { - return true; + if (dependencies.exportedVariableDeclarators.size > 0) { + for (const [currentExportName, currentDependencies] of exportDependencies) { + if (currentExportName === exportName) { + continue; + } + if ( + setsIntersect( + currentDependencies.exportedVariableDeclarators, + dependencies.exportedVariableDeclarators + ) + ) { + return false; + } } } - return false; + return true; }; +const getChunkableExportMap = ( + code: string, + cache: RouteChunkCache, + cacheKey: string +): Record => + getOrSetFromCache(cache, `${cacheKey}::getChunkableExportMap`, code, () => { + const exportDependencies = getExportDependencies(code, cache, cacheKey); + return createRouteChunkExportMap(exportName => + isExportChunkable(exportName, exportDependencies) + ); + }); + const hasChunkableExport = ( code: string, exportName: string, - cache: RouteChunkCache | undefined, + cache: RouteChunkCache, cacheKey: string +) => + (routeChunkExportNames as string[]).includes(exportName) + ? getChunkableExportMap(code, cache, cacheKey)[ + exportName as RouteChunkExportName + ] + : false; + +const generateCode = (program: AnyNode): string | undefined => { + if (program.body.length === 0) { + return undefined; + } + const result = print(program as any, { comments: true }); + if (result.errors.length > 0) { + throw new Error(result.errors.map(error => error.message).join('\n')); + } + return result.code; +}; + +const filterImportSpecifiers = ( + node: AnyNode, + shouldKeep: (importedName: string) => boolean ) => { - return getOrSetFromCache( - cache, - `${cacheKey}::hasChunkableExport::${exportName}`, - code, - () => { - const exportDependencies = getExportDependencies(code, cache, cacheKey); - const dependencies = exportDependencies.get(exportName); - if (!dependencies) { - return false; - } - for (const [ - currentExportName, - currentDependencies, - ] of exportDependencies) { - if (currentExportName === exportName) { - continue; - } - if ( - setsIntersect( - currentDependencies.topLevelNonModuleStatements, - dependencies.topLevelNonModuleStatements - ) - ) { - return false; - } - } - if (dependencies.exportedVariableDeclarators.size > 1) { - return false; - } - if (dependencies.exportedVariableDeclarators.size > 0) { - for (const [ - currentExportName, - currentDependencies, - ] of exportDependencies) { - if (currentExportName === exportName) { - continue; - } - if ( - setsIntersect( - currentDependencies.exportedVariableDeclarators, - dependencies.exportedVariableDeclarators - ) - ) { - return false; - } - } - } - return true; - } + if (node.specifiers.length === 0) { + return node; + } + const specifiers = node.specifiers.filter((specifier: AnyNode) => + shouldKeep(specifier.local.name) ); + return specifiers.length > 0 ? { ...node, specifiers } : null; }; const getChunkedExport = ( code: string, exportName: string, - generateOptions: Record = {}, - cache: RouteChunkCache | undefined, + cache: RouteChunkCache, cacheKey: string ): string | undefined => { return getOrSetFromCache( cache, - `${cacheKey}::getChunkedExport::${exportName}::${JSON.stringify( - generateOptions - )}`, + `${cacheKey}::getChunkedExport::${exportName}`, code, () => { if (!hasChunkableExport(code, exportName, cache, cacheKey)) { @@ -537,104 +442,94 @@ const getChunkedExport = ( const dependencies = exportDependencies.get(exportName); invariant(dependencies, 'Expected export to have dependencies'); - const topLevelStatementsArray = Array.from( - dependencies.topLevelStatements - ); - const exportedVariableDeclaratorsArray = Array.from( - dependencies.exportedVariableDeclarators - ); - - const ast = codeToAst(code, cache, cacheKey); - ast.program.body = ast.program.body - .filter(node => - topLevelStatementsArray.some(statement => - t.isNodesEquivalent(node, statement) - ) - ) - .map(node => { - if (!t.isImportDeclaration(node)) { + const program = analyzeCode(code, cache, cacheKey).program; + const body = program.body + .filter((node: AnyNode) => dependencies.topLevelStatements.has(node)) + .map((node: AnyNode) => { + if (node.type !== 'ImportDeclaration') { return node; } if (dependencies.importedIdentifierNames.size === 0) { return null; } - node.specifiers = node.specifiers.filter(specifier => - dependencies.importedIdentifierNames.has(specifier.local.name) - ); - invariant( - node.specifiers.length > 0, - 'Expected import statement to have used specifiers' + return filterImportSpecifiers(node, importedName => + dependencies.importedIdentifierNames.has(importedName) ); - return node; }) - .map(node => { - if (!t.isExportDeclaration(node)) { + .map((node: AnyNode | null) => { + if (!node || !node.type.startsWith('Export')) { return node; } - if (t.isExportAllDeclaration(node)) { + if (node.type === 'ExportAllDeclaration') { return null; } - if (t.isExportDefaultDeclaration(node)) { + if (node.type === 'ExportDefaultDeclaration') { return exportName === 'default' ? node : null; } const { declaration } = node; - if (t.isVariableDeclaration(declaration)) { - declaration.declarations = declaration.declarations.filter( - declarationNode => - exportedVariableDeclaratorsArray.some(declarator => - t.isNodesEquivalent(declarationNode, declarator) - ) + if (declaration?.type === 'VariableDeclaration') { + const declarations = declaration.declarations.filter( + (declarationNode: AnyNode) => + dependencies.exportedVariableDeclarators.has(declarationNode) ); - if (declaration.declarations.length === 0) { - return null; - } - return node; + return declarations.length > 0 + ? { + ...node, + declaration: { ...declaration, declarations }, + } + : null; } if ( - t.isFunctionDeclaration(node.declaration) || - t.isClassDeclaration(node.declaration) + declaration?.type === 'FunctionDeclaration' || + declaration?.type === 'ClassDeclaration' ) { - return node.declaration.id?.name === exportName ? node : null; + return declaration.id?.name === exportName ? node : null; } - if (t.isExportNamedDeclaration(node)) { - if (node.specifiers.length === 0) { - return null; - } - node.specifiers = node.specifiers.filter( - specifier => getExportedName(specifier.exported) === exportName + if (node.type === 'ExportNamedDeclaration') { + const specifiers = node.specifiers.filter( + (specifier: AnyNode) => + getExportedName(specifier.exported) === exportName ); - if (node.specifiers.length === 0) { - return null; - } - return node; + return specifiers.length > 0 ? { ...node, specifiers } : null; } throw new Error('Unknown export node type'); }) - .filter(Boolean) as t.Statement[]; + .filter(Boolean) as AnyNode[]; - return generate(ast, generateOptions).code; + return generateCode({ ...program, body }); } ); }; +const getChunkedExportCacheKey = ( + cacheKey: string, + exportName: RouteChunkExportName +) => `${cacheKey}::getChunkedExport::${exportName}`; + +const hasCachedChunkedExport = ( + code: string, + exportName: RouteChunkExportName, + cache: RouteChunkCache, + cacheKey: string +): boolean => + hasCachedValue(cache, getChunkedExportCacheKey(cacheKey, exportName), code); + const omitChunkedExports = ( code: string, exportNames: string[], - generateOptions: Record = {}, - cache: RouteChunkCache | undefined, + cache: RouteChunkCache, cacheKey: string ): string | undefined => { return getOrSetFromCache( cache, - `${cacheKey}::omitChunkedExports::${exportNames.join(',')}::${JSON.stringify( - generateOptions - )}`, + `${cacheKey}::omitChunkedExports::${exportNames.join(',')}`, code, () => { - const isChunkable = (exportName: string) => - hasChunkableExport(code, exportName, cache, cacheKey); + const chunkableExportMap = getChunkableExportMap(code, cache, cacheKey); + const exportNameSet = new Set(exportNames); const isOmitted = (exportName: string) => - exportNames.includes(exportName) && isChunkable(exportName); + exportNameSet.has(exportName) && + Boolean(chunkableExportMap[exportName as RouteChunkExportName]); const isRetained = (exportName: string) => !isOmitted(exportName); const exportDependencies = getExportDependencies(code, cache, cacheKey); @@ -642,9 +537,10 @@ const omitChunkedExports = ( const omittedExportNames = allExportNames.filter(isOmitted); const retainedExportNames = allExportNames.filter(isRetained); - const omittedStatements = new Set(); - const omittedExportedVariableDeclarators = - new Set(); + const omittedStatements = new Set(); + const omittedExportedVariableDeclarators = new Set(); + const retainedImportedIdentifierNames = new Set(); + const omittedImportedIdentifierNames = new Set(); for (const omittedExportName of omittedExportNames) { const dependencies = exportDependencies.get(omittedExportName); @@ -658,122 +554,118 @@ const omitChunkedExports = ( for (const declarator of dependencies.exportedVariableDeclarators) { omittedExportedVariableDeclarators.add(declarator); } + for (const importedName of dependencies.importedIdentifierNames) { + omittedImportedIdentifierNames.add(importedName); + } } - const ast = codeToAst(code, cache, cacheKey); - const omittedStatementsArray = Array.from(omittedStatements); - const omittedExportedVariableDeclaratorsArray = Array.from( - omittedExportedVariableDeclarators - ); - ast.program.body = ast.program.body - .filter(node => - omittedStatementsArray.every( - statement => !t.isNodesEquivalent(node, statement) - ) - ) - .map(node => { - if (!t.isImportDeclaration(node)) { - return node; - } - if (node.specifiers.length === 0) { + for (const retainedExportName of retainedExportNames) { + const dependencies = exportDependencies.get(retainedExportName); + if (!dependencies) { + continue; + } + for (const importedName of dependencies.importedIdentifierNames) { + retainedImportedIdentifierNames.add(importedName); + } + } + + const program = analyzeCode(code, cache, cacheKey).program; + const body = program.body + .filter((node: AnyNode) => !omittedStatements.has(node)) + .map((node: AnyNode) => { + if (node.type !== 'ImportDeclaration') { return node; } - node.specifiers = node.specifiers.filter(specifier => { - const importedName = specifier.local.name; - for (const retainedExportName of retainedExportNames) { - const dependencies = exportDependencies.get(retainedExportName); - if (dependencies?.importedIdentifierNames?.has(importedName)) { - return true; - } - } - for (const omittedExportName of omittedExportNames) { - const dependencies = exportDependencies.get(omittedExportName); - if (dependencies?.importedIdentifierNames?.has(importedName)) { - return false; - } - } + return filterImportSpecifiers(node, importedName => { + if (retainedImportedIdentifierNames.has(importedName)) return true; + if (omittedImportedIdentifierNames.has(importedName)) return false; return true; }); - if (node.specifiers.length === 0) { - return null; - } - return node; }) - .map(node => { - if (!t.isExportDeclaration(node)) { + .map((node: AnyNode | null) => { + if (!node || !node.type.startsWith('Export')) { return node; } - if (t.isExportAllDeclaration(node)) { + if (node.type === 'ExportAllDeclaration') { return node; } - if (t.isExportDefaultDeclaration(node)) { + if (node.type === 'ExportDefaultDeclaration') { return isOmitted('default') ? null : node; } - if (t.isVariableDeclaration(node.declaration)) { - node.declaration.declarations = - node.declaration.declarations.filter(declarationNode => - omittedExportedVariableDeclaratorsArray.every( - declarator => - !t.isNodesEquivalent(declarationNode, declarator) - ) - ); - if (node.declaration.declarations.length === 0) { - return null; - } - return node; + if (node.declaration?.type === 'VariableDeclaration') { + const declarations = node.declaration.declarations.filter( + (declarationNode: AnyNode) => + !omittedExportedVariableDeclarators.has(declarationNode) + ); + return declarations.length > 0 + ? { + ...node, + declaration: { ...node.declaration, declarations }, + } + : null; } if ( - t.isFunctionDeclaration(node.declaration) || - t.isClassDeclaration(node.declaration) + node.declaration?.type === 'FunctionDeclaration' || + node.declaration?.type === 'ClassDeclaration' ) { - const declarationId = node.declaration.id; - invariant( - declarationId, - 'Expected exported function or class declaration to have a name when not the default export' - ); - return isOmitted(declarationId.name) ? null : node; + return isOmitted(node.declaration.id.name) ? null : node; } - if (t.isExportNamedDeclaration(node)) { - if (node.specifiers.length === 0) { - return node; - } - node.specifiers = node.specifiers.filter(specifier => { + if (node.type === 'ExportNamedDeclaration') { + const specifiers = node.specifiers.filter((specifier: AnyNode) => { const exportedName = getExportedName(specifier.exported); return !isOmitted(exportedName); }); - if (node.specifiers.length === 0) { - return null; - } - return node; + return specifiers.length > 0 || node.declaration + ? { ...node, specifiers } + : null; } throw new Error('Unknown node type'); }) - .filter(Boolean) as t.Statement[]; + .filter(Boolean) as AnyNode[]; - if (ast.program.body.length === 0) { - return undefined; - } - return generate(ast, generateOptions).code; + return generateCode({ ...program, body }); } ); }; +const precomputeChunkedExports = ( + code: string, + cache: RouteChunkCache, + cacheKey: string +) => { + const chunkableExportMap = getChunkableExportMap(code, cache, cacheKey); + for (const exportName of routeChunkExportNames) { + if (!chunkableExportMap[exportName]) { + continue; + } + if (!hasCachedChunkedExport(code, exportName, cache, cacheKey)) { + getChunkedExport(code, exportName, cache, cacheKey); + } + } +}; + export const detectRouteChunks = ( code: string, cache: RouteChunkCache | undefined, cacheKey: string ): RouteChunkInfo => { - const hasRouteChunkByExportName = Object.fromEntries( - routeChunkExportNames.map(exportName => [ - exportName, - hasChunkableExport(code, exportName, cache, cacheKey), - ]) - ) as Record; + const analysisCache = cache ?? new Map(); + const exportDependencies = getExportDependencies( + code, + analysisCache, + cacheKey + ); + const hasRouteChunkByExportName = getChunkableExportMap( + code, + analysisCache, + cacheKey + ); const chunkedExports = Object.entries(hasRouteChunkByExportName) .filter(([, isChunked]) => isChunked) .map(([exportName]) => exportName as RouteChunkExportName); const hasRouteChunks = chunkedExports.length > 0; return { + exportNames: Array.from(exportDependencies.keys()), hasRouteChunks, hasRouteChunkByExportName, chunkedExports, @@ -791,10 +683,19 @@ export const getRouteChunkCode: ( cache: RouteChunkCache | undefined, cacheKey: string ) => { + const analysisCache = cache ?? new Map(); if (chunkName === 'main') { - return omitChunkedExports(code, routeChunkExportNames, {}, cache, cacheKey); + return omitChunkedExports( + code, + routeChunkExportNames, + analysisCache, + cacheKey + ); + } + if (!hasCachedChunkedExport(code, chunkName, analysisCache, cacheKey)) { + precomputeChunkedExports(code, analysisCache, cacheKey); } - return getChunkedExport(code, chunkName, {}, cache, cacheKey); + return getChunkedExport(code, chunkName, analysisCache, cacheKey); }; export const getRouteChunkModuleId = ( @@ -803,9 +704,7 @@ export const getRouteChunkModuleId = ( ) => `${filePath}${routeChunkQueryStrings[chunkName]}`; export const isRouteChunkModuleId: (id: string) => boolean = (id: string) => - Object.values(routeChunkQueryStrings).some(queryString => - id.endsWith(queryString) - ); + getRouteChunkNameFromModuleId(id) !== null; const isRouteChunkName = (name: string): name is RouteChunkName => name === 'main' || (routeChunkExportNames as string[]).includes(name); @@ -813,10 +712,16 @@ const isRouteChunkName = (name: string): name is RouteChunkName => export const getRouteChunkNameFromModuleId = ( id: string ): RouteChunkName | null => { - if (!id.includes(routeChunkQueryStringPrefix)) { + const queryIndex = id.indexOf(routeChunkQueryStringPrefix); + if (queryIndex === -1) { return null; } - const chunkName = id.split(routeChunkQueryStringPrefix)[1].split('&')[0]; + const chunkNameStart = queryIndex + routeChunkQueryStringPrefix.length; + const chunkNameEnd = id.indexOf('&', chunkNameStart); + const chunkName = id.slice( + chunkNameStart, + chunkNameEnd === -1 ? undefined : chunkNameEnd + ); if (!isRouteChunkName(chunkName)) { return null; } @@ -832,6 +737,38 @@ const normalizeRelativeFilePath = (file: string, appDirectory: string) => { const isRootRouteModuleId = (config: RouteChunkConfig, id: string) => normalizeRelativeFilePath(id, config.appDirectory) === config.rootRouteFile; +export const shouldAnalyzeRouteChunks = ( + config: RouteChunkConfig, + id: string, + code: string +): boolean => + Boolean(config.splitRouteModules) && + mightContainRouteChunkExportName(code) && + !isRootRouteModuleId(config, id); + +export const createEmptyRouteChunkByExportName = (): Record< + RouteChunkExportName, + boolean +> => createRouteChunkExportMap(() => false); + +export const buildEnforceChunkValidity = ( + exportNames: readonly string[] +): Record => { + const exportNameSet = new Set(exportNames); + return createRouteChunkExportMap( + exportName => !exportNameSet.has(exportName) + ); +}; + +export const buildManifestChunkValidity = ( + exportNames: ReadonlySet, + hasRouteChunkByExportName: Readonly> +): Record => + createRouteChunkExportMap( + exportName => + !exportNames.has(exportName) || hasRouteChunkByExportName[exportName] + ); + export const detectRouteChunksIfEnabled: ( cache: RouteChunkCache | undefined, config: RouteChunkConfig, @@ -844,23 +781,13 @@ export const detectRouteChunksIfEnabled: ( code: string ) => { const noRouteChunks = (): RouteChunkInfo => ({ + exportNames: [], chunkedExports: [] as RouteChunkExportName[], hasRouteChunks: false, - hasRouteChunkByExportName: { - clientAction: false, - clientLoader: false, - clientMiddleware: false, - HydrateFallback: false, - } as Record, + hasRouteChunkByExportName: createEmptyRouteChunkByExportName(), }); - if (!config.splitRouteModules) { - return noRouteChunks(); - } - if (isRootRouteModuleId(config, id)) { - return noRouteChunks(); - } - if (!routeChunkExportNames.some(exportName => code.includes(exportName))) { + if (!shouldAnalyzeRouteChunks(config, id, code)) { return noRouteChunks(); } @@ -884,6 +811,13 @@ export const getRouteChunkIfEnabled: ( if (!config.splitRouteModules) { return null; } + if (chunkName === 'main') { + if (!mightContainRouteChunkExportName(code)) { + return code; + } + } else if (!code.includes(chunkName)) { + return null; + } const cacheKey = normalizeRelativeFilePath(id, config.appDirectory); return getRouteChunkCode(code, chunkName, cache, cacheKey) ?? null; }; diff --git a/src/route-component-transform.ts b/src/route-component-transform.ts new file mode 100644 index 0000000..c1dbea6 --- /dev/null +++ b/src/route-component-transform.ts @@ -0,0 +1,314 @@ +import { + NAMED_COMPONENT_EXPORTS, + NAMED_COMPONENT_EXPORTS_SET, +} from './constants.js'; +import type { ParseResult } from 'yuku-parser'; +import { + callExpression, + exportNamedDeclaration, + exportSpecifier, + getExportedName, + getProgram, + identifier, + importDeclaration, + patternIncludesName, + variableDeclaration, + type AnyNode, +} from './route-ast.js'; + +export function toFunctionExpression(decl: AnyNode): AnyNode { + return { + ...decl, + type: 'FunctionExpression', + declare: undefined, + }; +} + +export function toClassExpression(decl: AnyNode): AnyNode { + return { + ...decl, + type: 'ClassExpression', + declare: undefined, + }; +} + +const getComponentExportName = (exportedName: string): string | null => { + if (exportedName === 'default') { + return 'Component'; + } + return isNamedComponentExport(exportedName) ? exportedName : null; +}; + +const getImportInsertionIndex = (program: AnyNode): number => { + let index = 0; + for (const statement of program.body ?? []) { + if ( + statement.type !== 'ExpressionStatement' || + (statement.directive === undefined && + statement.expression?.type !== 'Literal') + ) { + break; + } + index += 1; + } + return index; +}; + +const declarationIncludesName = ( + declaration: AnyNode, + name: string +): boolean => { + if (declaration.type === 'VariableDeclaration') { + return (declaration.declarations ?? []).some((declarator: AnyNode) => + patternIncludesName(declarator.id, name) + ); + } + if ( + (declaration.type === 'FunctionDeclaration' || + declaration.type === 'ClassDeclaration' || + declaration.type === 'TSEnumDeclaration') && + declaration.id?.name + ) { + return declaration.id.name === name; + } + if (declaration.type === 'ImportDeclaration') { + return (declaration.specifiers ?? []).some( + (specifier: AnyNode) => specifier.local?.name === name + ); + } + return false; +}; + +const hasTopLevelBindingName = (program: AnyNode, name: string): boolean => { + for (const statement of program.body ?? []) { + if (statement.type === 'ImportDeclaration') { + if (declarationIncludesName(statement, name)) { + return true; + } + continue; + } + + if (statement.type === 'ExportDefaultDeclaration') { + if (statement.declaration?.id?.name === name) { + return true; + } + continue; + } + + const declaration = + statement.type === 'ExportNamedDeclaration' + ? statement.declaration + : statement; + if (declaration && declarationIncludesName(declaration, name)) { + return true; + } + } + return false; +}; + +export const transformRoute = (ast: ParseResult | AnyNode): void => { + const program = getProgram(ast); + const usedNames = new Set(); + const hocs: Array<[string, string]> = []; + const componentWrapperDeclarations: AnyNode[] = []; + + function getUid(name: string) { + let uid = `_${name}`; + let index = 2; + while (usedNames.has(uid) || hasTopLevelBindingName(program, uid)) { + uid = `_${name}${index++}`; + } + usedNames.add(uid); + return uid; + } + + function getHocUid(hocName: string) { + const uid = getUid(hocName); + hocs.push([hocName, uid]); + return identifier(uid); + } + + function wrapNamedComponentDeclaration(name: string, declaration: AnyNode) { + const uid = getHocUid(`with${name}Props`); + const expression = + declaration.type === 'FunctionDeclaration' + ? toFunctionExpression(declaration) + : declaration.type === 'ClassDeclaration' + ? toClassExpression(declaration) + : declaration; + return variableDeclaration(name, callExpression(uid, [expression])); + } + + for (const statement of [...(program.body ?? [])]) { + if (statement.type === 'ExportDefaultDeclaration') { + const declaration = statement.declaration; + if (!declaration) { + continue; + } + if ( + declaration.declare === true || + declaration.type === 'TSInterfaceDeclaration' + ) { + continue; + } + const uid = getHocUid('withComponentProps'); + if ( + (declaration.type === 'FunctionDeclaration' || + declaration.type === 'ClassDeclaration') && + declaration.id?.name + ) { + const statementIndex = program.body.indexOf(statement); + program.body.splice(statementIndex, 0, declaration); + statement.declaration = callExpression(uid, [ + identifier(declaration.id.name), + ]); + continue; + } + const expression = + declaration.type === 'FunctionDeclaration' + ? toFunctionExpression(declaration) + : declaration.type === 'ClassDeclaration' + ? toClassExpression(declaration) + : declaration; + statement.declaration = callExpression(uid, [expression]); + continue; + } + + if (statement.type !== 'ExportNamedDeclaration') { + continue; + } + if (statement.exportKind === 'type') { + continue; + } + const declaration = statement.declaration; + if (declaration?.type === 'VariableDeclaration') { + for (const declarator of declaration.declarations ?? []) { + if ( + declarator.id?.type !== 'Identifier' || + !declarator.init || + !isNamedComponentExport(declarator.id.name) + ) { + continue; + } + const uid = getHocUid(`with${declarator.id.name}Props`); + declarator.init = callExpression(uid, [declarator.init]); + } + continue; + } + + if ( + (declaration?.type === 'FunctionDeclaration' || + declaration?.type === 'ClassDeclaration') && + declaration.id?.name && + isNamedComponentExport(declaration.id.name) + ) { + const name = declaration.id.name; + statement.declaration = wrapNamedComponentDeclaration(name, declaration); + continue; + } + + if (statement.source) { + const importSpecifiers: Array<{ local: string; imported: string }> = []; + const sourceWrapperDeclarations: AnyNode[] = []; + const wrappedExportSpecifiers: AnyNode[] = []; + statement.specifiers = (statement.specifiers ?? []).filter( + (specifier: AnyNode) => { + if ( + specifier.type !== 'ExportSpecifier' || + specifier.exportKind === 'type' + ) { + return true; + } + const exportedName = getExportedName(specifier); + const importedName = getExportedName(specifier.local); + const componentExportName = exportedName + ? getComponentExportName(exportedName) + : null; + if (!exportedName || !importedName || !componentExportName) { + return true; + } + const sourceLocalName = getUid(`${exportedName}Source`); + const wrappedLocalName = getUid(exportedName); + const uid = getHocUid(`with${componentExportName}Props`); + importSpecifiers.push({ + imported: importedName, + local: sourceLocalName, + }); + sourceWrapperDeclarations.push( + variableDeclaration( + wrappedLocalName, + callExpression(uid, [identifier(sourceLocalName)]) + ) + ); + wrappedExportSpecifiers.push( + exportSpecifier(wrappedLocalName, exportedName) + ); + return false; + } + ); + if (importSpecifiers.length > 0) { + const statementIndex = program.body.indexOf(statement); + const replacementStatements = [ + importDeclaration(importSpecifiers, String(statement.source.value)), + ]; + if (statement.specifiers.length > 0) { + replacementStatements.push(statement); + } + replacementStatements.push(...sourceWrapperDeclarations); + replacementStatements.push( + exportNamedDeclaration(wrappedExportSpecifiers) + ); + program.body.splice(statementIndex, 1, ...replacementStatements); + } + continue; + } + + for (const specifier of statement.specifiers ?? []) { + if ( + specifier.type !== 'ExportSpecifier' || + specifier.exportKind === 'type' + ) { + continue; + } + const exportedName = getExportedName(specifier); + const componentExportName = exportedName + ? getComponentExportName(exportedName) + : null; + if (!exportedName || !componentExportName) { + continue; + } + const localName = specifier.local?.name; + if (!localName) { + continue; + } + const wrappedLocalName = getUid(exportedName); + const uid = getHocUid(`with${componentExportName}Props`); + componentWrapperDeclarations.push( + variableDeclaration( + wrappedLocalName, + callExpression(uid, [identifier(localName)]) + ) + ); + specifier.local = identifier(wrappedLocalName); + } + } + + program.body.push(...componentWrapperDeclarations); + + if (hocs.length > 0) { + program.body.splice( + getImportInsertionIndex(program), + 0, + importDeclaration( + hocs.map(([name, local]) => ({ imported: name, local })), + 'virtual/react-router/with-props' + ) + ); + } +}; + +function isNamedComponentExport( + name: string +): name is (typeof NAMED_COMPONENT_EXPORTS)[number] { + return NAMED_COMPONENT_EXPORTS_SET.has(name); +} diff --git a/src/route-export-pruning.ts b/src/route-export-pruning.ts new file mode 100644 index 0000000..d3e3762 --- /dev/null +++ b/src/route-export-pruning.ts @@ -0,0 +1,668 @@ +import { walk, type ParseResult } from 'yuku-parser'; +import { + getExportedName, + getPatternIdentifierNames, + getProgram, + removeFromArray, + type AnyNode, + type ProgramNode, +} from './route-ast.js'; + +export function validateDestructuredExports( + id: AnyNode, + exportsToRemove: readonly string[] +): void { + validateBindingTarget(id, new Set(exportsToRemove)); +} + +export function invalidDestructureError(name: string): Error { + return new Error(`Cannot remove destructured export "${name}"`); +} + +const assertAllowedBindingName = ( + name: string, + exportsToRemove: ReadonlySet +): void => { + if (exportsToRemove.has(name)) { + throw invalidDestructureError(name); + } +}; + +const validateRestElement = ( + element: AnyNode, + exportsToRemove: ReadonlySet +): void => { + if (element.argument?.type === 'Identifier' && element.argument.name) { + assertAllowedBindingName(element.argument.name, exportsToRemove); + } +}; + +const validateObjectProperty = ( + property: AnyNode, + exportsToRemove: ReadonlySet +): void => { + if (property.type === 'RestElement') { + validateRestElement(property, exportsToRemove); + return; + } + if (property.type === 'Property') { + validateBindingTarget( + property.value as AnyNode | null | undefined, + exportsToRemove + ); + } +}; + +const validateBindingTarget = ( + node: AnyNode | null | undefined, + exportsToRemove: ReadonlySet +): void => { + if (!node) { + return; + } + + switch (node.type) { + case 'Identifier': + if (node.name) { + assertAllowedBindingName(node.name, exportsToRemove); + } + return; + case 'AssignmentPattern': + validateBindingTarget(node.left, exportsToRemove); + return; + case 'ArrayPattern': + for (const element of node.elements ?? []) { + if (element?.type === 'RestElement') { + validateRestElement(element, exportsToRemove); + } else { + validateBindingTarget(element, exportsToRemove); + } + } + return; + case 'ObjectPattern': + for (const property of node.properties ?? []) { + validateObjectProperty(property, exportsToRemove); + } + } +}; + +const getDeclaredNames = (node: AnyNode): Set => { + const names = new Set(); + if (node.type === 'VariableDeclaration') { + for (const declarator of node.declarations ?? []) { + getPatternIdentifierNames(declarator.id, names); + } + } else if ( + (node.type === 'FunctionDeclaration' || node.type === 'ClassDeclaration') && + node.id?.name + ) { + names.add(node.id.name); + } else if (node.type === 'ImportDeclaration') { + for (const specifier of node.specifiers ?? []) { + if (specifier.local?.name) { + names.add(specifier.local.name); + } + } + } + return names; +}; + +const isIdentifierDeclaration = (node: AnyNode, parent: AnyNode | null) => { + if (!parent || node.type !== 'Identifier') { + return false; + } + if ( + (parent.type === 'FunctionDeclaration' || + parent.type === 'FunctionExpression' || + parent.type === 'ClassDeclaration' || + parent.type === 'ClassExpression') && + parent.id === node + ) { + return true; + } + if (parent.type === 'VariableDeclarator') { + return Boolean( + node.name && getPatternIdentifierNames(parent.id).has(node.name) + ); + } + if ( + (parent.type === 'ImportSpecifier' || + parent.type === 'ImportDefaultSpecifier' || + parent.type === 'ImportNamespaceSpecifier') && + parent.local === node + ) { + return true; + } + if ( + (parent.type === 'FunctionDeclaration' || + parent.type === 'FunctionExpression' || + parent.type === 'ArrowFunctionExpression') && + node.name + ) { + const name = node.name; + return (parent.params ?? []).some((param: AnyNode) => + getPatternIdentifierNames(param).has(name) + ); + } + return false; +}; + +const isNonReferenceIdentifier = (node: AnyNode, parent: AnyNode | null) => { + if (!parent || node.type !== 'Identifier') { + return false; + } + if (isIdentifierDeclaration(node, parent)) { + return true; + } + if ( + parent.type === 'MemberExpression' && + parent.property === node && + !parent.computed + ) { + return true; + } + if ( + parent.type === 'Property' && + parent.key === node && + !parent.computed && + !parent.shorthand + ) { + return true; + } + if ( + parent.type === 'MethodDefinition' && + parent.key === node && + !parent.computed + ) { + return true; + } + if ( + parent.type === 'ExportSpecifier' || + parent.type === 'ExportDefaultSpecifier' || + parent.type === 'ExportNamespaceSpecifier' + ) { + return true; + } + if (parent.type === 'ImportSpecifier' && parent.imported === node) { + return true; + } + if ( + parent.type === 'LabeledStatement' || + parent.type === 'BreakStatement' || + parent.type === 'ContinueStatement' + ) { + return true; + } + return false; +}; + +const isUppercaseName = (name: string): boolean => /^[A-Z]/.test(name); + +const collectReferencedNames = (node: AnyNode): Set => { + const referenced = new Set(); + walk(node as never, { + Identifier(node, ctx) { + const current = node as unknown as AnyNode; + const parent = (ctx as { parent?: unknown }).parent as AnyNode | null; + if (!isNonReferenceIdentifier(current, parent) && current.name) { + referenced.add(current.name); + } + }, + JSXIdentifier(node, ctx) { + const current = node as unknown as AnyNode; + const parent = (ctx as { parent?: unknown }).parent as AnyNode | null; + if (!parent) { + return; + } + if (parent.type === 'JSXMemberExpression' && parent.object === current) { + if (current.name) { + referenced.add(current.name); + } + return; + } + if (!current.name || !isUppercaseName(current.name)) { + return; + } + if ( + (parent.type === 'JSXOpeningElement' || + parent.type === 'JSXClosingElement') && + parent.name === current + ) { + referenced.add(current.name); + return; + } + }, + ExportSpecifier(node, ctx) { + const current = node as unknown as AnyNode; + const declaration = (ctx as { parent?: unknown }) + .parent as AnyNode | null; + if ( + !declaration?.source && + declaration?.exportKind !== 'type' && + current.local?.name && + current.exportKind !== 'type' + ) { + referenced.add(current.local.name); + } + }, + }); + return referenced; +}; + +type TopLevelDeclaration = { + referencedNames: Set; +}; + +type TopLevelDeclarationGraph = { + declarationsByNode: Map; + declarationsByName: Map>; +}; + +const createTopLevelDeclarationGraph = ( + program: ProgramNode +): TopLevelDeclarationGraph => { + const declarationsByNode = new Map(); + const declarationsByName = new Map>(); + + const registerDeclaration = ( + node: AnyNode, + declarationNode: AnyNode, + declaredNames: Set + ) => { + const declaration: TopLevelDeclaration = { + referencedNames: collectReferencedNames(declarationNode), + }; + declarationsByNode.set(node, declaration); + for (const name of declaredNames) { + const namedDeclarations = declarationsByName.get(name) ?? new Set(); + namedDeclarations.add(declaration); + declarationsByName.set(name, namedDeclarations); + } + }; + + for (const statement of [...(program.body ?? [])]) { + if (statement.type === 'VariableDeclaration') { + for (const declarator of statement.declarations ?? []) { + registerDeclaration( + declarator, + declarator, + getPatternIdentifierNames(declarator.id) + ); + } + continue; + } + if ( + statement.type === 'FunctionDeclaration' || + statement.type === 'ClassDeclaration' + ) { + registerDeclaration(statement, statement, getDeclaredNames(statement)); + } + } + + return { declarationsByNode, declarationsByName }; +}; + +const collectLiveTopLevelDeclarations = ( + program: ProgramNode, + graph: TopLevelDeclarationGraph +): Set => { + const pendingNames: string[] = []; + + for (const statement of program.body ?? []) { + if (statement.type === 'VariableDeclaration') { + continue; + } + if (graph.declarationsByNode.has(statement)) { + continue; + } + for (const name of collectReferencedNames(statement)) { + pendingNames.push(name); + } + } + + // This is intentionally name-based and conservative: shadowing may retain a + // declaration, but it must never make a live declaration removable. + const visitedNames = new Set(); + const liveDeclarations = new Set(); + while (pendingNames.length > 0) { + const name = pendingNames.pop(); + if (!name || visitedNames.has(name)) { + continue; + } + visitedNames.add(name); + for (const declaration of graph.declarationsByName.get(name) ?? []) { + if (!liveDeclarations.has(declaration)) { + liveDeclarations.add(declaration); + for (const referencedName of declaration.referencedNames) { + pendingNames.push(referencedName); + } + } + } + } + + return liveDeclarations; +}; + +const declarationReferencesName = ( + declaration: TopLevelDeclaration, + names: ReadonlySet, + graph: TopLevelDeclarationGraph, + cache: Map, + visitedNames = new Set() +): boolean => { + const cached = cache.get(declaration); + if (cached !== undefined) { + return cached; + } + + for (const referencedName of declaration.referencedNames) { + if (names.has(referencedName)) { + cache.set(declaration, true); + return true; + } + if (visitedNames.has(referencedName)) { + continue; + } + visitedNames.add(referencedName); + for (const referencedDeclaration of graph.declarationsByName.get( + referencedName + ) ?? []) { + if ( + declarationReferencesName( + referencedDeclaration, + names, + graph, + cache, + visitedNames + ) + ) { + cache.set(declaration, true); + return true; + } + } + } + cache.set(declaration, false); + return false; +}; + +const removeNewlyDeadTopLevelDeclarations = ( + program: ProgramNode, + graph: TopLevelDeclarationGraph, + previouslyLive: ReadonlySet, + removedExportReferencedNames: ReadonlySet +): void => { + const currentlyLive = collectLiveTopLevelDeclarations(program, graph); + const removedReferenceCache = new Map(); + const isRemovableDeadDeclaration = (node: AnyNode) => { + const declaration = graph.declarationsByNode.get(node); + if (!declaration || currentlyLive.has(declaration)) { + return false; + } + return ( + previouslyLive.has(declaration) || + declarationReferencesName( + declaration, + removedExportReferencedNames, + graph, + removedReferenceCache + ) + ); + }; + + program.body = program.body.filter((statement: AnyNode) => { + if (statement.type === 'VariableDeclaration') { + statement.declarations = (statement.declarations ?? []).filter( + (declarator: AnyNode) => !isRemovableDeadDeclaration(declarator) + ); + return statement.declarations.length > 0; + } + return !isRemovableDeadDeclaration(statement); + }); +}; + +const hasRemovableExport = ( + program: ProgramNode, + exportsToRemove: ReadonlySet +): boolean => { + const removesNamedExports = [...exportsToRemove].some( + name => name !== 'default' + ); + for (const statement of program.body ?? []) { + if (statement.type === 'ExportAllDeclaration') { + const exportedName = statement.exported + ? getExportedName({ exported: statement.exported }) + : null; + if (exportedName && exportsToRemove.has(exportedName)) { + return true; + } + if (!exportedName && removesNamedExports) { + return true; + } + continue; + } + + if (statement.type === 'ExportDefaultDeclaration') { + if (exportsToRemove.has('default')) { + return true; + } + continue; + } + + if (statement.type !== 'ExportNamedDeclaration') { + continue; + } + + for (const specifier of statement.specifiers ?? []) { + if (specifier.type !== 'ExportSpecifier') { + continue; + } + const exportedName = getExportedName(specifier); + if (exportedName && exportsToRemove.has(exportedName)) { + return true; + } + } + + const declaration = statement.declaration; + if (declaration?.type === 'VariableDeclaration') { + for (const declarator of declaration.declarations ?? []) { + for (const name of getPatternIdentifierNames(declarator.id)) { + if (exportsToRemove.has(name)) { + return true; + } + } + } + continue; + } + + if ( + (declaration?.type === 'FunctionDeclaration' || + declaration?.type === 'ClassDeclaration') && + declaration.id?.name && + exportsToRemove.has(declaration.id.name) + ) { + return true; + } + } + return false; +}; + +export const removeExports = ( + ast: ParseResult | AnyNode, + exportsToRemove: readonly string[], + exportsToRemoveSet: ReadonlySet = new Set(exportsToRemove) +): boolean => { + const program = getProgram(ast); + if (!hasRemovableExport(program, exportsToRemoveSet)) { + return false; + } + + const declarationGraph = createTopLevelDeclarationGraph(program); + const previouslyLive = collectLiveTopLevelDeclarations( + program, + declarationGraph + ); + let exportsChanged = false; + const removedExportLocalNames = new Set(); + const removedExportReferencedNames = new Set(); + const removesNamedExports = exportsToRemove.some(name => name !== 'default'); + const trackRemovedExportReferences = (node: AnyNode | null | undefined) => { + if (!node) { + return; + } + const declaration = declarationGraph.declarationsByNode.get(node); + for (const name of declaration?.referencedNames ?? + collectReferencedNames(node)) { + removedExportReferencedNames.add(name); + } + }; + + for (const statement of [...program.body]) { + if (statement.type === 'ExportAllDeclaration') { + const exportedName = statement.exported + ? getExportedName({ exported: statement.exported }) + : null; + if (exportedName && exportsToRemoveSet.has(exportedName)) { + exportsChanged = true; + removeFromArray(program.body, statement); + } + if (!exportedName && removesNamedExports) { + throw new Error( + 'Cannot remove named exports from `export *`; use explicit named re-exports.' + ); + } + continue; + } + + if (statement.type === 'ExportNamedDeclaration') { + if (statement.specifiers?.length) { + statement.specifiers = statement.specifiers.filter( + (specifier: AnyNode) => { + if (specifier.type !== 'ExportSpecifier') { + return true; + } + const exportedName = getExportedName(specifier); + if (exportedName && exportsToRemoveSet.has(exportedName)) { + exportsChanged = true; + if (specifier.local?.name) { + removedExportLocalNames.add(specifier.local.name); + removedExportReferencedNames.add(specifier.local.name); + } + return false; + } + return true; + } + ); + if (statement.specifiers.length === 0 && !statement.declaration) { + removeFromArray(program.body, statement); + } + } + + const declaration = statement.declaration; + if (declaration?.type === 'VariableDeclaration') { + declaration.declarations = (declaration.declarations ?? []).filter( + (declarator: AnyNode) => { + const id = declarator.id; + if (id?.type === 'Identifier') { + if (id.name && exportsToRemoveSet.has(id.name)) { + exportsChanged = true; + removedExportLocalNames.add(id.name); + removedExportReferencedNames.add(id.name); + trackRemovedExportReferences(declarator); + return false; + } + return true; + } + + if (id) { + validateDestructuredExports(id, exportsToRemove); + } + return true; + } + ); + if (declaration.declarations.length === 0) { + removeFromArray(program.body, statement); + } + } + + if ( + (declaration?.type === 'FunctionDeclaration' || + declaration?.type === 'ClassDeclaration') && + declaration.id?.name && + exportsToRemoveSet.has(declaration.id.name) + ) { + exportsChanged = true; + removedExportLocalNames.add(declaration.id.name); + removedExportReferencedNames.add(declaration.id.name); + trackRemovedExportReferences(statement); + removeFromArray(program.body, statement); + } + } + + if ( + statement.type === 'ExportDefaultDeclaration' && + exportsToRemoveSet.has('default') + ) { + exportsChanged = true; + const declaration = statement.declaration; + if (declaration?.type === 'Identifier' && declaration.name) { + removedExportLocalNames.add(declaration.name); + removedExportReferencedNames.add(declaration.name); + } else if (declaration?.id?.name) { + removedExportLocalNames.add(declaration.id.name); + removedExportReferencedNames.add(declaration.id.name); + } + trackRemovedExportReferences(statement); + removeFromArray(program.body, statement); + } + } + + for (const statement of [...program.body]) { + const expression = + statement.type === 'ExpressionStatement' ? statement.expression : null; + const left = + expression?.type === 'AssignmentExpression' ? expression.left : null; + if ( + left?.type === 'MemberExpression' && + left.object?.type === 'Identifier' && + left.object.name && + removedExportLocalNames.has(left.object.name) + ) { + removeFromArray(program.body, statement); + } + } + + if (exportsChanged) { + removeNewlyDeadTopLevelDeclarations( + program, + declarationGraph, + previouslyLive, + removedExportReferencedNames + ); + } + + return exportsChanged; +}; + +export const removeUnusedImports = (ast: ParseResult | AnyNode): void => { + const program = getProgram(ast); + const referenced = collectReferencedNames(program); + for (const statement of [...program.body]) { + if (statement.type !== 'ImportDeclaration') { + continue; + } + if ((statement.specifiers ?? []).length === 0) { + continue; + } + statement.specifiers = (statement.specifiers ?? []).filter( + (specifier: AnyNode) => { + if (specifier.importKind === 'type') { + return false; + } + return !specifier.local?.name || referenced.has(specifier.local.name); + } + ); + if (statement.specifiers.length === 0) { + removeFromArray(program.body, statement); + } + } +}; diff --git a/src/route-export-resolution.ts b/src/route-export-resolution.ts new file mode 100644 index 0000000..8aa2bfd --- /dev/null +++ b/src/route-export-resolution.ts @@ -0,0 +1,329 @@ +import { readFileSync, statSync, type Stats } from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, relative, resolve } from 'pathe'; +import { JS_EXTENSIONS, PLUGIN_NAME } from './constants.js'; +import { + getExportNamesAndExportAll, + getRouteModuleAnalysis, +} from './export-utils.js'; + +const tryStat = (path: string): Stats | null => + statSync(path, { throwIfNoEntry: false }) ?? null; + +type PackageExportTarget = + | string + | PackageExportTarget[] + | { [condition: string]: PackageExportTarget | undefined } + | null; + +type PackageJson = { + exports?: PackageExportTarget; + main?: string; + module?: string; +}; + +const PACKAGE_IMPORT_CONDITIONS = new Set(['import', 'node']); +const PACKAGE_RESOLUTION_NOT_APPLICABLE = Symbol( + 'package resolution not applicable' +); + +const resolveIndexFile = (dirPath: string): string | null => { + for (const ext of JS_EXTENSIONS) { + const candidate = resolve(dirPath, `index${ext}`); + const stats = tryStat(candidate); + if (stats?.isFile()) { + return candidate; + } + } + return null; +}; + +const resolvePathWithExtensions = (basePath: string): string | null => { + const stats = tryStat(basePath); + if (stats?.isFile()) { + return basePath; + } + if (stats?.isDirectory()) { + return resolveIndexFile(basePath); + } + + for (const ext of JS_EXTENSIONS) { + const candidate = `${basePath}${ext}`; + const candidateStats = tryStat(candidate); + if (candidateStats?.isFile()) { + return candidate; + } + } + + return resolveIndexFile(basePath); +}; + +const parsePackageSpecifier = ( + specifier: string +): { packageName: string; packageSubpath: string } | null => { + if ( + specifier.startsWith('.') || + specifier.startsWith('/') || + specifier.startsWith('#') + ) { + return null; + } + + const parts = specifier.split('/'); + const packageName = specifier.startsWith('@') + ? parts.slice(0, 2).join('/') + : parts[0]; + const packagePathParts = specifier.startsWith('@') + ? parts.slice(2) + : parts.slice(1); + + return { + packageName, + packageSubpath: + packagePathParts.length > 0 ? `./${packagePathParts.join('/')}` : '.', + }; +}; + +const findPackageDirectory = ( + importerPath: string, + packageName: string +): string | null => { + let currentDirectory = dirname(importerPath); + + while (true) { + const packageDirectory = resolve( + currentDirectory, + 'node_modules', + packageName + ); + const packageJsonPath = resolve(packageDirectory, 'package.json'); + if (tryStat(packageJsonPath)?.isFile()) { + return packageDirectory; + } + + const parentDirectory = dirname(currentDirectory); + if (parentDirectory === currentDirectory) { + return null; + } + currentDirectory = parentDirectory; + } +}; + +const readPackageJson = (packageDirectory: string): PackageJson | null => { + try { + return JSON.parse( + readFileSync(resolve(packageDirectory, 'package.json'), 'utf8') + ) as PackageJson; + } catch { + return null; + } +}; + +const resolvePackageExportTarget = ( + target: PackageExportTarget | undefined +): string | null => { + if (!target) { + return null; + } + + if (typeof target === 'string') { + return target; + } + + if (Array.isArray(target)) { + for (const nestedTarget of target) { + const resolvedTarget = resolvePackageExportTarget(nestedTarget); + if (resolvedTarget) { + return resolvedTarget; + } + } + return null; + } + + for (const [condition, nestedTarget] of Object.entries(target)) { + if (condition === 'default' || PACKAGE_IMPORT_CONDITIONS.has(condition)) { + const resolvedTarget = resolvePackageExportTarget(nestedTarget); + if (resolvedTarget) { + return resolvedTarget; + } + } + } + + return null; +}; + +const resolvePackageExports = ( + packageDirectory: string, + packageSubpath: string, + packageJson: PackageJson +): string | null => { + const exports = packageJson.exports; + if (!exports) { + const entry = packageJson.module ?? packageJson.main; + return entry + ? resolvePathWithExtensions(resolve(packageDirectory, entry)) + : null; + } + + const target = + typeof exports === 'object' && + !Array.isArray(exports) && + Object.keys(exports).some(key => key.startsWith('.')) + ? exports[packageSubpath] + : packageSubpath === '.' + ? exports + : undefined; + + const resolvedTarget = resolvePackageExportTarget(target); + if (!resolvedTarget || !resolvedTarget.startsWith('./')) { + return null; + } + + return resolvePathWithExtensions(resolve(packageDirectory, resolvedTarget)); +}; + +const resolvePackageImport = ( + specifier: string, + importerPath: string +): string | null | typeof PACKAGE_RESOLUTION_NOT_APPLICABLE => { + const parsedSpecifier = parsePackageSpecifier(specifier); + if (!parsedSpecifier) { + return PACKAGE_RESOLUTION_NOT_APPLICABLE; + } + + const packageDirectory = findPackageDirectory( + importerPath, + parsedSpecifier.packageName + ); + if (!packageDirectory) { + return PACKAGE_RESOLUTION_NOT_APPLICABLE; + } + + const packageJson = readPackageJson(packageDirectory); + if (!packageJson) { + return PACKAGE_RESOLUTION_NOT_APPLICABLE; + } + + if ( + packageJson.exports === undefined && + !packageJson.module && + !packageJson.main + ) { + return PACKAGE_RESOLUTION_NOT_APPLICABLE; + } + + return resolvePackageExports( + packageDirectory, + parsedSpecifier.packageSubpath, + packageJson + ); +}; + +const resolveExportAllModule = ( + specifier: string, + importerPath: string +): string | null => { + if (specifier.startsWith('.') || specifier.startsWith('/')) { + const basePath = specifier.startsWith('/') + ? specifier + : resolve(dirname(importerPath), specifier); + const resolvedPath = resolvePathWithExtensions(basePath); + if (resolvedPath) { + return resolvedPath; + } + } + + const importResolvedPath = resolvePackageImport(specifier, importerPath); + if (importResolvedPath !== PACKAGE_RESOLUTION_NOT_APPLICABLE) { + return importResolvedPath; + } + + try { + return createRequire(importerPath).resolve(specifier); + } catch { + return null; + } +}; + +export type RouteExportResolver = ( + specifier: string, + importerPath: string +) => Promise | string | null; + +export type RouteModuleResolveCallback = ( + error: Error | null, + resolved?: string | false +) => void; + +export type RouteModuleResolver = ( + context: string, + specifier: string, + callback: RouteModuleResolveCallback +) => void; + +export const createBundlerRouteExportResolver = + (resolveModule: RouteModuleResolver): RouteExportResolver => + (specifier, importerPath) => + new Promise(resolveResolvedPath => { + resolveModule(dirname(importerPath), specifier, (error, resolved) => { + resolveResolvedPath(error || !resolved ? null : resolved); + }); + }); + +export const collectClientOnlyStubExportNames = async ( + code: string, + resourcePath: string, + resolveModule: RouteExportResolver = resolveExportAllModule +): Promise> => { + const { exportNames: directExportNames, exportAllModules } = + await getExportNamesAndExportAll(code); + const exportNames = new Set(directExportNames); + const unresolvedExportAll = new Set(); + const visitedModules = new Set(); + + const collectExportNamesFromModule = async ( + modulePath: string + ): Promise => { + if (visitedModules.has(modulePath)) { + return; + } + visitedModules.add(modulePath); + const { exports: moduleExportNames, exportAllModules: moduleExportAll } = + await getRouteModuleAnalysis(modulePath); + for (const name of moduleExportNames) { + if (name !== 'default') { + exportNames.add(name); + } + } + for (const nestedSpecifier of moduleExportAll) { + const nestedPath = await resolveModule(nestedSpecifier, modulePath); + if (!nestedPath) { + unresolvedExportAll.add(nestedSpecifier); + continue; + } + await collectExportNamesFromModule(nestedPath); + } + }; + + for (const specifier of exportAllModules) { + const resolvedPath = await resolveModule(specifier, resourcePath); + if (!resolvedPath) { + unresolvedExportAll.add(specifier); + continue; + } + await collectExportNamesFromModule(resolvedPath); + } + + if (unresolvedExportAll.size > 0) { + throw new Error( + `[${PLUGIN_NAME}] Client-only module uses \`export * from\` with ` + + `unresolvable specifier(s): ${Array.from(unresolvedExportAll) + .map(spec => `\`${spec}\``) + .join(', ')}. ` + + `Please explicitly re-export named bindings in ` + + `\`${relative(process.cwd(), resourcePath)}\`.` + ); + } + + return exportNames; +}; diff --git a/src/route-transform-tasks.ts b/src/route-transform-tasks.ts new file mode 100644 index 0000000..05a4759 --- /dev/null +++ b/src/route-transform-tasks.ts @@ -0,0 +1,249 @@ +import { basename as pathBasename, relative } from 'pathe'; +import { generate, parse } from './yuku.js'; +import { + SERVER_ONLY_ROUTE_EXPORTS, + SERVER_ONLY_ROUTE_EXPORTS_SET, +} from './constants.js'; +import { collectProgramExportNames } from './export-utils.js'; +import { + removeExports, + removeUnusedImports, + transformRoute, +} from './plugin-utils.js'; +import { + collectClientOnlyStubExportNames, + type RouteExportResolver, +} from './route-export-resolution.js'; +import { + createRouteChunkArtifact, + createRouteClientEntryArtifact, +} from './route-artifacts.js'; +import { + detectRouteChunksIfEnabled, + getRouteChunkModuleId, + type RouteChunkCache, + type RouteChunkConfig, +} from './route-chunks.js'; +import { getProgram } from './route-ast.js'; + +export type RouteTransformResult = { + code: string; + map?: ReturnType['map']; +}; + +type BaseRouteTransformTask = { + code: string; + resourcePath: string; +}; + +export type RouteClientEntryTransformTask = BaseRouteTransformTask & { + kind: 'routeClientEntry'; + environmentName?: string; + isBuild: boolean; + routeChunkConfig: RouteChunkConfig; +}; + +export type RouteChunkTransformTask = BaseRouteTransformTask & { + kind: 'routeChunk'; + resource: string; + isBuild: boolean; + routeChunkConfig: RouteChunkConfig; +}; + +export type SplitRouteExportsTransformTask = BaseRouteTransformTask & { + kind: 'splitRouteExports'; + routeChunkConfig: RouteChunkConfig; +}; + +export type ClientOnlyStubTransformTask = BaseRouteTransformTask & { + kind: 'clientOnlyStub'; + resolveExportAllModule?: RouteExportResolver; +}; + +export type RouteModuleTransformTask = BaseRouteTransformTask & { + kind: 'routeModule'; + resource: string; + environmentName: string; + sourceMaps: boolean; + ssr: boolean; + isBuild: boolean; + isSpaMode: boolean; + rootRoutePath: string | null; +}; + +export type RouteTransformTask = + | RouteClientEntryTransformTask + | RouteChunkTransformTask + | SplitRouteExportsTransformTask + | ClientOnlyStubTransformTask + | RouteModuleTransformTask; + +export type RouteTransformTaskOptions = { + routeChunkCache?: RouteChunkCache; +}; + +const defaultRouteChunkCache: RouteChunkCache = new Map(); + +const getRouteChunkCache = (options?: RouteTransformTaskOptions) => + options?.routeChunkCache ?? defaultRouteChunkCache; + +const splitRouteExports = async ( + task: SplitRouteExportsTransformTask, + options?: RouteTransformTaskOptions +): Promise => { + const { exportNames, hasRouteChunks, chunkedExports } = + await detectRouteChunksIfEnabled( + getRouteChunkCache(options), + task.routeChunkConfig, + task.resourcePath, + task.code + ); + if (!hasRouteChunks) { + return { code: task.code, map: null }; + } + + const chunkedExportSet = new Set(chunkedExports); + const mainChunkReexports = exportNames + .filter(name => !chunkedExportSet.has(name)) + .join(', '); + const chunkBasePath = `./${pathBasename(task.resourcePath)}`; + + return { + code: [ + mainChunkReexports + ? `export { ${mainChunkReexports} } from "${getRouteChunkModuleId( + chunkBasePath, + 'main' + )}";` + : null, + ...chunkedExports.map( + exportName => + `export { ${exportName} } from "${getRouteChunkModuleId( + chunkBasePath, + exportName + )}";` + ), + ] + .filter(Boolean) + .join('\n'), + map: null, + }; +}; + +const createClientOnlyStub = async ( + task: ClientOnlyStubTransformTask +): Promise => { + const exportNames = await collectClientOnlyStubExportNames( + task.code, + task.resourcePath, + task.resolveExportAllModule + ); + + return { + code: Array.from(exportNames) + .map(name => + name === 'default' + ? 'export default undefined;' + : `export const ${name} = undefined;` + ) + .join('\n'), + map: null, + }; +}; + +const transformRouteModule = async ( + task: RouteModuleTransformTask +): Promise => { + let code = task.code; + + const defaultExportMatch = code.match(/\n\s{0,}([\w\d_]+)\sas default,?/); + if (defaultExportMatch && typeof defaultExportMatch.index === 'number') { + code = + code.slice(0, defaultExportMatch.index) + + code.slice(defaultExportMatch.index + defaultExportMatch[0].length); + code += `\nexport default ${defaultExportMatch[1]};`; + } + + const ast = parse(code, { sourceType: 'module' }); + if (task.environmentName === 'web' && !task.ssr && task.isSpaMode) { + const resolvedExportNames = collectProgramExportNames(getProgram(ast)); + const isRootRoute = task.resourcePath === task.rootRoutePath; + const relativePath = relative(process.cwd(), task.resourcePath); + + const invalidServerOnly = resolvedExportNames.filter(exp => { + if (isRootRoute && exp === 'loader') return false; + return SERVER_ONLY_ROUTE_EXPORTS_SET.has(exp); + }); + + if (invalidServerOnly.length > 0) { + const list = invalidServerOnly.map(e => `\`${e}\``).join(', '); + throw new Error( + `SPA Mode: ${invalidServerOnly.length} invalid route export(s) in ` + + `\`${relativePath}\`: ${list}. ` + + `See https://reactrouter.com/how-to/spa for more information.` + ); + } + + if (!isRootRoute && resolvedExportNames.includes('HydrateFallback')) { + throw new Error( + `SPA Mode: Invalid \`HydrateFallback\` export found in ` + + `\`${relativePath}\`. ` + + `\`HydrateFallback\` is only permitted on the root route in SPA Mode. ` + + `See https://reactrouter.com/how-to/spa for more information.` + ); + } + } + + const removedServerOnlyExports = + task.environmentName === 'web' + ? removeExports( + ast, + SERVER_ONLY_ROUTE_EXPORTS, + SERVER_ONLY_ROUTE_EXPORTS_SET + ) + : false; + transformRoute(ast); + if (removedServerOnlyExports) { + removeUnusedImports(ast); + } + + return generate(ast, { + // Rsbuild merges this map with its downstream SWC transform. Only pay the + // code-generation cost when this environment actually emits JS maps. + sourceMaps: task.sourceMaps, + filename: task.resource, + sourceFileName: task.resourcePath, + }); +}; + +export const executeRouteTransformTask = async ( + task: RouteTransformTask, + options?: RouteTransformTaskOptions +): Promise => { + switch (task.kind) { + case 'routeClientEntry': + return createRouteClientEntryArtifact({ + code: task.code, + resourcePath: task.resourcePath, + environmentName: task.environmentName, + isBuild: task.isBuild, + routeChunkCache: getRouteChunkCache(options), + routeChunkConfig: task.routeChunkConfig, + }); + case 'routeChunk': + return createRouteChunkArtifact({ + code: task.code, + resource: task.resource, + resourcePath: task.resourcePath, + isBuild: task.isBuild, + routeChunkCache: getRouteChunkCache(options), + routeChunkConfig: task.routeChunkConfig, + }); + case 'splitRouteExports': + return splitRouteExports(task, options); + case 'clientOnlyStub': + return createClientOnlyStub(task); + case 'routeModule': + return transformRouteModule(task); + } +}; diff --git a/src/route-watch.ts b/src/route-watch.ts new file mode 100644 index 0000000..ec26780 --- /dev/null +++ b/src/route-watch.ts @@ -0,0 +1,322 @@ +import { watch, type FSWatcher } from 'node:fs'; +import { access, mkdir, readdir, writeFile } from 'node:fs/promises'; +import type { RsbuildConfig } from '@rsbuild/core'; +import { dirname, resolve } from 'pathe'; +import type { Route } from './types.js'; + +const ROUTE_RESTART_MARKER_ASSET = '.react-router/route-watch'; +const INITIAL_RESTART_MARKER_CONTENT = 'react-router-route-watch'; + +type RouteManifestSnapshotEntry = Pick< + Route, + 'caseSensitive' | 'file' | 'id' | 'index' | 'parentId' | 'path' +>; +type RouteManifestSnapshotEntryPair = readonly [ + string, + RouteManifestSnapshotEntry, +]; +type RouteManifestSnapshotEntries = + | Record + | ReadonlyArray; + +type WatchFilesConfig = NonNullable< + NonNullable['watchFiles'] +>; +export type WatchFileConfig = + | Exclude + | Extract[number]; + +type RouteDirectoryState = { + directories: Set; + routeTopology: Set; +}; + +type DirectoryWatcher = Pick; +type WatchDirectoryEntry = ( + directory: string, + onChange: () => void, + onError: (error: unknown) => void +) => DirectoryWatcher; + +const defaultWatchDirectoryEntry: WatchDirectoryEntry = ( + directory, + onChange, + onError +) => { + const watcher = watch(directory, onChange); + watcher.on('error', onError); + return watcher; +}; + +export const mergeWatchFiles = ( + existing: WatchFilesConfig | undefined, + additions: WatchFileConfig[] +): WatchFilesConfig => { + if (!existing) { + return additions as WatchFilesConfig; + } + return [ + ...(Array.isArray(existing) ? existing : [existing]), + ...additions, + ] as WatchFilesConfig; +}; + +export const getRouteRestartMarkerPath = (outputClientPath: string): string => + resolve(outputClientPath, ROUTE_RESTART_MARKER_ASSET); + +export const createRouteManifestSnapshot = ( + routes: RouteManifestSnapshotEntries +): Set => + new Set( + (Array.isArray(routes) ? routes : Object.entries(routes)) + // React Router uses sibling declaration order as a match tiebreaker, so + // callers that have ordered route config should pass ordered entries + // instead of a record with numeric-like keys. + .map(([routeId, route], order) => + JSON.stringify([ + order, + routeId, + route.id, + route.parentId ?? null, + route.path ?? null, + route.index ?? null, + route.caseSensitive ?? null, + route.file, + ]) + ) + ); + +export const ensureDevRestartMarker = async ( + restartMarkerPath: string +): Promise => { + // Dev owns this watched file directly so ordinary rebuilds do not rewrite it + // and trigger reload loops. + await mkdir(dirname(restartMarkerPath), { recursive: true }); + try { + await access(restartMarkerPath); + } catch { + await writeFile(restartMarkerPath, INITIAL_RESTART_MARKER_CONTENT); + } +}; + +const areSetsEqual = (left: Set, right: Set): boolean => { + if (left.size !== right.size) { + return false; + } + for (const value of left) { + if (!right.has(value)) { + return false; + } + } + return true; +}; + +const readRouteDirectories = async ( + watchDirectory: string +): Promise> => { + const directories = new Set(); + + const walkDirectory = async (directory: string): Promise => { + let entries; + try { + entries = await readdir(directory, { withFileTypes: true }); + } catch { + return; + } + + directories.add(directory); + await Promise.all( + entries.map(async entry => { + const entryPath = resolve(directory, entry.name); + if (entry.isDirectory()) { + await walkDirectory(entryPath); + } + }) + ); + }; + + await walkDirectory(watchDirectory); + return directories; +}; + +export const createRouteTopologyWatcher = async ({ + watchDirectory, + getRouteTopology, + initialRouteTopology, + restartMarkerPath, + onError, + onRouteTopologyChange, + watchDirectoryEntry: watchDirectoryOverride = defaultWatchDirectoryEntry, +}: { + watchDirectory: string; + getRouteTopology: () => Promise>; + initialRouteTopology?: Set; + restartMarkerPath: string; + onError: (error: unknown) => void; + onRouteTopologyChange?: () => void | Promise; + watchDirectoryEntry?: WatchDirectoryEntry; +}): Promise<() => Promise> => { + const discoveredDirectories = await readRouteDirectories(watchDirectory); + let discoveredState: RouteDirectoryState; + try { + discoveredState = { + directories: discoveredDirectories, + routeTopology: await getRouteTopology(), + }; + } catch (error) { + if (!initialRouteTopology) { + throw error; + } + onError(error); + discoveredState = { + directories: discoveredDirectories, + routeTopology: initialRouteTopology, + }; + } + let state = { + ...discoveredState, + routeTopology: initialRouteTopology ?? discoveredState.routeTopology, + }; + let closed = false; + let rescanTimer: ReturnType | undefined; + let rescanQueue = Promise.resolve(); + const directoryWatchers = new Map(); + + const touchRestartMarker = async (): Promise => { + await mkdir(dirname(restartMarkerPath), { recursive: true }); + await writeFile(restartMarkerPath, String(Date.now())); + }; + + const closeRemovedDirectoryWatchers = ( + nextDirectories: Set + ): void => { + for (const [directory, watcher] of directoryWatchers) { + if (!nextDirectories.has(directory)) { + watcher.close(); + directoryWatchers.delete(directory); + } + } + }; + + const watchNewDirectories = (nextDirectories: Set): void => { + for (const directory of nextDirectories) { + if (directoryWatchers.has(directory)) { + continue; + } + try { + let watcher: DirectoryWatcher; + watcher = watchDirectoryOverride(directory, scheduleRescan, error => { + if (directoryWatchers.get(directory) === watcher) { + watcher.close(); + directoryWatchers.delete(directory); + } + onError(error); + }); + directoryWatchers.set(directory, watcher); + } catch (error) { + onError(error); + } + } + }; + + const syncDirectoryWatchers = (nextDirectories: Set): void => { + closeRemovedDirectoryWatchers(nextDirectories); + watchNewDirectories(nextDirectories); + }; + + const applyNextState = async (nextState: RouteDirectoryState) => { + if (closed) { + return; + } + syncDirectoryWatchers(nextState.directories); + if (!areSetsEqual(state.routeTopology, nextState.routeTopology)) { + if (onRouteTopologyChange) { + // This is a notification boundary, not part of the rescan + // transaction. A custom-server callback may close this watcher while + // replacing its compiler, so awaiting it here would deadlock close(). + state = nextState; + try { + void Promise.resolve(onRouteTopologyChange()).catch(onError); + } catch (error) { + onError(error); + } + return; + } else { + await touchRestartMarker(); + } + if (closed) { + return; + } + state = nextState; + return; + } + state = nextState; + }; + + const runRescan = async (): Promise => { + if (closed) { + return; + } + let nextDirectories: Set | undefined; + try { + nextDirectories = await readRouteDirectories(watchDirectory); + if (closed) { + return; + } + const nextState = { + directories: nextDirectories, + routeTopology: await getRouteTopology(), + }; + if (closed) { + return; + } + await applyNextState(nextState); + } catch (error) { + if (nextDirectories && !closed) { + syncDirectoryWatchers(nextDirectories); + } + onError(error); + } + }; + + const rescan = (): Promise => { + rescanQueue = rescanQueue.then(runRescan, runRescan); + return rescanQueue; + }; + + const scheduleRescan = (): void => { + if (rescanTimer) { + clearTimeout(rescanTimer); + } + rescanTimer = setTimeout(() => { + rescanTimer = undefined; + void rescan(); + }, 100); + }; + + try { + await applyNextState(discoveredState); + } catch (error) { + onError(error); + } + + return async () => { + if (closed) { + await rescanQueue; + return; + } + closed = true; + if (rescanTimer) { + clearTimeout(rescanTimer); + } + for (const watcher of directoryWatchers.values()) { + watcher.close(); + } + directoryWatchers.clear(); + await rescanQueue; + for (const watcher of directoryWatchers.values()) { + watcher.close(); + } + directoryWatchers.clear(); + }; +}; diff --git a/src/server-build-plan.ts b/src/server-build-plan.ts new file mode 100644 index 0000000..60dadfd --- /dev/null +++ b/src/server-build-plan.ts @@ -0,0 +1,91 @@ +import { PLUGIN_NAME } from './constants.js'; +import type { ReactRouterDevBuildPlan } from './dev-runtime-artifacts.js'; +import type { Route } from './types.js'; + +export type ReactRouterServerBundleEntry = { + bundleId: string; + entryName: string; +}; + +export type ReactRouterServerBuildPlan = ReactRouterDevBuildPlan & { + serverBundleEntries: ReactRouterServerBundleEntry[]; +}; + +export const createReactRouterServerBuildPlan = ({ + routesByServerBundleId, + serverBuildFile, + defaultEntryName, +}: { + routesByServerBundleId: Record>; + serverBuildFile: string | undefined; + defaultEntryName: string; +}): ReactRouterServerBuildPlan => { + const serverBuildFileBase = (serverBuildFile || 'index.js').replace( + /\.js$/, + '' + ); + const serverBundleEntries = Object.entries(routesByServerBundleId) + .filter(([, bundleRoutes]) => + Boolean(bundleRoutes && Object.keys(bundleRoutes).length > 0) + ) + .map(([bundleId]) => ({ + bundleId, + entryName: `${bundleId}/${serverBuildFileBase}`, + })); + + const reservedNodeEntryNames = new Set([ + 'static/js/app', + 'static/js/entry.server', + defaultEntryName, + ]); + for (const { entryName } of serverBundleEntries) { + if (reservedNodeEntryNames.has(entryName)) { + throw new Error( + `[${PLUGIN_NAME}] Server bundle entry ${JSON.stringify(entryName)} conflicts with a reserved node entry.` + ); + } + reservedNodeEntryNames.add(entryName); + } + + return { + defaultEntryName, + entryNames: [ + defaultEntryName, + ...serverBundleEntries.map(({ entryName }) => entryName), + ], + serverBundleEntries, + }; +}; + +export const createReactRouterNodeEntries = ({ + hasServerApp, + isBuild, + serverAppPath, + entryServerPath, + defaultEntryName, + serverBundleEntries, +}: { + hasServerApp: boolean; + isBuild: boolean; + serverAppPath: string; + entryServerPath: string; + defaultEntryName: string; + serverBundleEntries: readonly ReactRouterServerBundleEntry[]; +}): Record => { + const entries: Record = { + 'static/js/app': hasServerApp + ? serverAppPath + : 'virtual/react-router/server-build', + 'static/js/entry.server': entryServerPath, + }; + + if (hasServerApp && !isBuild) { + entries[defaultEntryName] = 'virtual/react-router/server-build'; + } + + for (const { bundleId, entryName } of serverBundleEntries) { + entries[entryName] = `virtual/react-router/server-build-${bundleId}`; + } + + return entries; +}; diff --git a/src/server-utils.ts b/src/server-utils.ts index 43d28f5..c189b83 100644 --- a/src/server-utils.ts +++ b/src/server-utils.ts @@ -1,4 +1,5 @@ import { resolve } from 'pathe'; +import type { ServerBuild } from 'react-router'; import type { Route } from './types.js'; /** @@ -105,30 +106,28 @@ const RESOLVABLE_BUILD_EXPORTS = new Set([ 'ssr', ]); -const isPromiseLike = (value: unknown): value is Promise => - typeof (value as Promise)?.then === 'function'; +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} -export const normalizeBuildModule = >( - build: T -): T => { - if (!build || typeof build !== 'object') { - return build; - } - if ( - 'default' in build && - Object.keys(build).length === 1 && - typeof build.default === 'object' && - build.default - ) { - return build.default as T; - } - return build; -}; +function isPromiseLike(value: unknown): value is PromiseLike { + return isRecord(value) && typeof value.then === 'function'; +} + +function isRouteDiscovery(value: unknown): boolean { + return ( + isRecord(value) && + (value.mode === 'initial' || + (value.mode === 'lazy' && + (value.manifestPath === undefined || + typeof value.manifestPath === 'string'))) + ); +} -export const resolveBuildExports = async >( - build: T -): Promise => { - const resolved: Record = { ...build }; +async function resolveBuildExports( + build: Record +): Promise> { + const resolved = { ...build }; for (const key of Object.keys(build)) { if (!RESOLVABLE_BUILD_EXPORTS.has(key)) { continue; @@ -143,7 +142,69 @@ export const resolveBuildExports = async >( resolved[key] = await value; } } - return resolved as T; -}; + return resolved; +} + +function isServerBuild(value: unknown): value is ServerBuild { + return Boolean( + isRecord(value) && + isRecord(value.entry) && + isRecord(value.entry.module) && + typeof value.entry.module.default === 'function' && + isRecord(value.routes) && + isRecord(value.assets) && + typeof value.assetsBuildDirectory === 'string' && + (value.basename === undefined || typeof value.basename === 'string') && + isRecord(value.future) && + typeof value.isSpaMode === 'boolean' && + Array.isArray(value.prerender) && + typeof value.publicPath === 'string' && + isRouteDiscovery(value.routeDiscovery) && + typeof value.ssr === 'boolean' + ); +} + +async function resolveServerBuildCandidate( + candidate: unknown +): Promise { + if (!isRecord(candidate)) { + return undefined; + } + const resolved = await resolveBuildExports(candidate); + return isServerBuild(resolved) ? resolved : undefined; +} + +export async function resolveServerBuildModule( + buildModule: unknown, + source: string +): Promise { + const moduleValue = await buildModule; + const candidates = [() => moduleValue]; + if (isRecord(moduleValue)) { + if ('default' in moduleValue) { + candidates.push(() => moduleValue.default); + } + if ('module.exports' in moduleValue) { + candidates.push(() => moduleValue['module.exports']); + } + } + + for (const getCandidate of candidates) { + const candidate = await getCandidate(); + const serverBuild = await resolveServerBuildCandidate(candidate); + if (serverBuild) { + return serverBuild; + } + } + throw new Error( + `[rsbuild-plugin-react-router] ${source} did not contain a valid React Router ServerBuild.` + ); +} + +export function resolveReactRouterServerBuild( + buildModule: unknown +): Promise { + return resolveServerBuildModule(buildModule, 'Imported module'); +} export { generateServerBuild }; diff --git a/src/templates/entry.server.tsx b/src/templates/entry.server.tsx index c0e202c..aecef83 100644 --- a/src/templates/entry.server.tsx +++ b/src/templates/entry.server.tsx @@ -30,11 +30,17 @@ export default function handleRequest( (userAgent && isbot(userAgent)) || routerContext.isSpaMode ? 'onAllReady' : 'onShellReady'; + let abortDelay: ReturnType | undefined; + let ready = false; const { pipe, abort } = renderToPipeableStream( , { [readyOption]() { + ready = true; + if (readyOption === 'onAllReady' && abortDelay) { + clearTimeout(abortDelay); + } shellRendered = true; const body = new PassThrough(); const stream = createReadableStreamFromReadable(body); @@ -62,6 +68,9 @@ export default function handleRequest( } ); - setTimeout(abort, ABORT_DELAY); + abortDelay = setTimeout(abort, ABORT_DELAY); + if (readyOption === 'onAllReady' && ready) { + clearTimeout(abortDelay); + } }); } diff --git a/src/typegen.ts b/src/typegen.ts new file mode 100644 index 0000000..de10f5e --- /dev/null +++ b/src/typegen.ts @@ -0,0 +1,49 @@ +import type { RsbuildPluginAPI } from '@rsbuild/core'; +import type { ResultPromise } from 'execa'; + +export const registerReactRouterTypegen = (api: RsbuildPluginAPI): void => { + let typegenProcess: ResultPromise | undefined; + + api.onBeforeStartDevServer(async () => { + if (typegenProcess) { + return; + } + const { execa } = await import('execa'); + const process = execa( + 'npx', + ['--yes', 'react-router', 'typegen', '--watch'], + { + stdio: 'inherit', + detached: false, + cleanup: true, + } + ); + typegenProcess = process; + process + .catch(() => { + // Ignore errors when the process is killed on server shutdown. + }) + .finally(() => { + if (typegenProcess === process) { + typegenProcess = undefined; + } + }); + }); + + api.onCloseDevServer(async () => { + const process = typegenProcess; + typegenProcess = undefined; + if (!process) { + return; + } + process.kill('SIGTERM'); + await process.catch(() => undefined); + }); + + api.onBeforeBuild(async () => { + const { execa } = await import('execa'); + await execa('npx', ['--yes', 'react-router', 'typegen'], { + stdio: 'inherit', + }); + }); +}; diff --git a/src/types.ts b/src/types.ts index a8d3fca..5d2fe1c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import type { RsbuildConfig } from '@rsbuild/core'; + export type Route = { id: string; parentId?: string; @@ -27,19 +29,34 @@ export type PluginOptions = { * Federation mode configuration */ federation?: boolean; -}; -/** - * Arguments passed to transform functions - */ -export type TransformArgs = { - code: string; - resource: string; - resourcePath: string; - context?: string | null; - environment?: { - name: string; - }; + /** + * Rsbuild dev-only lazy compilation behavior. + * @default false + */ + lazyCompilation?: NonNullable['lazyCompilation']; + + /** + * Emit structured React Router plugin timing logs. + * @default false + */ + logPerformance?: boolean; + + /** + * Run route transforms in a worker-thread pool. + * Pass `false` to disable or an integer to override the default worker count. + * @default true. The default uses available CPU cores minus 2, capped at 4 + * workers, with 3-4 core machines limited to 1 worker. + */ + parallelTransforms?: false | number; + + /** + * Called when the route graph changes during development. + * Programmatic/custom servers can use this to recreate their Rsbuild server; + * the CLI uses its built-in reload-server watcher when this is omitted. This + * notification is not awaited, so it may safely close the current server. + */ + onRouteTopologyChange?: () => void | Promise; }; export type RouteManifestItem = Omit & { diff --git a/src/virtual-modules.ts b/src/virtual-modules.ts new file mode 100644 index 0000000..9fe3a78 --- /dev/null +++ b/src/virtual-modules.ts @@ -0,0 +1,30 @@ +const VIRTUAL_MODULE_PREFIX = 'virtual/react-router/'; + +export const getVirtualModuleFilePath = (moduleId: string): string => { + if (!moduleId.startsWith(VIRTUAL_MODULE_PREFIX)) { + throw new Error( + `Virtual module id must start with ${JSON.stringify(VIRTUAL_MODULE_PREFIX)}: ${moduleId}` + ); + } + + const relativeId = moduleId.slice(VIRTUAL_MODULE_PREFIX.length); + const segments = relativeId.split('/'); + if ( + !relativeId || + segments.some(segment => !segment || segment === '.' || segment === '..') + ) { + throw new Error(`Invalid virtual module id: ${moduleId}`); + } + + return `node_modules/${moduleId}.js`; +}; + +export const mapVirtualModules = ( + modules: Record +): Record => + Object.fromEntries( + Object.entries(modules).map(([moduleId, contents]) => [ + getVirtualModuleFilePath(moduleId), + contents, + ]) + ); diff --git a/src/warnings/warn-on-client-source-maps.ts b/src/warnings/warn-on-client-source-maps.ts index e595bb9..25c818a 100644 --- a/src/warnings/warn-on-client-source-maps.ts +++ b/src/warnings/warn-on-client-source-maps.ts @@ -2,13 +2,14 @@ import type { NormalizedConfig } from '@rsbuild/core'; type Warn = (message: string) => void; type ToolsRspackConfig = NonNullable['rspack']; +type SourceMapConfigObject = { js?: unknown }; function isProdBuild(mode?: string): boolean { // Prefer Rsbuild's normalized `mode` (explicit) and fall back to NODE_ENV. return mode === 'production' || process.env.NODE_ENV === 'production'; } -function isSourceMapEnabled(value: unknown): boolean { +export function isSourceMapEnabled(value: unknown): boolean { // Rsbuild normalizes `output.sourceMap` into either: // - boolean // - { js?: devtool; css: boolean } @@ -16,7 +17,7 @@ function isSourceMapEnabled(value: unknown): boolean { if (value === false || value == null) return false; if (typeof value === 'string') return true; if (typeof value === 'object') { - const js = (value as any).js; + const js = (value as SourceMapConfigObject).js; // Any truthy devtool string/object means source maps are on for JS. return Boolean(js); } @@ -30,8 +31,7 @@ function isDevtoolSourceMap(value: unknown): boolean { return value.includes('source-map'); } // Unknown object shape - treat as enabled to be safe. - if (typeof value === 'object') return true; - return false; + return typeof value === 'object'; } export function getClientSourceMapSetting( diff --git a/src/yuku.ts b/src/yuku.ts new file mode 100644 index 0000000..18a9ccc --- /dev/null +++ b/src/yuku.ts @@ -0,0 +1,67 @@ +import { + parse as yukuParse, + walk, + type ParseOptions, + type ParseResult, +} from 'yuku-parser'; +import type { Rspack } from '@rsbuild/core'; +import { print } from 'yuku-codegen'; + +export const parse = ( + code: string, + options: ParseOptions = {} +): ParseResult => { + const result = yukuParse(code, { + ...options, + sourceType: options.sourceType ?? 'module', + lang: options.lang ?? 'tsx', + attachComments: options.attachComments ?? true, + }); + const errors = result.diagnostics.filter( + diagnostic => diagnostic.severity === 'error' + ); + if (errors.length > 0) { + throw new Error(errors.map(error => error.message).join('\n')); + } + return result; +}; + +export const traverse: typeof walk = walk; + +export const generate = ( + ast: ParseResult | { type: 'Program' }, + options: { + sourceMaps?: boolean; + filename?: string; + sourceFileName?: string; + } = {} +): { code: string; map: Rspack.RawSourceMap | null } => { + const result = 'program' in ast ? ast : { program: ast, lineStarts: [] }; + const generated = print(result.program as Parameters[0], { + comments: true, + sourceMaps: options.sourceMaps + ? { + lineStarts: result.lineStarts, + file: options.filename, + sourceFileName: options.sourceFileName, + } + : undefined, + }); + if (generated.errors.length > 0) { + throw new Error(generated.errors.map(error => error.message).join('\n')); + } + const map = generated.map + ? { + ...generated.map, + file: generated.map.file ?? options.filename ?? '', + sourceRoot: generated.map.sourceRoot ?? undefined, + sourcesContent: + generated.map.sourcesContent?.map(source => source ?? '') ?? + undefined, + } + : null; + + return { code: generated.code, map }; +}; + +export type { ParseResult }; diff --git a/tests/benchmark-fixture.test.ts b/tests/benchmark-fixture.test.ts index 5f9815b..c66e0ac 100644 --- a/tests/benchmark-fixture.test.ts +++ b/tests/benchmark-fixture.test.ts @@ -1,4 +1,10 @@ -import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { + existsSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { spawnSync } from 'node:child_process'; @@ -96,14 +102,12 @@ describe('benchmark fixture generator', () => { root, routeCount: 1, variant: 'ssr-esm', - parallelTransforms: { maxWorkers: 3 }, + parallelTransforms: 3, }); const rsbuildConfig = readFileSync(join(root, 'rsbuild.config.mjs'), 'utf8'); - expect(result.parallelTransforms).toEqual({ maxWorkers: 3 }); - expect(rsbuildConfig).toContain( - 'parallelTransforms: { maxWorkers: 3 },' - ); + expect(result.parallelTransforms).toBe(3); + expect(rsbuildConfig).toContain('parallelTransforms: 3,'); } finally { rmSync(root, { recursive: true, force: true }); } @@ -369,4 +373,38 @@ describe('benchmark fixture generator', () => { expect(result.stderr).toContain('No benchmarks matched filter "missing".'); expect(result.stderr).not.toContain('Unknown profile "large"'); }); + + it('rejects mixed benchmark modes in CI reports', () => { + const root = mkdtempSync(join(tmpdir(), 'rr-benchmark-report-')); + try { + const base = join(root, 'base.json'); + const head = join(root, 'head.json'); + writeFileSync( + base, + JSON.stringify({ mode: 'build', benchmarks: [] }), + 'utf8' + ); + writeFileSync( + head, + JSON.stringify({ mode: 'dev', benchmarks: [] }), + 'utf8' + ); + + const result = spawnSync( + process.execPath, + ['scripts/report-benchmark-ci.mjs', '--base', base, '--head', head], + { + cwd: process.cwd(), + encoding: 'utf8', + } + ); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + 'Cannot compare benchmark results with different modes: base=build, head=dev.' + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); }); diff --git a/tests/bounded-cache.test.ts b/tests/bounded-cache.test.ts new file mode 100644 index 0000000..338b9ed --- /dev/null +++ b/tests/bounded-cache.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from '@rstest/core'; +import { setBoundedCacheEntry } from '../src/bounded-cache'; + +describe('bounded cache helpers', () => { + it('evicts the oldest entry only when inserting past the maximum size', () => { + const cache = new Map([ + ['first', 1], + ['second', 2], + ]); + + setBoundedCacheEntry(cache, 'second', 22, 2); + expect([...cache.entries()]).toEqual([ + ['first', 1], + ['second', 22], + ]); + + setBoundedCacheEntry(cache, 'third', 3, 2); + expect([...cache.entries()]).toEqual([ + ['second', 22], + ['third', 3], + ]); + }); + + it('clears entries when the maximum size is not positive', () => { + const cache = new Map([['first', 1]]); + + setBoundedCacheEntry(cache, 'second', 2, 0); + + expect([...cache.entries()]).toEqual([]); + }); + + it('evicts an undefined oldest key when inserting past the maximum size', () => { + const cache = new Map([ + [undefined, 1], + ['second', 2], + ]); + + setBoundedCacheEntry(cache, 'third', 3, 2); + + expect([...cache.entries()]).toEqual([ + ['second', 2], + ['third', 3], + ]); + }); +}); diff --git a/tests/build-manifest.test.ts b/tests/build-manifest.test.ts index 0dd363e..5361c74 100644 --- a/tests/build-manifest.test.ts +++ b/tests/build-manifest.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from '@rstest/core'; import type { Config } from '../src/react-router-config'; -import { getBuildManifest } from '../src/build-manifest'; +import { + getBuildManifest, + getRoutesByServerBundleId, +} from '../src/build-manifest'; describe('build manifest', () => { it('returns routes only when serverBundles is not configured', async () => { @@ -52,6 +55,9 @@ describe('build manifest', () => { expect(result).toHaveProperty('serverBundles'); expect(result).toHaveProperty('routeIdToServerBundleId'); expect(result?.routes.root.file).toBeDefined(); + const bundleRoutes = getRoutesByServerBundleId(result, routes).bundle_2; + expect(bundleRoutes.root.file).toBe('root.tsx'); + expect(bundleRoutes['routes/about'].file).toBe('routes/about.tsx'); }); it('validates server bundle IDs based on vite environment API flag', async () => { diff --git a/tests/build-output-transforms.test.ts b/tests/build-output-transforms.test.ts new file mode 100644 index 0000000..b080e5b --- /dev/null +++ b/tests/build-output-transforms.test.ts @@ -0,0 +1,152 @@ +import type { TransformDescriptor, TransformHandler } from '@rsbuild/core'; +import { describe, expect, it, rstest } from '@rstest/core'; +import { resolve } from 'pathe'; +import { registerBuildOutputTransforms } from '../src/build-output-transforms'; + +type TransformRegistration = { + descriptor: TransformDescriptor; + handler: TransformHandler; +}; + +const createTransformHarness = () => { + const transforms: TransformRegistration[] = []; + + return { + api: { + processAssets: rstest.fn(), + transform(descriptor: TransformDescriptor, handler: TransformHandler) { + transforms.push({ descriptor, handler }); + }, + }, + transforms, + }; +}; + +const createBaseOptions = ( + transforms: ReturnType +) => { + const appDirectory = resolve('/project/app'); + const routePath = resolve(appDirectory, 'routes/page.tsx'); + + return { + api: transforms.api as never, + resolvedServerOutput: 'module' as const, + performanceProfiler: { + record: (_environment: string, _label: string, _resource: string, run) => + run(), + }, + getLatestServerManifest: () => null, + getLatestServerManifestByBundleId: () => undefined, + routes: { + page: { id: 'page', file: 'routes/page.tsx', path: 'page' }, + }, + pluginOptions: {}, + getClientStats: () => undefined, + appDirectory, + getAssetPrefix: () => '/', + routeChunkOptions: { isBuild: true }, + routeTransformExecutor: { + run: rstest.fn(async task => ({ code: `${task.kind}:${task.code}` })), + close: rstest.fn(async () => undefined), + }, + routeByFilePath: new Map([[routePath, { id: 'page' }]]), + routeChunkConfig: { + splitRouteModules: true, + appDirectory, + rootRouteFile: 'root.tsx', + }, + isBuild: true, + splitRouteModules: true, + ssr: true, + isSpaMode: false, + rootRoutePath: resolve(appDirectory, 'root.tsx'), + routePath, + }; +}; + +const createTransformArgs = ( + routePath: string, + resourceQuery = '', + code = 'export async function loader() {}' +) => + ({ + code, + resource: `${routePath}${resourceQuery}`, + resourcePath: routePath, + resourceQuery, + environment: { + name: 'web', + config: { output: { sourceMap: false } }, + }, + }) as never; + +describe('build output transforms', () => { + it('registers post-order route-module transforms for explicit and queryless route modules', async () => { + const harness = createTransformHarness(); + const options = createBaseOptions(harness); + + registerBuildOutputTransforms(options); + + const routeModuleTransforms = harness.transforms.filter( + transform => transform.descriptor.order === 'post' + ); + + expect(routeModuleTransforms).toHaveLength(2); + expect(routeModuleTransforms[0].descriptor).toMatchObject({ + resourceQuery: /\?react-router-route/, + order: 'post', + }); + expect(routeModuleTransforms[1].descriptor).toMatchObject({ + order: 'post', + }); + expect( + (routeModuleTransforms[1].descriptor.test as (path: string) => boolean)( + options.routePath + ) + ).toBe(true); + + await routeModuleTransforms[0].handler( + createTransformArgs(options.routePath, '?react-router-route') + ); + await routeModuleTransforms[1].handler( + createTransformArgs(options.routePath) + ); + + const run = options.routeTransformExecutor.run; + expect(run).toHaveBeenCalledWith( + expect.objectContaining({ kind: 'routeModule' }) + ); + expect(run).toHaveBeenCalledTimes(2); + }); + + it('does not match queryless route-module transforms for internal route requests', () => { + const harness = createTransformHarness(); + const options = createBaseOptions(harness); + + registerBuildOutputTransforms(options); + + const querylessRouteModuleTransform = harness.transforms.find( + transform => + transform.descriptor.order === 'post' && + typeof transform.descriptor.test === 'function' + ); + + expect(querylessRouteModuleTransform).toBeDefined(); + const predicate = querylessRouteModuleTransform!.descriptor.test as ( + path: string + ) => boolean; + expect(predicate(options.routePath)).toBe(true); + expect(predicate(resolve(options.appDirectory, 'not-a-route.tsx'))).toBe( + false + ); + + const resourceQuery = querylessRouteModuleTransform!.descriptor + .resourceQuery as { not: RegExp }; + expect(resourceQuery.not.test('?__react-router-build-client-route')).toBe( + true + ); + expect(resourceQuery.not.test('?react-router-route')).toBe(true); + expect(resourceQuery.not.test('?route-chunk=clientLoader')).toBe(true); + expect(resourceQuery.not.test('')).toBe(false); + }); +}); diff --git a/tests/client-modules.test.ts b/tests/client-modules.test.ts index 75fc22d..1f952a2 100644 --- a/tests/client-modules.test.ts +++ b/tests/client-modules.test.ts @@ -1,10 +1,43 @@ -import { readFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { resolve } from 'pathe'; import { describe, expect, it } from '@rstest/core'; import { createStubRsbuild } from '@scripts/test-helper'; import { pluginReactRouter } from '../src'; +import { collectClientOnlyStubExportNames } from '../src/route-export-resolution'; describe('client-only module transforms', () => { + const createConditionalClientPackage = async ( + root: string, + packageName: string + ): Promise => { + const packageDirectory = join(root, 'node_modules', packageName); + await mkdir(packageDirectory, { recursive: true }); + await writeFile( + join(packageDirectory, 'package.json'), + JSON.stringify({ + name: packageName, + exports: { + '.': { + import: './esm.js', + require: './cjs.cjs', + }, + }, + type: 'module', + }) + ); + await writeFile( + join(packageDirectory, 'esm.js'), + 'export const esmOnly = true; export const shared = true;' + ); + await writeFile( + join(packageDirectory, 'cjs.cjs'), + 'exports.cjsOnly = true; exports.shared = true;' + ); + return join(packageDirectory, 'esm.js'); + }; + it('stubs exports for .client modules using export *', async () => { const rsbuild = await createStubRsbuild({ rsbuildConfig: {}, @@ -34,4 +67,162 @@ describe('client-only module transforms', () => { expect(result.code).toContain('export const local = undefined;'); expect(result.code).not.toContain('export default undefined;'); }); + + it('uses import conditions for bare export-all modules', async () => { + const root = await mkdtemp(join(tmpdir(), 'rr-client-modules-')); + const resolvedPath = await createConditionalClientPackage( + root, + 'conditional-client-lib' + ); + const resourcePath = join(root, 'app', 'example.client.ts'); + await mkdir(join(root, 'app'), { recursive: true }); + await writeFile(resourcePath, "export * from 'conditional-client-lib';"); + + try { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + const plugin = pluginReactRouter(); + await plugin.setup(rsbuild as any); + + const transformCall = (rsbuild.transform as any).mock.calls.find( + (call: any[]) => call[0].test?.toString().includes('\\.client') + ); + expect(transformCall).toBeDefined(); + + const handler = transformCall?.[1]; + const result = await handler({ + environment: { name: 'node' }, + code: await readFile(resourcePath, 'utf8'), + resourcePath, + resolve( + context: string, + specifier: string, + callback: (error: Error | null, resolved?: string) => void + ) { + expect(context).toBe(join(root, 'app')); + expect(specifier).toBe('conditional-client-lib'); + callback(null, resolvedPath); + }, + }); + + expect(result.code).toContain('export const esmOnly = undefined;'); + expect(result.code).toContain('export const shared = undefined;'); + expect(result.code).not.toContain('cjsOnly'); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + it('uses import conditions in the fallback export-all resolver', async () => { + const root = await mkdtemp(join(tmpdir(), 'rr-client-modules-fallback-')); + await createConditionalClientPackage( + root, + 'fallback-conditional-client-lib' + ); + const resourcePath = join(root, 'app', 'example.client.ts'); + await mkdir(join(root, 'app'), { recursive: true }); + await writeFile( + resourcePath, + "export * from 'fallback-conditional-client-lib';" + ); + + try { + const exportNames = await collectClientOnlyStubExportNames( + await readFile(resourcePath, 'utf8'), + resourcePath + ); + + expect(exportNames).toContain('esmOnly'); + expect(exportNames).toContain('shared'); + expect(exportNames).not.toContain('cjsOnly'); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + it('does not bypass package exports for private export-all subpaths', async () => { + const root = await mkdtemp(join(tmpdir(), 'rr-client-modules-private-')); + const packageDirectory = join(root, 'node_modules', 'private-client-lib'); + await mkdir(packageDirectory, { recursive: true }); + await writeFile( + join(packageDirectory, 'package.json'), + JSON.stringify({ + name: 'private-client-lib', + exports: { + '.': './public.js', + }, + type: 'module', + }) + ); + await writeFile(join(packageDirectory, 'public.js'), 'export const ok = true;'); + await writeFile( + join(packageDirectory, 'private.js'), + 'export const hidden = true;' + ); + const resourcePath = join(root, 'app', 'example.client.ts'); + await mkdir(join(root, 'app'), { recursive: true }); + await writeFile(resourcePath, "export * from 'private-client-lib/private';"); + + try { + await expect( + collectClientOnlyStubExportNames( + await readFile(resourcePath, 'utf8'), + resourcePath + ) + ).rejects.toThrow('private-client-lib/private'); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + it('uses the Rsbuild transform resolver for export-all modules', async () => { + const root = await mkdtemp(join(tmpdir(), 'rr-client-modules-resolve-')); + const appDirectory = join(root, 'app'); + const resourcePath = join(appDirectory, 'example.client.ts'); + const resolvedPath = join(root, 'generated', 'client-exports.ts'); + await mkdir(appDirectory, { recursive: true }); + await mkdir(join(root, 'generated'), { recursive: true }); + await writeFile(resourcePath, "export * from '@client/exports';"); + await writeFile( + resolvedPath, + 'export const fromResolver = true; export const alsoResolver = true;' + ); + + try { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + const plugin = pluginReactRouter(); + await plugin.setup(rsbuild as any); + + const transformCall = (rsbuild.transform as any).mock.calls.find( + (call: any[]) => call[0].test?.toString().includes('\\.client') + ); + expect(transformCall).toBeDefined(); + + const handler = transformCall?.[1]; + const result = await handler({ + environment: { name: 'node' }, + code: await readFile(resourcePath, 'utf8'), + resourcePath, + resolve( + context: string, + specifier: string, + callback: (error: Error | null, resolved?: string) => void + ) { + expect(context).toBe(appDirectory); + expect(specifier).toBe('@client/exports'); + callback(null, resolvedPath); + }, + }); + + expect(result.code).toContain('export const fromResolver = undefined;'); + expect(result.code).toContain('export const alsoResolver = undefined;'); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); }); diff --git a/tests/dev-generation-multi-entry.test.ts b/tests/dev-generation-multi-entry.test.ts new file mode 100644 index 0000000..64e3e7c --- /dev/null +++ b/tests/dev-generation-multi-entry.test.ts @@ -0,0 +1,284 @@ +import type { RsbuildDevServer, Rspack } from '@rsbuild/core'; +import { describe, expect, it, rstest } from '@rstest/core'; +import type { ServerBuild } from 'react-router'; +import { + createReactRouterDevRuntime, + type DevGraphChanges, + type DevGraphIdentity, +} from '../src/dev-generation'; + +const noKnownChanges: DevGraphChanges = { + web: { known: false, files: new Set() }, + node: { known: false, files: new Set() }, +}; + +const identityByCompilation = new WeakMap(); + +const getCompilationIdentity = (compilation: Rspack.Compilation): symbol => { + const existing = identityByCompilation.get(compilation); + if (existing) { + return existing; + } + const identity = Symbol(); + identityByCompilation.set(compilation, identity); + return identity; +}; + +const graphIdentity = ( + webCompilation: Rspack.Compilation, + nodeCompilation: Rspack.Compilation +): DevGraphIdentity => ({ + web: getCompilationIdentity(webCompilation), + node: getCompilationIdentity(nodeCompilation), + nodeWeb: getCompilationIdentity(webCompilation), +}); + +type TestServerBuild = ServerBuild & { marker: string }; + +const createBuild = (marker: string): TestServerBuild => + ({ + entry: { module: { default: () => new Response() } }, + routes: {}, + assets: { routes: {}, version: marker }, + assetsBuildDirectory: '/app/build/client', + basename: '/', + future: {}, + isSpaMode: false, + marker, + prerender: [], + publicPath: '/', + routeDiscovery: { mode: 'initial' }, + ssr: true, + }) as unknown as TestServerBuild; + +const createCompilation = (name: 'web' | 'node') => + ({ + name, + buildDependencies: new Set(), + fileDependencies: new Set(), + contextDependencies: new Set(), + missingDependencies: new Set(), + }) as unknown as Rspack.Compilation; + +const createStats = (compilation: Rspack.Compilation) => + ({ compilation, hasErrors: () => false }) as Rspack.Stats; + +const createGraphStats = ( + webCompilation: Rspack.Compilation, + nodeCompilation: Rspack.Compilation +) => + ({ + stats: [createStats(webCompilation), createStats(nodeCompilation)], + }) as Rspack.MultiStats; + +describe('React Router multi-entry development generations', () => { + it('publishes and selects a complete multi-entry generation deterministically', async () => { + const defaultEntry = 'static/js/react-router-server-build'; + const bundleEntry = 'bundle/nested/index'; + const rawDefault = createBuild('default-raw'); + const rawBundle = createBuild('bundle-raw'); + const loadBundle = rstest.fn((entryName: string) => + entryName === defaultEntry ? rawDefault : rawBundle + ); + const server = { + environments: { node: { loadBundle } }, + } as unknown as RsbuildDevServer; + const runtime = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: defaultEntry, + // Deliberately list the bundle first: default selection must be + // explicit rather than depend on object insertion order. + entryNames: [bundleEntry, defaultEntry], + }, + onEvaluationError() {}, + }); + const web = createCompilation('web'); + const node = createCompilation('node'); + + runtime.beginAttempt(); + runtime.captureWeb(web, { + [defaultEntry]: { routes: {}, version: 'default-web' }, + [bundleEntry]: { routes: {}, version: 'bundle-web' }, + }); + await runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + await expect(runtime.load()).resolves.toMatchObject({ + marker: 'default-raw', + assets: { version: 'default-web' }, + }); + await expect(runtime.load(bundleEntry)).resolves.toMatchObject({ + marker: 'bundle-raw', + assets: { version: 'bundle-web' }, + }); + await expect(runtime.load('missing/entry')).rejects.toThrow( + 'not part of this development server build plan' + ); + expect(rawDefault.assets).toMatchObject({ version: 'default-raw' }); + expect(rawBundle.assets).toMatchObject({ version: 'bundle-raw' }); + }); + + it('keeps every last-good entry while a new multi-entry candidate is incomplete', async () => { + const defaultEntry = 'static/js/app'; + const bundleEntry = 'bundle/index'; + let generation = 'a'; + let resolveBundle!: (build: ServerBuild) => void; + const loadBundle = rstest.fn((entryName: string) => { + if (generation === 'b' && entryName === bundleEntry) { + return new Promise(resolve => { + resolveBundle = resolve; + }); + } + return createBuild(`${entryName}-${generation}`); + }); + const server = { + environments: { node: { loadBundle } }, + } as unknown as RsbuildDevServer; + const runtime = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: defaultEntry, + entryNames: [defaultEntry, bundleEntry], + }, + onEvaluationError() {}, + }); + const webA = createCompilation('web'); + const nodeA = createCompilation('node'); + runtime.beginAttempt(); + runtime.captureWeb(webA, { + [defaultEntry]: { routes: {}, version: 'default-a' }, + [bundleEntry]: { routes: {}, version: 'bundle-a' }, + }); + await runtime.finishAttempt( + createGraphStats(webA, nodeA), + noKnownChanges, + graphIdentity(webA, nodeA) + ); + const committedDefault = await runtime.load(); + const committedBundle = await runtime.load(bundleEntry); + + generation = 'b'; + const webB = createCompilation('web'); + const nodeB = createCompilation('node'); + runtime.beginAttempt(); + runtime.captureWeb(webB, { + [defaultEntry]: { routes: {}, version: 'default-b' }, + [bundleEntry]: { routes: {}, version: 'bundle-b' }, + }); + const finishing = runtime.finishAttempt( + createGraphStats(webB, nodeB), + noKnownChanges, + graphIdentity(webB, nodeB) + ); + await Promise.resolve(); + + expect(await runtime.load()).toBe(committedDefault); + expect(await runtime.load(bundleEntry)).toBe(committedBundle); + + resolveBundle(createBuild(`${bundleEntry}-b`)); + await finishing; + await expect(runtime.load()).resolves.toMatchObject({ + marker: `${defaultEntry}-b`, + assets: { version: 'default-b' }, + }); + await expect(runtime.load(bundleEntry)).resolves.toMatchObject({ + marker: `${bundleEntry}-b`, + assets: { version: 'bundle-b' }, + }); + }); + + it('rejects a multi-entry candidate atomically when any entry fails', async () => { + const defaultEntry = 'static/js/app'; + const bundleEntry = 'bundle/index'; + let generation = 'a'; + const loadBundle = rstest.fn((entryName: string) => { + if (generation === 'b' && entryName === bundleEntry) { + throw new Error('bundle evaluation failed'); + } + return createBuild(`${entryName}-${generation}`); + }); + const errors: Error[] = []; + const server = { + environments: { node: { loadBundle } }, + } as unknown as RsbuildDevServer; + const runtime = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: defaultEntry, + entryNames: [defaultEntry, bundleEntry], + }, + onEvaluationError: error => errors.push(error), + }); + const webA = createCompilation('web'); + const nodeA = createCompilation('node'); + runtime.beginAttempt(); + runtime.captureWeb(webA, { + [defaultEntry]: { routes: {}, version: 'default-a' }, + [bundleEntry]: { routes: {}, version: 'bundle-a' }, + }); + await runtime.finishAttempt( + createGraphStats(webA, nodeA), + noKnownChanges, + graphIdentity(webA, nodeA) + ); + const committedDefault = await runtime.load(); + const committedBundle = await runtime.load(bundleEntry); + + generation = 'b'; + const webB = createCompilation('web'); + const nodeB = createCompilation('node'); + runtime.beginAttempt(); + runtime.captureWeb(webB, { + [defaultEntry]: { routes: {}, version: 'default-b' }, + [bundleEntry]: { routes: {}, version: 'bundle-b' }, + }); + await runtime.finishAttempt( + createGraphStats(webB, nodeB), + noKnownChanges, + graphIdentity(webB, nodeB) + ); + + expect(await runtime.load()).toBe(committedDefault); + expect(await runtime.load(bundleEntry)).toBe(committedBundle); + expect(errors.at(-1)?.message).toContain('bundle evaluation failed'); + expect(committedDefault.assets).toMatchObject({ version: 'default-a' }); + expect(committedBundle.assets).toMatchObject({ version: 'bundle-a' }); + }); + + it('rejects an initial generation missing a required entry manifest', async () => { + const defaultEntry = 'static/js/app'; + const bundleEntry = 'bundle/index'; + const server = { + environments: { + node: { loadBundle: () => createBuild('raw') }, + }, + } as unknown as RsbuildDevServer; + const runtime = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: defaultEntry, + entryNames: [defaultEntry, bundleEntry], + }, + onEvaluationError() {}, + }); + const web = createCompilation('web'); + const node = createCompilation('node'); + runtime.beginAttempt(); + runtime.captureWeb(web, { + [defaultEntry]: { routes: {}, version: 'default-only' }, + }); + const waiting = runtime.load(); + + await runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + await expect(waiting).rejects.toThrow('has no matching web manifest'); + }); +}); diff --git a/tests/dev-generation.test.ts b/tests/dev-generation.test.ts new file mode 100644 index 0000000..9af9446 --- /dev/null +++ b/tests/dev-generation.test.ts @@ -0,0 +1,1240 @@ +import type { RsbuildDevServer, Rspack } from '@rsbuild/core'; +import { describe, expect, it, rstest } from '@rstest/core'; +import type { ServerBuild } from 'react-router'; +import { loadReactRouterServerBuild } from '../src'; +import { + createReactRouterDevRuntime, + registerReactRouterDevRuntime, + unregisterReactRouterDevRuntime, + type DevGraphChanges, + type DevGraphIdentity, + type ReactRouterDevManifest, + type ReactRouterDevRuntime, +} from '../src/dev-generation'; + +const noKnownChanges: DevGraphChanges = { + web: { known: false, files: new Set() }, + node: { known: false, files: new Set() }, +}; + +const identityByCompilation = new WeakMap(); + +const getCompilationIdentity = (compilation: Rspack.Compilation): symbol => { + const existing = identityByCompilation.get(compilation); + if (existing) { + return existing; + } + const identity = Symbol(); + identityByCompilation.set(compilation, identity); + return identity; +}; + +const graphIdentity = ( + webCompilation: Rspack.Compilation, + nodeCompilation: Rspack.Compilation, + nodeWebCompilation: Rspack.Compilation = webCompilation +): DevGraphIdentity => ({ + web: getCompilationIdentity(webCompilation), + node: getCompilationIdentity(nodeCompilation), + nodeWeb: getCompilationIdentity(nodeWebCompilation), +}); + +type TestServerBuild = ServerBuild & { marker: string }; + +const createBuild = ( + marker: string, + routeIds = ['routes/about', 'routes/home'] +): TestServerBuild => + ({ + entry: { module: { default: () => new Response() } }, + routes: Object.fromEntries( + routeIds.map(routeId => [ + routeId, + { module: { default: () => null } }, + ]) + ), + assets: { routes: {}, version: marker }, + assetsBuildDirectory: '/app/build/client', + basename: '/', + future: {}, + isSpaMode: false, + marker, + prerender: [], + publicPath: '/', + routeDiscovery: { mode: 'initial' }, + ssr: true, + }) as unknown as TestServerBuild; + +const createRouteManifest = ( + id: string, + css: string[], + imports: string[] = [] +): ReactRouterDevManifest['routes'][string] => ({ + id, + module: `/${id}.js`, + hasAction: false, + hasLoader: false, + hasClientAction: false, + hasClientLoader: false, + hasClientMiddleware: false, + hasDefaultExport: true, + hasErrorBoundary: false, + imports, + css, +}); + +const createDevManifest = ( + version: string, + css: { + entry?: string[]; + routes?: Record; + routeImports?: Record; + } = {} +): ReactRouterDevManifest => ({ + version, + url: '/manifest', + entry: { module: '/entry.js', imports: [], css: css.entry ?? [] }, + routes: Object.fromEntries( + Object.entries(css.routes ?? {}).map(([id, routeCss]) => [ + id, + createRouteManifest(id, routeCss, css.routeImports?.[id]), + ]) + ), +}); + +const createCompilation = ( + name: 'web' | 'node', + dependencies: { + builds?: string[]; + files?: string[]; + contexts?: string[]; + missing?: string[]; + } = {} +) => + ({ + name, + buildDependencies: new Set(dependencies.builds), + fileDependencies: new Set(dependencies.files), + contextDependencies: new Set(dependencies.contexts), + missingDependencies: new Set(dependencies.missing), + }) as unknown as Rspack.Compilation; + +const createStats = (compilation: Rspack.Compilation, hasErrors = false) => + ({ + compilation, + hasErrors: () => hasErrors, + }) as Rspack.Stats; + +const createGraphStats = ( + webCompilation: Rspack.Compilation, + nodeCompilation: Rspack.Compilation, + errors: { web?: boolean; node?: boolean } = {} +) => + ({ + stats: [ + createStats(webCompilation, errors.web), + createStats(nodeCompilation, errors.node), + ], + }) as Rspack.MultiStats; + +const captureWeb = ( + runtime: ReactRouterDevRuntime, + compilation: Rspack.Compilation, + marker: string, + css?: Parameters[1] +) => { + runtime.captureWeb(compilation, { + 'static/js/app': createDevManifest(marker, css), + }); +}; + +const createHarness = ( + loadBundle: (entryName: string) => Promise | unknown, + options: { onCssAssetOwnershipChanged?: () => void } = {} +) => { + const errors: Error[] = []; + const warnings: string[] = []; + const loadBundleMock = rstest.fn(loadBundle); + const server = { + environments: { + node: { loadBundle: loadBundleMock }, + }, + } as unknown as RsbuildDevServer; + const runtime = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: 'static/js/app', + entryNames: ['static/js/app'], + }, + onEvaluationError: error => errors.push(error), + onCssAssetOwnershipChanged: options.onCssAssetOwnershipChanged, + onWarning: warning => warnings.push(warning), + }); + return { errors, loadBundle: loadBundleMock, runtime, server, warnings }; +}; + +describe('React Router development runtime', () => { + it('publishes a validated server build pinned to its exact web manifest', async () => { + const rawBuild = createBuild('raw'); + const { runtime } = createHarness(() => rawBuild); + const web = createCompilation('web'); + const node = createCompilation('node'); + const manifest = createDevManifest('web-1'); + + runtime.beginAttempt(); + runtime.captureWeb(web, { 'static/js/app': manifest }); + const waiting = runtime.load(); + await runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + const committed = await waiting; + expect(committed).not.toBe(rawBuild); + expect(committed.assets).toEqual(manifest); + expect(committed.assets).not.toBe(manifest); + }); + + it('detects retargeted route css ownership', async () => { + const onCssAssetOwnershipChanged = rstest.fn(); + const { runtime } = createHarness(() => createBuild('build'), { + onCssAssetOwnershipChanged, + }); + const firstWeb = createCompilation('web'); + const firstNode = createCompilation('node'); + + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'about-css', { + routes: { 'routes/about': ['/assets/shared.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(firstWeb, firstNode), + noKnownChanges, + graphIdentity(firstWeb, firstNode) + ); + + const nextWeb = createCompilation('web'); + const nextNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, nextWeb, 'home-css', { + routes: { 'routes/home': ['/assets/shared.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(nextWeb, nextNode), + noKnownChanges, + graphIdentity(nextWeb, nextNode) + ); + + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + }); + + it('notifies after a committed web manifest removes route or entry css ownership', async () => { + const onCssAssetOwnershipChanged = rstest.fn(); + const { runtime } = createHarness(() => createBuild('build'), { + onCssAssetOwnershipChanged, + }); + const firstWeb = createCompilation('web'); + const firstNode = createCompilation('node'); + + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'with-css', { + entry: ['/assets/entry.css'], + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(firstWeb, firstNode), + noKnownChanges, + graphIdentity(firstWeb, firstNode) + ); + expect(onCssAssetOwnershipChanged).not.toHaveBeenCalled(); + + const removedRouteCssWeb = createCompilation('web'); + const secondNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, removedRouteCssWeb, 'without-route-css', { + entry: ['/assets/entry.css'], + }); + await runtime.finishAttempt( + createGraphStats(removedRouteCssWeb, secondNode), + noKnownChanges, + graphIdentity(removedRouteCssWeb, secondNode) + ); + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + + const removedEntryCssWeb = createCompilation('web'); + const thirdNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, removedEntryCssWeb, 'without-entry-css'); + await runtime.finishAttempt( + createGraphStats(removedEntryCssWeb, thirdNode), + noKnownChanges, + graphIdentity(removedEntryCssWeb, thirdNode) + ); + + expect(onCssAssetOwnershipChanged).toHaveBeenCalledTimes(2); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'without-entry-css' }, + }); + }); + + it('publishes css-only removals when the route file overlaps node dependencies', async () => { + const routePath = '/app/routes/about.tsx'; + const onCssAssetOwnershipChanged = rstest.fn(); + const { runtime, warnings } = createHarness(() => createBuild('build'), { + onCssAssetOwnershipChanged, + }); + const firstWeb = createCompilation('web'); + const node = createCompilation('node', { files: [routePath] }); + + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'with-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(firstWeb, node), + noKnownChanges, + graphIdentity(firstWeb, node) + ); + + const removedCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, removedCssWeb, 'without-css', { + routes: { 'routes/about': [] }, + }); + await runtime.finishAttempt( + createGraphStats(removedCssWeb, node), + { + web: { known: true, files: new Set([routePath]) }, + node: { known: false, files: new Set() }, + }, + graphIdentity(removedCssWeb, node) + ); + + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + expect(warnings).toEqual([]); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'without-css' }, + }); + }); + + it('keeps normal hmr for css-only additions, stable css assets, and node-only compiles', async () => { + const routePath = '/app/routes/about.tsx'; + const onCssAssetOwnershipChanged = rstest.fn(); + const { runtime } = createHarness(() => createBuild('build'), { + onCssAssetOwnershipChanged, + }); + const firstWeb = createCompilation('web'); + const firstNode = createCompilation('node', { files: [routePath] }); + + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'base'); + await runtime.finishAttempt( + createGraphStats(firstWeb, firstNode), + noKnownChanges, + graphIdentity(firstWeb, firstNode) + ); + + const cssOnlyChange: DevGraphChanges = { + web: { known: true, files: new Set([routePath]) }, + node: { known: false, files: new Set() }, + }; + + const addedCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, addedCssWeb, 'added-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(addedCssWeb, firstNode), + cssOnlyChange, + graphIdentity(addedCssWeb, firstNode) + ); + + const stableCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, stableCssWeb, 'same-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(stableCssWeb, firstNode), + cssOnlyChange, + graphIdentity(stableCssWeb, firstNode) + ); + + const nodeOnly = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(stableCssWeb, nodeOnly), + noKnownChanges, + graphIdentity(stableCssWeb, nodeOnly) + ); + + expect(onCssAssetOwnershipChanged).not.toHaveBeenCalled(); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'same-css' }, + }); + }); + + it('notifies when css ownership is re-added after a removal', async () => { + const onCssAssetOwnershipChanged = rstest.fn(); + const { runtime } = createHarness(() => createBuild('build'), { + onCssAssetOwnershipChanged, + }); + const node = createCompilation('node'); + const cssOnlyChange: DevGraphChanges = { + web: { known: true, files: new Set(['/app/routes/about.tsx']) }, + node: { known: false, files: new Set() }, + }; + + const firstWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'with-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(firstWeb, node), + noKnownChanges, + graphIdentity(firstWeb, node) + ); + + const removedCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, removedCssWeb, 'without-css', { + routes: { 'routes/about': [] }, + }); + await runtime.finishAttempt( + createGraphStats(removedCssWeb, node), + cssOnlyChange, + graphIdentity(removedCssWeb, node) + ); + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + + const contentOnlyWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, contentOnlyWeb, 'without-css-content-edit', { + routes: { 'routes/about': [] }, + }); + await runtime.finishAttempt( + createGraphStats(contentOnlyWeb, node), + cssOnlyChange, + graphIdentity(contentOnlyWeb, node) + ); + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + + const readdedCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, readdedCssWeb, 'readded-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(readdedCssWeb, node), + cssOnlyChange, + graphIdentity(readdedCssWeb, node) + ); + + expect(onCssAssetOwnershipChanged).toHaveBeenCalledTimes(2); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'readded-css' }, + }); + }); + + it('publishes css-only web manifest changes when a node result comes from an older web cycle', async () => { + const onCssAssetOwnershipChanged = rstest.fn(); + const { loadBundle, runtime, warnings } = createHarness( + () => createBuild('build'), + { onCssAssetOwnershipChanged } + ); + const node = createCompilation('node'); + const webOnlyCssChange: DevGraphChanges = { + web: { known: true, files: new Set(['/app/routes/about.tsx']) }, + node: { known: false, files: new Set() }, + }; + const cssOnlyChange: DevGraphChanges = { + web: { known: true, files: new Set(['/app/routes/about.tsx']) }, + node: { known: true, files: new Set(['/app/routes/about.tsx']) }, + }; + + const firstWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'with-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(firstWeb, node), + noKnownChanges, + graphIdentity(firstWeb, node) + ); + + const removedCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, removedCssWeb, 'without-css', { + routes: { 'routes/about': [] }, + }); + await runtime.finishAttempt( + createGraphStats(removedCssWeb, node), + webOnlyCssChange, + graphIdentity(removedCssWeb, node) + ); + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'without-css' }, + }); + + const readdedCssWeb = createCompilation('web'); + const staleNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, readdedCssWeb, 'readded-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(readdedCssWeb, staleNode), + cssOnlyChange, + graphIdentity(readdedCssWeb, staleNode, removedCssWeb) + ); + + expect(onCssAssetOwnershipChanged).toHaveBeenCalledTimes(2); + expect(loadBundle).toHaveBeenCalledOnce(); + expect(warnings).toEqual([]); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'readded-css' }, + }); + }); + + it('publishes re-added css when route imports change with css ownership', async () => { + const onCssAssetOwnershipChanged = rstest.fn(); + const { loadBundle, runtime, warnings } = createHarness( + () => createBuild('build'), + { onCssAssetOwnershipChanged } + ); + const node = createCompilation('node'); + const cssOnlyChange: DevGraphChanges = { + web: { known: true, files: new Set(['/app/routes/about.tsx']) }, + node: { known: true, files: new Set(['/app/routes/about.tsx']) }, + }; + + const firstWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'with-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + routeImports: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(firstWeb, node), + noKnownChanges, + graphIdentity(firstWeb, node) + ); + + const removedCssWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, removedCssWeb, 'without-css', { + routes: { 'routes/about': [] }, + }); + await runtime.finishAttempt( + createGraphStats(removedCssWeb, node), + cssOnlyChange, + graphIdentity(removedCssWeb, node, firstWeb) + ); + expect(onCssAssetOwnershipChanged).toHaveBeenCalledOnce(); + + const readdedCssWeb = createCompilation('web'); + const staleNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, readdedCssWeb, 'readded-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + routeImports: { 'routes/about': ['/assets/about.css'] }, + }); + await runtime.finishAttempt( + createGraphStats(readdedCssWeb, staleNode), + cssOnlyChange, + graphIdentity(readdedCssWeb, staleNode, removedCssWeb) + ); + + expect(onCssAssetOwnershipChanged).toHaveBeenCalledTimes(2); + expect(loadBundle).toHaveBeenCalledOnce(); + expect(warnings).toEqual([]); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'readded-css' }, + }); + }); + + it('rejects initial waiters on evaluation failure and recovers on a new attempt', async () => { + let shouldFail = true; + const { runtime } = createHarness(() => { + if (shouldFail) { + throw new Error('top-level evaluation failed'); + } + return createBuild('recovered'); + }); + const firstWeb = createCompilation('web'); + const firstNode = createCompilation('node'); + + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'first'); + const waiting = runtime.load(); + await runtime.finishAttempt( + createGraphStats(firstWeb, firstNode), + noKnownChanges, + graphIdentity(firstWeb, firstNode) + ); + + await expect(waiting).rejects.toThrow('top-level evaluation failed'); + await expect(runtime.load()).rejects.toThrow('top-level evaluation failed'); + + shouldFail = false; + const nextWeb = createCompilation('web'); + const nextNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, nextWeb, 'recovered'); + const recovery = runtime.load(); + await runtime.finishAttempt( + createGraphStats(nextWeb, nextNode), + noKnownChanges, + graphIdentity(nextWeb, nextNode) + ); + + await expect(recovery).resolves.toMatchObject({ + assets: { version: 'recovered' }, + }); + }); + + it('rejects initial waiters on a fatal compiler failure and recovers', async () => { + const { loadBundle, runtime } = createHarness(() => + createBuild('recovered') + ); + runtime.beginAttempt(); + const waiting = runtime.load(); + + runtime.failAttempt(new Error('fatal compiler failure')); + + await expect(waiting).rejects.toThrow('fatal compiler failure'); + const staleWeb = createCompilation('web'); + const staleNode = createCompilation('node'); + captureWeb(runtime, staleWeb, 'stale'); + await runtime.finishAttempt( + createGraphStats(staleWeb, staleNode), + noKnownChanges, + graphIdentity(staleWeb, staleNode) + ); + expect(loadBundle).not.toHaveBeenCalled(); + + const web = createCompilation('web'); + const node = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'recovered'); + await runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'recovered' }, + }); + }); + + it('rejects objects that are not React Router ServerBuild values', async () => { + const { runtime } = createHarness(() => ({})); + const web = createCompilation('web'); + const node = createCompilation('node'); + + runtime.beginAttempt(); + captureWeb(runtime, web, 'invalid'); + const waiting = runtime.load(); + await runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + await expect(waiting).rejects.toThrow('valid React Router ServerBuild'); + }); + + it('rejects a near-shaped build without a document request handler', async () => { + const invalid = { + ...createBuild('invalid-entry'), + entry: { module: {} }, + }; + const { runtime } = createHarness(() => invalid); + const web = createCompilation('web'); + const node = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'invalid-entry'); + const waiting = runtime.load(); + + await runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + await expect(waiting).rejects.toThrow('valid React Router ServerBuild'); + }); + + it('keeps serving last-good output when a later compilation or evaluation fails', async () => { + let build: unknown = createBuild('first'); + const { runtime } = createHarness(() => build); + const firstWeb = createCompilation('web'); + const firstNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'first'); + await runtime.finishAttempt( + createGraphStats(firstWeb, firstNode), + noKnownChanges, + graphIdentity(firstWeb, firstNode) + ); + const committed = await runtime.load(); + + runtime.beginAttempt(); + const failedWeb = createCompilation('web'); + const failedNode = createCompilation('node'); + captureWeb(runtime, failedWeb, 'compile-error'); + await runtime.finishAttempt( + createGraphStats(failedWeb, failedNode, { node: true }), + noKnownChanges, + graphIdentity(failedWeb, failedNode) + ); + expect(await runtime.load()).toBe(committed); + + build = {}; + runtime.beginAttempt(); + const invalidWeb = createCompilation('web'); + const invalidNode = createCompilation('node'); + captureWeb(runtime, invalidWeb, 'invalid-build'); + await runtime.finishAttempt( + createGraphStats(invalidWeb, invalidNode), + noKnownChanges, + graphIdentity(invalidWeb, invalidNode) + ); + expect(await runtime.load()).toBe(committed); + }); + + it('ignores stale async failure after supersession', async () => { + let resolveEvaluation: ((value: unknown) => void) | undefined; + let rejectEvaluation: ((error: Error) => void) | undefined; + let nextBuild: unknown = createBuild('first'); + const { errors, runtime } = createHarness( + () => + new Promise((resolve, reject) => { + resolveEvaluation = resolve; + rejectEvaluation = reject; + }) + ); + + const firstWeb = createCompilation('web'); + const firstNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'first'); + const firstFinish = runtime.finishAttempt( + createGraphStats(firstWeb, firstNode), + noKnownChanges, + graphIdentity(firstWeb, firstNode) + ); + resolveEvaluation?.(nextBuild); + await firstFinish; + const committed = await runtime.load(); + + const staleWeb = createCompilation('web'); + const staleNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, staleWeb, 'stale'); + const staleFinish = runtime.finishAttempt( + createGraphStats(staleWeb, staleNode), + noKnownChanges, + graphIdentity(staleWeb, staleNode) + ); + + runtime.beginAttempt(); + rejectEvaluation?.(new Error('stale rejection')); + await staleFinish; + + expect(await runtime.load()).toBe(committed); + expect(errors).toEqual([]); + }); + + it('ignores stale async success after a newer attempt commits', async () => { + let staleResolve: ((value: unknown) => void) | undefined; + let load: () => Promise | unknown = () => createBuild('base'); + const { runtime } = createHarness(() => load()); + const baseWeb = createCompilation('web'); + const baseNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, baseWeb, 'base'); + await runtime.finishAttempt( + createGraphStats(baseWeb, baseNode), + noKnownChanges, + graphIdentity(baseWeb, baseNode) + ); + + load = () => + new Promise(resolve => { + staleResolve = resolve; + }); + const staleWeb = createCompilation('web'); + const staleNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, staleWeb, 'stale'); + const staleFinish = runtime.finishAttempt( + createGraphStats(staleWeb, staleNode), + noKnownChanges, + graphIdentity(staleWeb, staleNode) + ); + + load = () => createBuild('newest'); + const newestWeb = createCompilation('web'); + const newestNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, newestWeb, 'newest'); + await runtime.finishAttempt( + createGraphStats(newestWeb, newestNode), + noKnownChanges, + graphIdentity(newestWeb, newestNode) + ); + staleResolve?.(createBuild('stale')); + await staleFinish; + + await expect(runtime.load()).resolves.toMatchObject({ + marker: 'newest', + assets: { version: 'newest' }, + }); + }); + + it('rejects mixed web and node results from overlapping compiler cycles', async () => { + let build = createBuild('base'); + const { runtime, warnings } = createHarness(() => build); + const baseWeb = createCompilation('web'); + const baseNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, baseWeb, 'base'); + await runtime.finishAttempt( + createGraphStats(baseWeb, baseNode), + noKnownChanges, + graphIdentity(baseWeb, baseNode) + ); + const committed = await runtime.load(); + + build = createBuild('node-a'); + const webB = createCompilation('web'); + const nodeA = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, webB, 'web-b'); + await runtime.finishAttempt( + createGraphStats(webB, nodeA), + noKnownChanges, + graphIdentity(webB, nodeA, baseWeb) + ); + + expect(await runtime.load()).toBe(committed); + expect(warnings.at(-1)).toContain('different compiler cycles'); + + build = createBuild('coherent-b'); + const nodeB = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(webB, nodeB), + noKnownChanges, + graphIdentity(webB, nodeB) + ); + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'web-b' }, + }); + }); + + it('keeps initial readiness pending for a transient mixed result', async () => { + const { loadBundle, runtime } = createHarness(() => createBuild('web-b')); + const webA = createCompilation('web'); + const webB = createCompilation('web'); + const nodeA = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, webB, 'web-b'); + const waiting = runtime.load(); + + await runtime.finishAttempt( + createGraphStats(webB, nodeA), + noKnownChanges, + graphIdentity(webB, nodeA, webA) + ); + let published = false; + void waiting.then(() => { + published = true; + }); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(published).toBe(false); + expect(loadBundle).not.toHaveBeenCalled(); + + const nodeB = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(webB, nodeB), + noKnownChanges, + graphIdentity(webB, nodeB) + ); + + await expect(waiting).resolves.toMatchObject({ + marker: 'web-b', + assets: { version: 'web-b' }, + }); + }); + + it('rejects a node result compiled against an unseen web compilation', async () => { + let build = createBuild('base'); + const { loadBundle, runtime, warnings } = createHarness(() => build); + const web = createCompilation('web'); + const baseNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'web-a'); + await runtime.finishAttempt( + createGraphStats(web, baseNode), + noKnownChanges, + graphIdentity(web, baseNode) + ); + const committed = await runtime.load(); + + build = createBuild('node-b'); + const nextNode = createCompilation('node'); + const unseenWeb = createCompilation('web'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(web, nextNode), + { + web: { known: false, files: new Set() }, + node: { known: true, files: new Set(['/app/server-only.ts']) }, + }, + graphIdentity(web, nextNode, unseenWeb) + ); + + expect(await runtime.load()).toBe(committed); + expect(warnings.at(-1)).toContain('different compiler cycles'); + expect(loadBundle).toHaveBeenCalledOnce(); + }); + + it('does not let a stale unchanged callback consume the active attempt', async () => { + let build = createBuild('base'); + const { runtime } = createHarness(() => build); + const baseWeb = createCompilation('web'); + const baseNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, baseWeb, 'base'); + await runtime.finishAttempt( + createGraphStats(baseWeb, baseNode), + noKnownChanges, + graphIdentity(baseWeb, baseNode) + ); + + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(baseWeb, baseNode), + noKnownChanges, + graphIdentity(baseWeb, baseNode) + ); + + build = createBuild('next'); + const nextWeb = createCompilation('web'); + const nextNode = createCompilation('node'); + captureWeb(runtime, nextWeb, 'next'); + await runtime.finishAttempt( + createGraphStats(nextWeb, nextNode), + noKnownChanges, + graphIdentity(nextWeb, nextNode) + ); + + await expect(runtime.load()).resolves.toMatchObject({ + marker: 'next', + assets: { version: 'next' }, + }); + }); + + it('does not publish an intermediate additional compiler pass', async () => { + let build = createBuild('intermediate'); + const { loadBundle, runtime } = createHarness(() => build); + const web = createCompilation('web'); + const intermediateNode = createCompilation('node'); + intermediateNode.needAdditionalPass = true; + runtime.beginAttempt(); + captureWeb(runtime, web, 'web'); + const waiting = runtime.load(); + + await runtime.finishAttempt( + createGraphStats(web, intermediateNode, { node: true }), + noKnownChanges, + graphIdentity(web, intermediateNode) + ); + let published = false; + void waiting.then(() => { + published = true; + }); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(published).toBe(false); + expect(loadBundle).not.toHaveBeenCalled(); + + build = createBuild('final'); + const finalNode = createCompilation('node'); + await runtime.finishAttempt( + createGraphStats(web, finalNode), + noKnownChanges, + graphIdentity(web, finalNode) + ); + await expect(waiting).resolves.toMatchObject({ marker: 'final' }); + }); + + it('commits known server-only changes that do not touch web dependencies', async () => { + let build = createBuild('first'); + const { runtime } = createHarness(() => build); + const web = createCompilation('web', { + files: ['/app/shared.ts'], + contexts: ['/app/routes'], + missing: ['/app/generated.ts'], + }); + const firstNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'web'); + await runtime.finishAttempt( + createGraphStats(web, firstNode), + noKnownChanges, + graphIdentity(web, firstNode) + ); + + build = createBuild('node-2'); + const nextNode = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(web, nextNode), + { + web: { known: false, files: new Set() }, + node: { known: true, files: new Set(['/app/server-only.ts']) }, + }, + graphIdentity(web, nextNode) + ); + + await expect(runtime.load()).resolves.toMatchObject({ + assets: { version: 'web' }, + marker: 'node-2', + }); + }); + + it('captures web dependencies after manifest generation finishes', async () => { + let build = createBuild('first'); + const { loadBundle, runtime, warnings } = createHarness(() => build); + const web = createCompilation('web'); + const firstNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'web'); + web.fileDependencies.add('/app/late.ts'); + await runtime.finishAttempt( + createGraphStats(web, firstNode), + noKnownChanges, + graphIdentity(web, firstNode) + ); + const committed = await runtime.load(); + + build = createBuild('unsafe-node'); + const nextNode = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(web, nextNode), + { + web: { known: false, files: new Set() }, + node: { known: true, files: new Set(['/app/late.ts']) }, + }, + graphIdentity(web, nextNode) + ); + + expect(await runtime.load()).toBe(committed); + expect(loadBundle).toHaveBeenCalledOnce(); + expect(warnings).toHaveLength(1); + }); + + it('treats web build dependencies as unsafe node-only changes', async () => { + let build = createBuild('first'); + const { loadBundle, runtime } = createHarness(() => build); + const web = createCompilation('web', { + builds: ['/app/web-loader.config.ts'], + }); + const firstNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'web'); + await runtime.finishAttempt( + createGraphStats(web, firstNode), + noKnownChanges, + graphIdentity(web, firstNode) + ); + const committed = await runtime.load(); + + build = createBuild('unsafe-node'); + const nextNode = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(web, nextNode), + { + web: { known: false, files: new Set() }, + node: { + known: true, + files: new Set(['/app/web-loader.config.ts']), + }, + }, + graphIdentity(web, nextNode) + ); + + expect(await runtime.load()).toBe(committed); + expect(loadBundle).toHaveBeenCalledOnce(); + }); + + it('treats node build dependencies as unsafe web-only changes', async () => { + const build = createBuild('first'); + const { runtime } = createHarness(() => build); + const firstWeb = createCompilation('web'); + const node = createCompilation('node', { + builds: ['/app/node-loader.config.ts'], + }); + runtime.beginAttempt(); + captureWeb(runtime, firstWeb, 'web-1'); + await runtime.finishAttempt( + createGraphStats(firstWeb, node), + noKnownChanges, + graphIdentity(firstWeb, node) + ); + const committed = await runtime.load(); + + const nextWeb = createCompilation('web'); + runtime.beginAttempt(); + captureWeb(runtime, nextWeb, 'web-2'); + await runtime.finishAttempt( + createGraphStats(nextWeb, node), + { + web: { + known: true, + files: new Set(['/app/node-loader.config.ts']), + }, + node: { known: false, files: new Set() }, + }, + graphIdentity(nextWeb, node) + ); + + expect(await runtime.load()).toBe(committed); + }); + + it('discards ambiguous or overlapping one-sided rebuilds', async () => { + let build = createBuild('first'); + const { errors, runtime, warnings } = createHarness(() => build); + const web = createCompilation('web', { files: ['/app/shared.ts'] }); + const firstNode = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'web'); + await runtime.finishAttempt( + createGraphStats(web, firstNode), + noKnownChanges, + graphIdentity(web, firstNode) + ); + const committed = await runtime.load(); + + build = createBuild('unsafe'); + const unsafeNode = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(web, unsafeNode), + { + web: { known: false, files: new Set() }, + node: { known: true, files: new Set(['/app/shared.ts']) }, + }, + graphIdentity(web, unsafeNode) + ); + + expect(await runtime.load()).toBe(committed); + expect(errors).toEqual([]); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain('last-good'); + + const ambiguousNode = createCompilation('node'); + runtime.beginAttempt(); + await runtime.finishAttempt( + createGraphStats(web, ambiguousNode), + { + web: { known: false, files: new Set() }, + node: { known: true, files: new Set() }, + }, + graphIdentity(web, ambiguousNode) + ); + expect(await runtime.load()).toBe(committed); + }); + + it('does not let cleanup from an old session unregister a replacement', async () => { + const server = { + environments: { node: { loadBundle: () => createBuild('replacement') } }, + } as unknown as RsbuildDevServer; + const first = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: 'static/js/app', + entryNames: ['static/js/app'], + }, + onEvaluationError() {}, + }); + const replacement = createReactRouterDevRuntime({ + server, + buildPlan: { + defaultEntryName: 'static/js/app', + entryNames: ['static/js/app'], + }, + onEvaluationError() {}, + }); + registerReactRouterDevRuntime(server, first); + registerReactRouterDevRuntime(server, replacement); + unregisterReactRouterDevRuntime(server, first); + expect( + Reflect.get( + server, + Symbol.for('rsbuild-plugin-react-router.dev-runtime.v1') + ) + ).toBe(replacement); + + const web = createCompilation('web'); + const node = createCompilation('node'); + replacement.beginAttempt(); + captureWeb(replacement, web, 'replacement'); + await replacement.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + await expect(loadReactRouterServerBuild(server)).resolves.toMatchObject({ + assets: { version: 'replacement' }, + }); + unregisterReactRouterDevRuntime(server, replacement); + expect( + Reflect.get( + server, + Symbol.for('rsbuild-plugin-react-router.dev-runtime.v1') + ) + ).toBeUndefined(); + }); + + it('closes pending waiters and ignores late completions', async () => { + let resolveEvaluation: ((value: unknown) => void) | undefined; + const { errors, runtime } = createHarness( + () => + new Promise(resolve => { + resolveEvaluation = resolve; + }) + ); + const web = createCompilation('web'); + const node = createCompilation('node'); + runtime.beginAttempt(); + captureWeb(runtime, web, 'late'); + const waiting = runtime.load(); + const finishing = runtime.finishAttempt( + createGraphStats(web, node), + noKnownChanges, + graphIdentity(web, node) + ); + + runtime.close(); + resolveEvaluation?.(createBuild('late')); + await finishing; + + await expect(waiting).rejects.toThrow( + 'development server closed before a React Router build was ready' + ); + expect(errors).toEqual([]); + }); + +}); diff --git a/tests/dev-runtime-controller.test.ts b/tests/dev-runtime-controller.test.ts new file mode 100644 index 0000000..b78810b --- /dev/null +++ b/tests/dev-runtime-controller.test.ts @@ -0,0 +1,1085 @@ +import type { + OnAfterCreateCompilerFn, + OnAfterDevCompileFn, + OnBeforeDevCompileFn, + OnBeforeStartDevServerFn, + OnCloseDevServerFn, + RsbuildDevServer, + Rspack, +} from '@rsbuild/core'; +import { describe, expect, it, rstest } from '@rstest/core'; +import type { ServerBuild } from 'react-router'; +import type { ReactRouterDevManifest } from '../src/dev-generation'; +import { createReactRouterDevRuntimeController } from '../src/dev-runtime-controller'; + +type FailedCallback = (error: Error) => void; + +const afterDoneByCompiler = new WeakMap< + Rspack.Compiler, + (stats: Rspack.Stats) => void +>(); + +type TestServerBuild = ServerBuild & { marker: string }; + +const createBuild = ( + marker: string, + routeIds = ['routes/about', 'routes/home'] +): TestServerBuild => + ({ + entry: { module: { default: () => new Response() } }, + routes: Object.fromEntries( + routeIds.map(routeId => [ + routeId, + { module: { default: () => null } }, + ]) + ), + assets: { routes: {}, version: marker }, + assetsBuildDirectory: '/app/build/client', + basename: '/', + future: {}, + isSpaMode: false, + marker, + prerender: [], + publicPath: '/', + routeDiscovery: { mode: 'initial' }, + ssr: true, + }) as unknown as TestServerBuild; + +const createRouteManifest = ( + id: string, + css: string[] +): ReactRouterDevManifest['routes'][string] => ({ + id, + module: `/${id}.js`, + hasAction: false, + hasLoader: false, + hasClientAction: false, + hasClientLoader: false, + hasClientMiddleware: false, + hasDefaultExport: true, + hasErrorBoundary: false, + imports: [], + css, +}); + +const createManifest = ( + version: string, + css: { entry?: string[]; routes?: Record } = {} +) => ({ + 'static/js/app': { + version, + url: '/manifest', + entry: { module: '/entry.js', imports: [], css: css.entry ?? [] }, + routes: Object.fromEntries( + Object.entries(css.routes ?? {}).map(([id, routeCss]) => [ + id, + createRouteManifest(id, routeCss), + ]) + ), + }, +}); + +const createStats = (compilation: Rspack.Compilation): Rspack.Stats => + ({ compilation, hasErrors: () => false }) as Rspack.Stats; + +const createGraphStats = ( + web: Rspack.Compilation, + node: Rspack.Compilation +): Rspack.MultiStats => + ({ stats: [createStats(web), createStats(node)] }) as Rspack.MultiStats; + +const createCompiler = (name: 'web' | 'node') => { + let failed: FailedCallback | undefined; + let invalid: (() => void) | undefined; + let thisCompilation: ((compilation: Rspack.Compilation) => void) | undefined; + const doneTaps: Array<{ + stage: number; + callback: (stats: Rspack.Stats) => void; + }> = []; + const compiler = { + name, + hooks: { + thisCompilation: { + tap(_name: string, callback: typeof thisCompilation) { + thisCompilation = callback; + }, + }, + done: { + tap(options: unknown, callback: (stats: Rspack.Stats) => void) { + const stage = + typeof options === 'object' && + options !== null && + 'stage' in options && + typeof options.stage === 'number' + ? options.stage + : 0; + doneTaps.push({ stage, callback }); + }, + }, + afterDone: { + tap(_name: unknown, callback: (stats: Rspack.Stats) => void) { + afterDoneByCompiler.set(compiler, callback); + }, + }, + failed: { + tap(_name: string, callback: FailedCallback) { + failed = callback; + }, + }, + invalid: { + tap(_name: string, callback: () => void) { + invalid = callback; + }, + }, + }, + } as unknown as Rspack.Compiler; + const compile = (): Rspack.Compilation => { + const compilation = { + name, + compiler, + buildDependencies: new Set(), + fileDependencies: new Set(), + contextDependencies: new Set(), + missingDependencies: new Set(), + } as unknown as Rspack.Compilation; + thisCompilation?.(compilation); + return compilation; + }; + const runDoneTaps = ( + compilation: Rspack.Compilation, + predicate: (stage: number) => boolean + ): void => { + const stats = { compilation } as Rspack.Stats; + for (const tap of doneTaps + .filter(({ stage }) => predicate(stage)) + .sort((left, right) => left.stage - right.stage)) { + tap.callback(stats); + } + }; + return { + compiler, + compile, + complete: (compilation: Rspack.Compilation) => + runDoneTaps(compilation, stage => stage < 0), + completeLate: (compilation: Rspack.Compilation) => + runDoneTaps(compilation, stage => stage >= 0), + fail: (error: Error) => failed?.(error), + invalidate: () => invalid?.(), + setChanges: (files: string[]) => { + compiler.modifiedFiles = new Set(files); + compiler.removedFiles = undefined; + }, + settle: (compilation: Rspack.Compilation) => + afterDoneByCompiler.get(compiler)?.(createStats(compilation)), + }; +}; + +type TestServerSetup = (context: { + action: 'dev'; + server: RsbuildDevServer; +}) => void; + +type TestConfig = { + server?: { setup?: TestServerSetup | TestServerSetup[] }; +}; + +const createHarness = (userSetup?: TestServerSetup) => { + let start!: OnBeforeStartDevServerFn; + let startOrder: 'pre' | 'post' | 'default' = 'default'; + let before!: OnBeforeDevCompileFn; + let beforeOrder: 'pre' | 'post' | 'default' = 'default'; + let closeHook: OnCloseDevServerFn | undefined; + let closeOrder: 'pre' | 'post' | 'default' = 'default'; + let created!: OnAfterCreateCompilerFn; + let after!: OnAfterDevCompileFn; + const closeRecords = new WeakMap(); + const warn = rstest.fn(); + let serverSetups = userSetup ? [userSetup] : []; + const api = { + logger: { error: rstest.fn(), warn }, + modifyRsbuildConfig: ( + callback: + | ((config: TestConfig) => TestConfig | void) + | { handler: (config: TestConfig) => TestConfig | void } + ) => { + const handler = + typeof callback === 'function' ? callback : callback.handler; + const nextConfig: TestConfig = { server: { setup: serverSetups } }; + const config = handler(nextConfig) ?? nextConfig; + const setup = config.server?.setup; + serverSetups = setup + ? Array.isArray(setup) + ? setup + : [setup] + : []; + }, + onBeforeStartDevServer: ( + callback: + | OnBeforeStartDevServerFn + | { + handler: OnBeforeStartDevServerFn; + order: 'pre' | 'post' | 'default'; + } + ) => { + if (typeof callback === 'function') { + start = callback; + return; + } + start = callback.handler; + startOrder = callback.order; + }, + onBeforeDevCompile: ( + callback: + | OnBeforeDevCompileFn + | { + handler: OnBeforeDevCompileFn; + order: 'pre' | 'post' | 'default'; + } + ) => { + if (typeof callback === 'function') { + before = callback; + return; + } + before = callback.handler; + beforeOrder = callback.order; + }, + onCloseDevServer: ( + callback: + | OnCloseDevServerFn + | { + handler: OnCloseDevServerFn; + order: 'pre' | 'post' | 'default'; + } + ) => { + if (typeof callback === 'function') { + closeHook = callback; + return; + } + closeHook = callback.handler; + closeOrder = callback.order; + }, + onAfterCreateCompiler: (callback: OnAfterCreateCompilerFn) => { + created = callback; + }, + onAfterDevCompile: (callback: OnAfterDevCompileFn) => { + after = callback; + }, + }; + const controller = createReactRouterDevRuntimeController({ + api: api as never, + isBuild: false, + buildPlan: { + defaultEntryName: 'static/js/app', + entryNames: ['static/js/app'], + }, + }); + const createServer = ( + loadBundle: (entryName: string) => Promise | unknown, + afterCloseHook?: () => Promise | void + ): RsbuildDevServer => { + let closing: Promise | undefined; + const record = { count: 0 }; + const server = { + close() { + if (!closing) { + record.count++; + closing = (async () => { + await closeHook?.(); + await afterCloseHook?.(); + })(); + } + return closing; + }, + environments: { node: { loadBundle } }, + sockWrite: rstest.fn(), + } as unknown as RsbuildDevServer; + closeRecords.set(server, record); + for (const setup of serverSetups) { + setup({ action: 'dev', server }); + } + return server; + }; + const loadBundle = rstest.fn(); + const server = createServer(loadBundle); + let currentServer = server; + const environments = {}; + const settleStats = (stats: Rspack.Stats | Rspack.MultiStats): void => { + const children = Array.isArray((stats as Rspack.MultiStats).stats) + ? (stats as Rspack.MultiStats).stats + : [stats as Rspack.Stats]; + for (const child of children) { + afterDoneByCompiler.get(child.compilation.compiler)?.(child); + } + }; + const callbacks = { + after: async ({ stats }: { stats: Rspack.Stats | Rspack.MultiStats }) => { + await after({ environments, isFirstCompile: false, stats }); + settleStats(stats); + await new Promise(resolve => setTimeout(resolve, 0)); + }, + aggregate: ({ stats }: { stats: Rspack.Stats | Rspack.MultiStats }) => + after({ environments, isFirstCompile: false, stats }), + before: () => + before({ + environments, + isFirstCompile: false, + isWatch: true, + }), + close: () => currentServer.close(), + created: ({ + compiler, + }: { + compiler: Rspack.Compiler | { compilers: Rspack.Compiler[] }; + }) => + created({ + compiler: compiler as Rspack.Compiler | Rspack.MultiCompiler, + environments, + }), + start: ({ server }: { server: RsbuildDevServer }) => { + const previous = currentServer; + currentServer = server; + return Promise.resolve(start({ environments, server })).catch(error => { + if (currentServer === server) { + currentServer = previous; + } + throw error; + }); + }, + settle: settleStats, + }; + return { + callbacks, + beforeOrder, + closeOrder, + controller, + createServer, + getCloseCount: (server: RsbuildDevServer) => + closeRecords.get(server)?.count ?? 0, + loadBundle, + server, + startOrder, + warn, + }; +}; + +describe('React Router development runtime controller', () => { + it('validates lifecycle state before default startup hooks', () => { + const { beforeOrder, closeOrder, startOrder } = createHarness(); + expect(beforeOrder).toBe('pre'); + expect(startOrder).toBe('pre'); + expect(closeOrder).toBe('pre'); + }); + + it('rejects readiness when Rsbuild does not create both compilers', async () => { + const { callbacks, controller, server } = createHarness(); + const compiler = createCompiler('web'); + await callbacks.start({ server }); + + callbacks.created({ compiler: compiler.compiler }); + + await expect(controller.createBuildLoader()()).rejects.toThrow( + 'did not create a multi-compiler' + ); + }); + + it('rejects initial readiness when aggregate stats omit an environment', async () => { + const { callbacks, controller, server } = createHarness(); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + const waiting = controller.createBuildLoader()(); + const webCompilation = web.compile(); + controller.captureWeb(webCompilation, createManifest('web')); + + await callbacks.after({ stats: createStats(webCompilation) }); + + await expect(waiting).rejects.toThrow( + 'did not provide both web and node results' + ); + }); + + it('rejects a fatal child failure and recovers on the next compile', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const waiting = controller.createBuildLoader()(); + + web.fail(new Error('fatal compiler failure')); + + await expect(waiting).rejects.toThrow('fatal compiler failure'); + + loadBundle.mockImplementation(() => createBuild('recovered')); + callbacks.before(); + const webCompilation = web.compile(); + controller.captureWeb(webCompilation, createManifest('recovered')); + web.complete(webCompilation); + const nodeCompilation = node.compile(); + const recovered = controller.createBuildLoader()(); + await callbacks.after({ + stats: createGraphStats(webCompilation, nodeCompilation), + }); + + await expect(recovered).resolves.toMatchObject({ + marker: 'recovered', + assets: { version: 'recovered' }, + }); + }); + + it('publishes a safe web-only compile after the aggregate pre-hook', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + loadBundle.mockImplementation(() => createBuild('base')); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const baseWeb = web.compile(); + controller.captureWeb(baseWeb, createManifest('web-base')); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + + web.setChanges(['/app/web-only.ts']); + callbacks.before(); + const nextWeb = web.compile(); + controller.captureWeb(nextWeb, createManifest('web-next')); + web.complete(nextWeb); + await callbacks.after({ stats: createGraphStats(nextWeb, baseNode) }); + + await expect(controller.createBuildLoader()()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'web-next' }, + }); + expect(loadBundle).toHaveBeenCalledOnce(); + }); + + it('hard reloads when a safe web-only compile removes CSS assets', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + loadBundle.mockImplementation(() => createBuild('base')); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const baseWeb = web.compile(); + controller.captureWeb( + baseWeb, + createManifest('web-base', { + entry: ['/assets/entry.css'], + routes: { 'routes/about': ['/assets/about.css'] }, + }) + ); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + expect(server.sockWrite).not.toHaveBeenCalled(); + + web.setChanges(['/app/routes/about.tsx']); + callbacks.before(); + const nextWeb = web.compile(); + controller.captureWeb( + nextWeb, + createManifest('web-next', { + entry: ['/assets/entry.css'], + }) + ); + web.complete(nextWeb); + await callbacks.after({ stats: createGraphStats(nextWeb, baseNode) }); + + await expect(controller.createBuildLoader()()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'web-next' }, + }); + expect(server.sockWrite).toHaveBeenCalledWith('full-reload', { + path: '*', + }); + }); + + it('hard reloads when CSS ownership is restored after a removal', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + loadBundle.mockImplementation(() => createBuild('base')); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const baseWeb = web.compile(); + controller.captureWeb( + baseWeb, + createManifest('web-base', { + routes: { 'routes/about': ['/assets/about.css'] }, + }) + ); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + + web.setChanges(['/app/routes/about.tsx']); + callbacks.before(); + const removedCssWeb = web.compile(); + controller.captureWeb( + removedCssWeb, + createManifest('without-css', { + routes: { 'routes/about': [] }, + }) + ); + web.complete(removedCssWeb); + await callbacks.after({ stats: createGraphStats(removedCssWeb, baseNode) }); + + web.setChanges(['/app/routes/about.tsx']); + callbacks.before(); + const readdedCssWeb = web.compile(); + controller.captureWeb( + readdedCssWeb, + createManifest('readded-css', { + routes: { 'routes/about': ['/assets/about.css'] }, + }) + ); + web.complete(readdedCssWeb); + await callbacks.after({ stats: createGraphStats(readdedCssWeb, baseNode) }); + + await expect(controller.createBuildLoader()()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'readded-css' }, + }); + expect(server.sockWrite).toHaveBeenCalledTimes(2); + expect(server.sockWrite).toHaveBeenNthCalledWith(1, 'full-reload', { + path: '*', + }); + expect(server.sockWrite).toHaveBeenNthCalledWith(2, 'full-reload', { + path: '*', + }); + }); + + it('publishes a safe node-only compile after the aggregate pre-hook', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + let build = createBuild('base'); + loadBundle.mockImplementation(() => build); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const baseWeb = web.compile(); + controller.captureWeb(baseWeb, createManifest('web-base')); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + + build = createBuild('node-next'); + node.setChanges(['/app/node-only.ts']); + callbacks.before(); + const nextNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, nextNode) }); + + await expect(controller.createBuildLoader()()).resolves.toMatchObject({ + marker: 'node-next', + assets: { version: 'web-base' }, + }); + expect(loadBundle).toHaveBeenCalledTimes(2); + }); + + it('publishes an initial same-attempt compile when node starts before web completes', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + loadBundle.mockImplementation(() => createBuild('parallel')); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const waiting = controller.createBuildLoader()(); + + const nodeCompilation = node.compile(); + const webCompilation = web.compile(); + controller.captureWeb(webCompilation, createManifest('parallel')); + web.complete(webCompilation); + await callbacks.after({ + stats: createGraphStats(webCompilation, nodeCompilation), + }); + + const published = await Promise.race([ + waiting, + new Promise(resolve => setTimeout(() => resolve('pending'), 0)), + ]); + expect(published).toMatchObject({ + marker: 'parallel', + assets: { version: 'parallel' }, + }); + expect(loadBundle).toHaveBeenCalledOnce(); + }); + + it('publishes a same-attempt rebuild when node starts before web completes', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + let build = createBuild('base'); + loadBundle.mockImplementation(() => build); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + const loadBuild = controller.createBuildLoader(); + + callbacks.before(); + const baseWeb = web.compile(); + controller.captureWeb(baseWeb, createManifest('base')); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + + build = createBuild('node-b'); + callbacks.before(); + const nodeB = node.compile(); + const webB = web.compile(); + controller.captureWeb(webB, createManifest('web-b')); + web.complete(webB); + await callbacks.after({ stats: createGraphStats(webB, nodeB) }); + + expect(loadBundle).toHaveBeenCalledTimes(2); + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'node-b', + assets: { version: 'web-b' }, + }); + }); + + it('waits for late done hooks before snapshotting dependencies', async () => { + const routePath = '/app/routes.ts'; + const { callbacks, controller, loadBundle, server, warn } = createHarness(); + loadBundle.mockImplementation(() => createBuild('base')); + const web = createCompiler('web'); + const node = createCompiler('node'); + node.compiler.hooks.done.tap( + { name: 'late-node-dependency', stage: 1000 }, + stats => stats.compilation.buildDependencies.add(routePath) + ); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + callbacks.before(); + const baseWeb = web.compile(); + controller.captureWeb(baseWeb, createManifest('web-base')); + web.complete(baseWeb); + web.settle(baseWeb); + const baseNode = node.compile(); + await callbacks.aggregate({ stats: createGraphStats(baseWeb, baseNode) }); + node.completeLate(baseNode); + node.settle(baseNode); + const committed = await controller.createBuildLoader()(); + + web.setChanges([routePath]); + callbacks.before(); + const nextWeb = web.compile(); + controller.captureWeb(nextWeb, createManifest('web-next')); + web.complete(nextWeb); + await callbacks.aggregate({ stats: createGraphStats(nextWeb, baseNode) }); + web.settle(nextWeb); + + expect(await controller.createBuildLoader()()).toBe(committed); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('incomplete web-only') + ); + }); + + it('requires the active server to close before replacement', async () => { + const { + callbacks, + controller, + createServer, + getCloseCount, + server, + } = createHarness(); + await callbacks.start({ server }); + const activeLoader = controller.createBuildLoader(); + const replacementServer = createServer(rstest.fn()); + + await expect( + callbacks.start({ server: replacementServer }) + ).rejects.toThrow('development server is already active'); + + expect(getCloseCount(server)).toBe(0); + await server.close(); + expect(getCloseCount(server)).toBe(1); + await expect(activeLoader()).rejects.toThrow('not registered'); + await callbacks.start({ server: replacementServer }); + const replacementLoader = controller.createBuildLoader(); + await callbacks.close(); + await expect(replacementLoader()).rejects.toThrow('not registered'); + }); + + it('observes one close promise and rejects replacement until it settles', async () => { + const { callbacks, createServer, getCloseCount } = createHarness(); + let releaseClose!: () => void; + const closeGate = new Promise(resolve => { + releaseClose = resolve; + }); + const closingServer = createServer(rstest.fn(), () => closeGate); + await callbacks.start({ server: closingServer }); + const closing = closingServer.close(); + expect(closingServer.close()).toBe(closing); + expect(getCloseCount(closingServer)).toBe(1); + + const replacement = createServer(rstest.fn()); + await expect(callbacks.start({ server: replacement })).rejects.toThrow( + 'still closing' + ); + + releaseClose(); + await closing; + await callbacks.start({ server: replacement }); + expect(getCloseCount(closingServer)).toBe(1); + await callbacks.close(); + }); + + it('observes close captured by an earlier server setup callback', async () => { + let capturedClose!: () => Promise; + const { callbacks, createServer, getCloseCount, server } = createHarness( + ({ server }) => { + capturedClose = server.close; + } + ); + const closeFirstServer = capturedClose; + await callbacks.start({ server }); + + await closeFirstServer(); + expect(getCloseCount(server)).toBe(1); + + const replacement = createServer(rstest.fn()); + await callbacks.start({ server: replacement }); + await callbacks.close(); + }); + + it('fails replacement closed after the active server cannot close', async () => { + const { callbacks, controller, createServer } = createHarness(); + const closeError = new Error('could not close abandoned server'); + const abandonedServer = createServer(rstest.fn(), () => { + throw closeError; + }); + await callbacks.start({ server: abandonedServer }); + const abandonedLoader = controller.createBuildLoader(); + + await expect(abandonedServer.close()).rejects.toThrow(closeError); + + await expect(abandonedLoader()).rejects.toThrow('not registered'); + await expect(controller.createBuildLoader()()).rejects.toThrow( + 'previous development server failed to close' + ); + + await expect( + callbacks.start({ server: createServer(rstest.fn()) }) + ).rejects.toThrow('previous development server failed to close'); + await expect(controller.createBuildLoader()()).rejects.toThrow( + 'previous development server failed to close' + ); + }); + + it('ignores a late fatal callback from a replaced server session', async () => { + const { callbacks, controller, createServer, server } = createHarness(); + const oldWeb = createCompiler('web'); + const oldNode = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [oldWeb.compiler, oldNode.compiler] }, + }); + const oldLoader = controller.createBuildLoader(); + + const replacementServer = createServer(rstest.fn()); + const newWeb = createCompiler('web'); + const newNode = createCompiler('node'); + await callbacks.close(); + await callbacks.start({ server: replacementServer }); + callbacks.created({ + compiler: { compilers: [newWeb.compiler, newNode.compiler] }, + }); + callbacks.before(); + const waiting = controller.createBuildLoader()(); + + oldWeb.fail(new Error('stale compiler failure')); + await expect(oldLoader()).rejects.toThrow('not registered'); + newWeb.fail(new Error('current compiler failure')); + + await expect(waiting).rejects.toThrow('current compiler failure'); + }); + + it('disposes the current session through the supported close hook', async () => { + const { callbacks, controller, server } = createHarness(); + await callbacks.start({ server }); + const loadBuild = controller.createBuildLoader(); + + await callbacks.close(); + + await expect(loadBuild()).rejects.toThrow('not registered'); + await expect(controller.createBuildLoader()()).rejects.toThrow( + 'runtime is not ready' + ); + }); + + it('ignores manifest and completion callbacks from a replaced compiler', async () => { + const { callbacks, controller, createServer, server } = createHarness(); + const oldWeb = createCompiler('web'); + const oldNode = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [oldWeb.compiler, oldNode.compiler] }, + }); + const oldWebCompilation = oldWeb.compile(); + oldWeb.complete(oldWebCompilation); + const oldNodeCompilation = oldNode.compile(); + + const replacementLoadBundle = rstest.fn(); + const replacementServer = createServer(replacementLoadBundle); + const newWeb = createCompiler('web'); + const newNode = createCompiler('node'); + await callbacks.close(); + await callbacks.start({ server: replacementServer }); + callbacks.created({ + compiler: { compilers: [newWeb.compiler, newNode.compiler] }, + }); + callbacks.before(); + controller.captureWeb(oldWebCompilation, createManifest('old')); + + await callbacks.after({ + stats: createGraphStats(oldWebCompilation, oldNodeCompilation), + }); + + expect(replacementLoadBundle).not.toHaveBeenCalled(); + const waiting = controller.createBuildLoader()(); + newWeb.fail(new Error('current failure')); + await expect(waiting).rejects.toThrow('current failure'); + }); + + it('isolates web lineage callbacks from a replaced compiler pair', async () => { + const { callbacks, controller, createServer, server } = createHarness(); + const oldWeb = createCompiler('web'); + const oldNode = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [oldWeb.compiler, oldNode.compiler] }, + }); + const staleWebCompilation = oldWeb.compile(); + + const replacementLoadBundle = rstest.fn(() => createBuild('replacement')); + const replacementServer = createServer(replacementLoadBundle); + const newWeb = createCompiler('web'); + const newNode = createCompiler('node'); + await callbacks.close(); + await callbacks.start({ server: replacementServer }); + callbacks.created({ + compiler: { compilers: [newWeb.compiler, newNode.compiler] }, + }); + + const newWebCompilation = newWeb.compile(); + controller.captureWeb(newWebCompilation, createManifest('replacement')); + newWeb.complete(newWebCompilation); + oldWeb.complete(staleWebCompilation); + const newNodeCompilation = newNode.compile(); + + await callbacks.after({ + stats: createGraphStats(newWebCompilation, newNodeCompilation), + }); + + expect(replacementLoadBundle).toHaveBeenCalledOnce(); + await expect(controller.createBuildLoader()()).resolves.toMatchObject({ + marker: 'replacement', + assets: { version: 'replacement' }, + }); + }); + + it('keeps the initial loader pending until a mixed result becomes coherent', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + loadBundle.mockImplementation(() => createBuild('web-b')); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + const waiting = controller.createBuildLoader()(); + + const webA = web.compile(); + web.complete(webA); + const nodeA = node.compile(); + const webB = web.compile(); + controller.captureWeb(webB, createManifest('web-b')); + web.complete(webB); + await callbacks.after({ stats: createGraphStats(webB, nodeA) }); + + let published = false; + void waiting.then(() => { + published = true; + }); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(published).toBe(false); + expect(loadBundle).not.toHaveBeenCalled(); + + const nodeB = node.compile(); + await callbacks.after({ stats: createGraphStats(webB, nodeB) }); + + await expect(waiting).resolves.toMatchObject({ + marker: 'web-b', + assets: { version: 'web-b' }, + }); + }); + + it('ignores a coherent aggregate older than an invalidated compilation', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + let build = createBuild('base'); + loadBundle.mockImplementation(() => build); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + const loadBuild = controller.createBuildLoader(); + + const baseWeb = web.compile(); + controller.captureWeb(baseWeb, createManifest('base')); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + + callbacks.before(); + const webA = web.compile(); + controller.captureWeb(webA, createManifest('a')); + web.complete(webA); + const nodeA = node.compile(); + web.invalidate(); + build = createBuild('a'); + await callbacks.after({ stats: createGraphStats(webA, nodeA) }); + + expect(loadBundle).toHaveBeenCalledOnce(); + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'base' }, + }); + + const webB = web.compile(); + controller.captureWeb(webB, createManifest('b')); + web.complete(webB); + const nodeB = node.compile(); + build = createBuild('b'); + await callbacks.after({ stats: createGraphStats(webB, nodeB) }); + + expect(loadBundle).toHaveBeenCalledTimes(2); + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'b', + assets: { version: 'b' }, + }); + }); + + it('does not publish an evaluation superseded during invalidation', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + let resolveCandidate!: (build: TestServerBuild) => void; + const candidate = new Promise(resolve => { + resolveCandidate = resolve; + }); + loadBundle + .mockImplementationOnce(() => createBuild('base')) + .mockImplementationOnce(() => candidate); + const web = createCompiler('web'); + const node = createCompiler('node'); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + const loadBuild = controller.createBuildLoader(); + + const baseWeb = web.compile(); + controller.captureWeb(baseWeb, createManifest('base')); + web.complete(baseWeb); + const baseNode = node.compile(); + await callbacks.after({ stats: createGraphStats(baseWeb, baseNode) }); + + callbacks.before(); + const webA = web.compile(); + controller.captureWeb(webA, createManifest('a')); + web.complete(webA); + const nodeA = node.compile(); + const evaluating = callbacks.after({ + stats: createGraphStats(webA, nodeA), + }); + await Promise.resolve(); + expect(loadBundle).toHaveBeenCalledTimes(2); + + web.invalidate(); + resolveCandidate(createBuild('a')); + await evaluating; + + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'base' }, + }); + }); + + it('rejects web B with node A and publishes web B with node B', async () => { + const { callbacks, controller, loadBundle, server } = createHarness(); + const web = createCompiler('web'); + const node = createCompiler('node'); + let build = createBuild('base'); + loadBundle.mockImplementation(() => build); + await callbacks.start({ server }); + callbacks.created({ + compiler: { compilers: [web.compiler, node.compiler] }, + }); + const loadBuild = controller.createBuildLoader(); + + const webBase = web.compile(); + controller.captureWeb(webBase, createManifest('web-base')); + web.complete(webBase); + const nodeBase = node.compile(); + await callbacks.after({ stats: createGraphStats(webBase, nodeBase) }); + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'web-base' }, + }); + + callbacks.before(); + const webA = web.compile(); + controller.captureWeb(webA, createManifest('web-a')); + web.complete(webA); + const nodeA = node.compile(); + + web.invalidate(); + node.invalidate(); + const webB = web.compile(); + controller.captureWeb(webB, createManifest('web-b')); + web.complete(webB); + build = createBuild('node-a'); + await callbacks.after({ stats: createGraphStats(webB, nodeA) }); + + expect(loadBundle).toHaveBeenCalledTimes(1); + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'base', + assets: { version: 'web-base' }, + }); + + const nodeB = node.compile(); + build = createBuild('node-b'); + await callbacks.after({ stats: createGraphStats(webB, nodeB) }); + + expect(loadBundle).toHaveBeenCalledTimes(2); + await expect(loadBuild()).resolves.toMatchObject({ + marker: 'node-b', + assets: { version: 'web-b' }, + }); + }); +}); diff --git a/tests/dev-runtime.integration.test.ts b/tests/dev-runtime.integration.test.ts new file mode 100644 index 0000000..5416aa4 --- /dev/null +++ b/tests/dev-runtime.integration.test.ts @@ -0,0 +1,549 @@ +import { EventEmitter } from 'node:events'; +import { spawn } from 'node:child_process'; +import * as fs from 'node:fs'; +import { createServer, type Server as HttpServer } from 'node:http'; +import * as vm from 'node:vm'; +import { + chmodSync, + cpSync, + mkdirSync, + mkdtempSync, + rmSync, + writeFileSync, +} from 'node:fs'; +import { delimiter, join } from 'node:path'; +import { + createLogger, + createRsbuild, + type RsbuildConfig, + type RsbuildDevServer, + type RsbuildPlugin, + type Rspack, +} from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { expect, it, rstest } from '@rstest/core'; +import { createRequestHandler, type ServerBuild } from 'react-router'; + +const ESM_SUBPROCESS_ENV = 'RR_DEV_RUNTIME_ESM_SUBPROCESS'; +const isEsmSubprocess = process.env[ESM_SUBPROCESS_ENV] === '1'; + +const INITIAL_COMPILATION_ERROR = ` +export const handle = { marker: ; + +export default function IndexRoute() { + return

broken

; +} +`; + +const EVALUATION_ERROR_MARKER = 'RR_TEST_EVALUATION_FAILURE'; +const EVALUATION_ERROR = ` +throw new Error('${EVALUATION_ERROR_MARKER}'); + +export const handle = { marker: 'uncommitted' }; + +export default function IndexRoute() { + return

uncommitted

; +} +`; + +const routeSource = (marker: string): string => ` +export const handle = { marker: '${marker}' }; + +export default function IndexRoute() { + return

${marker}

; +} +`; + +const withTimeout = async ( + promise: Promise, + timeoutMs: number, + label: string +): Promise => { + let timeout: ReturnType | undefined; + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timeout = setTimeout( + () => reject(new Error(`Timed out waiting for ${label}`)), + timeoutMs + ); + }), + ]); + } finally { + if (timeout) { + clearTimeout(timeout); + } + } +}; + +const getBuildMarker = (build: ServerBuild): string | undefined => { + for (const route of Object.values(build.routes)) { + const handle = (route.module as { handle?: unknown }).handle as + | { marker?: unknown } + | undefined; + if (typeof handle?.marker === 'string') { + return handle.marker; + } + } + return undefined; +}; + +const createNoopWatcher = (): fs.FSWatcher => { + const watcher = new EventEmitter() as EventEmitter & { + close: () => void; + ref: () => fs.FSWatcher; + unref: () => fs.FSWatcher; + }; + watcher.close = () => undefined; + watcher.ref = () => watcher as fs.FSWatcher; + watcher.unref = () => watcher as fs.FSWatcher; + return watcher as fs.FSWatcher; +}; + +const runEsmIntegrationSubprocess = (repositoryRoot: string): Promise => + new Promise((resolve, reject) => { + const rstestCli = join( + repositoryRoot, + 'node_modules/@rstest/core/bin/rstest.js' + ); + const output: Buffer[] = []; + const child = spawn( + process.execPath, + [ + '--experimental-vm-modules', + rstestCli, + 'run', + 'tests/dev-runtime.integration.test.ts', + ], + { + cwd: repositoryRoot, + env: { ...process.env, [ESM_SUBPROCESS_ENV]: '1' }, + stdio: ['ignore', 'pipe', 'pipe'], + } + ); + child.stdout.on('data', chunk => output.push(Buffer.from(chunk))); + child.stderr.on('data', chunk => output.push(Buffer.from(chunk))); + + let settled = false; + let timeout: ReturnType | undefined; + let forceKillTimeout: ReturnType | undefined; + let timedOut = false; + const finish = (error?: Error): void => { + if (settled) { + return; + } + settled = true; + if (timeout) { + clearTimeout(timeout); + } + if (forceKillTimeout) { + clearTimeout(forceKillTimeout); + } + error ? reject(error) : resolve(); + }; + timeout = setTimeout(() => { + timedOut = true; + child.kill('SIGTERM'); + forceKillTimeout = setTimeout(() => { + if (child.exitCode === null && child.signalCode === null) { + child.kill('SIGKILL'); + } + }, 5_000); + }, 80_000); + child.once('error', error => finish(error)); + child.once('close', exitCode => { + if (timedOut) { + finish( + new Error( + `ESM development integration subprocess timed out.\n${Buffer.concat(output).toString('utf8')}` + ) + ); + return; + } + if (exitCode === 0) { + finish(); + return; + } + finish( + new Error( + `ESM development integration subprocess exited with ${exitCode}.\n${Buffer.concat(output).toString('utf8')}` + ) + ); + }); + }); + +const createDevRuntimeHarness = async (esm: boolean) => { + const repositoryRoot = process.cwd(); + const temporaryFixtures = join(repositoryRoot, 'tests/.tmp-dev-runtime'); + mkdirSync(temporaryFixtures, { recursive: true }); + const fixtureRoot = mkdtempSync(join(temporaryFixtures, 'case-')); + cpSync(join(repositoryRoot, 'tests/fixtures/dev-runtime'), fixtureRoot, { + recursive: true, + }); + const executableDirectory = join(fixtureRoot, '.bin'); + mkdirSync(executableDirectory, { recursive: true }); + const npxPath = join(executableDirectory, 'npx'); + writeFileSync(npxPath, '#!/bin/sh\nexit 0\n'); + chmodSync(npxPath, 0o755); + const routePath = join(fixtureRoot, 'app/routes/index.tsx'); + writeFileSync(routePath, INITIAL_COMPILATION_ERROR); + + const existsSyncMock = fs.existsSync as typeof fs.existsSync & { + mockRestore?: () => void; + }; + existsSyncMock.mockRestore?.(); + const watchMock = rstest + .spyOn(fs, 'watch') + .mockImplementation((() => createNoopWatcher()) as typeof fs.watch); + const logger = createLogger({ level: 'silent' }); + const loggerError = rstest.spyOn(logger, 'error'); + const consoleError = rstest + .spyOn(console, 'error') + .mockImplementation(() => undefined); + const originalPath = process.env.PATH; + let server: RsbuildDevServer | null = null; + let httpServer: HttpServer | null = null; + let builtInServerUrl: string | undefined; + let compiler: Rspack.MultiCompiler | undefined; + let compileAttempts = 0; + let completedCompiles = 0; + let cleaned = false; + + const closeBuiltInServer = async (): Promise => { + const current = httpServer; + httpServer = null; + builtInServerUrl = undefined; + if (current) { + await new Promise((resolve, reject) => + current.close(error => (error ? reject(error) : resolve())) + ); + } + }; + const closeDevServer = async (): Promise => { + const current = server; + server = null; + await current?.close(); + }; + const cleanup = async (): Promise => { + if (cleaned) { + return; + } + cleaned = true; + try { + await Promise.all([closeBuiltInServer(), closeDevServer()]); + } finally { + process.chdir(repositoryRoot); + if (originalPath === undefined) { + delete process.env.PATH; + } else { + process.env.PATH = originalPath; + } + loggerError.mockRestore(); + consoleError.mockRestore(); + watchMock.mockRestore(); + rmSync(fixtureRoot, { force: true, recursive: true }); + } + }; + + process.env.PATH = `${executableDirectory}${delimiter}${originalPath ?? ''}`; + process.chdir(fixtureRoot); + try { + rstest.doUnmock('jiti'); + const { loadReactRouterServerBuild, pluginReactRouter } = + await import('../src/index'); + const captureCompilerPlugin: RsbuildPlugin = { + name: 'test:capture-real-multi-compiler', + setup(api) { + api.onBeforeDevCompile(() => { + compileAttempts += 1; + }); + api.onAfterDevCompile(() => { + completedCompiles += 1; + }); + api.onAfterCreateCompiler(({ compiler: createdCompiler }) => { + if (!('compilers' in createdCompiler)) { + throw new Error('Expected Rsbuild to create a MultiCompiler.'); + } + compiler = createdCompiler; + }); + }, + }; + const rsbuildConfig: RsbuildConfig = { + customLogger: logger, + dev: { cliShortcuts: false, hmr: false, liveReload: false }, + plugins: [ + pluginReactRouter({ + onRouteTopologyChange() {}, + ...(esm ? {} : { serverOutput: 'commonjs' as const }), + }), + pluginReact(), + captureCompilerPlugin, + ], + root: fixtureRoot, + server: { middlewareMode: true }, + tools: { + rspack(config) { + config.watchOptions = { + ...config.watchOptions, + aggregateTimeout: 10, + poll: 50, + }; + }, + }, + }; + const rsbuild = await createRsbuild({ cwd: fixtureRoot, rsbuildConfig }); + + const assertNodeCompiler = (): void => { + const nodeCompiler = compiler?.compilers.find( + child => child.name === 'node' + ); + expect(nodeCompiler?.options.output.module).toBe(esm); + expect(Object.keys(nodeCompiler?.options.entry ?? {})).toEqual( + expect.arrayContaining([ + 'static/js/app', + 'static/js/react-router-server-build', + 'index/index', + 'other/index', + ]) + ); + if (esm) { + expect(vm.SourceTextModule).toBeTypeOf('function'); + } + }; + const startDevServer = async (): Promise => { + server = await rsbuild.createDevServer({ getPortSilently: true }); + assertNodeCompiler(); + }; + const loadBuild = (entryName?: string): Promise => + server + ? loadReactRouterServerBuild(server, entryName) + : Promise.reject(new Error('The test dev server is closed.')); + const loadRawEntry = (entryName: string): Promise => + server + ? server.environments.node.loadBundle(entryName) + : Promise.reject(new Error('The test dev server is closed.')); + const requestHandler = createRequestHandler(loadBuild); + const requestDocument = async (): Promise => { + const response = await requestHandler(new Request('http://test.local/')); + expect(response.status).toBe(200); + return response.text(); + }; + const invalidate = async ( + source: string, + webOnly: boolean + ): Promise => { + await new Promise(resolve => setTimeout(resolve, 75)); + writeFileSync(routePath, source); + const watching = webOnly + ? compiler?.compilers.find(child => child.name === 'web')?.watching + : compiler?.watching; + if (!watching) { + throw new Error( + `Expected the real ${webOnly ? 'web compiler' : 'MultiCompiler'} to be watching.` + ); + } + await new Promise((resolve, reject) => { + watching.invalidateWithChangesAndRemovals( + new Set([routePath]), + new Set(), + error => (error ? reject(error) : resolve()) + ); + }); + }; + const startBuiltInServer = async (): Promise => { + if (!server) { + throw new Error('The test dev server is closed.'); + } + httpServer = createServer(server.middlewares); + await new Promise((resolve, reject) => { + httpServer!.once('error', reject); + httpServer!.listen(0, '127.0.0.1', resolve); + }); + const address = httpServer.address(); + if (!address || typeof address === 'string') { + throw new Error('Expected the built-in middleware server to listen.'); + } + builtInServerUrl = `http://127.0.0.1:${address.port}/`; + }; + const requestBuiltInDocument = async (): Promise => { + if (!builtInServerUrl) { + throw new Error('The built-in middleware server is not listening.'); + } + const response = await fetch(builtInServerUrl); + expect(response.status).toBe(200); + return response.text(); + }; + const restartDevServer = async (): Promise => { + await Promise.all([closeBuiltInServer(), closeDevServer()]); + await startDevServer(); + }; + + await startDevServer(); + return { + cleanup, + get compileAttempts() { + return compileAttempts; + }, + get completedCompiles() { + return completedCompiles; + }, + hasConsoleError: (marker: string) => + consoleError.mock.calls.some(args => + args.some(arg => String(arg).includes(marker)) + ), + hasLoggedError: (marker: string) => + loggerError.mock.calls.some(args => + args.some(arg => String(arg).includes(marker)) + ), + loadBuild, + loadRawEntry, + rebuildRoute: (source: string) => invalidate(source, false), + rebuildWebFirst: (source: string) => invalidate(source, true), + requestBuiltInDocument, + requestDocument, + restartDevServer, + startBuiltInServer, + }; + } catch (error) { + await cleanup(); + throw error; + } +}; + +type DevRuntimeHarness = Awaited>; + +const expectInitialCompilationFailure = async ( + harness: DevRuntimeHarness +): Promise => { + await expect( + withTimeout(harness.loadBuild(), 20_000, 'the initial compilation failure') + ).rejects.toThrow('development compilation failed'); + expect(harness.hasConsoleError('Unexpected token')).toBe(true); + expect(harness.compileAttempts).toBeGreaterThan(0); + expect(harness.completedCompiles).toBeGreaterThan(0); +}; + +const expectFirstCommittedGeneration = async ( + harness: DevRuntimeHarness +): Promise => { + const attemptsBeforeRecovery = harness.compileAttempts; + const completedBeforeRecovery = harness.completedCompiles; + await harness.rebuildRoute(routeSource('v1')); + expect(harness.compileAttempts).toBeGreaterThan(attemptsBeforeRecovery); + await expect + .poll(() => harness.completedCompiles, { + intervals: [50, 100, 250], + timeout: 20_000, + }) + .toBeGreaterThan(completedBeforeRecovery); + + const build = await withTimeout( + harness.loadBuild(), + 5_000, + `the first valid generation after ${harness.compileAttempts} attempts and ${harness.completedCompiles} completions` + ); + expect(getBuildMarker(build)).toBe('v1'); + expect(build.assets).toBeTypeOf('object'); + expect(build.entry).toBeTypeOf('object'); + expect(build.routes).toBeTypeOf('object'); + await expect(harness.loadBuild('index/index')).resolves.toMatchObject({ + assets: { + routes: { + root: expect.any(Object), + 'routes/index': expect.any(Object), + }, + }, + }); + await expect(harness.loadBuild('other/index')).resolves.toMatchObject({ + assets: { + routes: { + root: expect.any(Object), + 'routes/other': expect.any(Object), + }, + }, + }); + await expect(harness.loadBuild('missing/index')).rejects.toThrow( + 'not part of this development server build plan' + ); + await expect(harness.loadRawEntry('static/js/app')).resolves.toMatchObject({ + customServerMarker: 'custom-server-entry', + }); + await expect(harness.requestDocument()).resolves.toContain('v1'); + await harness.startBuiltInServer(); + await expect(harness.requestBuiltInDocument()).resolves.toContain('v1'); +}; + +const expectWebFirstRebuild = async ( + harness: DevRuntimeHarness +): Promise => { + await harness.rebuildWebFirst(routeSource('v2')); + await expect + .poll(async () => getBuildMarker(await harness.loadBuild()), { + intervals: [50, 100, 250], + timeout: 20_000, + }) + .toBe('v2'); + await expect(harness.requestDocument()).resolves.toContain('v2'); + await expect(harness.requestBuiltInDocument()).resolves.toContain('v2'); +}; + +const expectEvaluationFailurePreservesLastGood = async ( + harness: DevRuntimeHarness +): Promise => { + await harness.rebuildRoute(EVALUATION_ERROR); + await expect + .poll(() => harness.hasLoggedError(EVALUATION_ERROR_MARKER), { + intervals: [50, 100, 250], + timeout: 20_000, + }) + .toBe(true); + expect(getBuildMarker(await harness.loadBuild())).toBe('v2'); +}; + +const expectRecoveryAndRestart = async ( + harness: DevRuntimeHarness +): Promise => { + await harness.rebuildRoute(routeSource('v3')); + await expect + .poll(async () => getBuildMarker(await harness.loadBuild()), { + intervals: [50, 100, 250], + timeout: 20_000, + }) + .toBe('v3'); + await harness.restartDevServer(); + const build = await withTimeout( + harness.loadBuild(), + 20_000, + 'a generation after restarting the same Rsbuild instance' + ); + expect(getBuildMarker(build)).toBe('v3'); + await expect(harness.requestDocument()).resolves.toContain('v3'); +}; + +const runDevRuntimeScenario = async (esm: boolean): Promise => { + const harness = await createDevRuntimeHarness(esm); + try { + await expectInitialCompilationFailure(harness); + await expectFirstCommittedGeneration(harness); + await expectWebFirstRebuild(harness); + await expectEvaluationFailurePreservesLastGood(harness); + await expectRecoveryAndRestart(harness); + } finally { + await harness.cleanup(); + } +}; + +it( + `publishes recoverable generations through real Rsbuild ${ + isEsmSubprocess ? 'ESM' : 'CommonJS' + } server paths`, + () => runDevRuntimeScenario(isEsmSubprocess), + 90_000 +); + +if (!isEsmSubprocess) { + it('runs the real default ESM development path with VM modules enabled', async () => { + await runEsmIntegrationSubprocess(process.cwd()); + }, 90_000); +} diff --git a/tests/dev-server.test.ts b/tests/dev-server.test.ts new file mode 100644 index 0000000..74a6185 --- /dev/null +++ b/tests/dev-server.test.ts @@ -0,0 +1,93 @@ +import type { IncomingMessage, ServerResponse } from 'node:http'; +import { describe, expect, it, rstest } from '@rstest/core'; +import type { ServerBuild } from 'react-router'; +import { createDevServerMiddleware } from '../src/dev-server'; + +const build = { + entry: { module: {} }, + routes: {}, + assets: {}, +} as unknown as ServerBuild; + +describe('React Router development middleware', () => { + it('constructs one lazy request handler over the committed build provider', async () => { + const loadBuild = rstest.fn(() => Promise.resolve(build)); + const requestHandler = rstest.fn(async () => { + await capturedBuildProvider?.(); + return new Response(); + }); + let capturedBuildProvider: (() => Promise) | undefined; + const createRequestHandler = rstest.fn( + (buildProvider: () => Promise) => { + capturedBuildProvider = buildProvider; + return requestHandler; + } + ); + const listener = rstest.fn( + async (_req: IncomingMessage, _res: ServerResponse) => { + await requestHandler(new Request('http://localhost/')); + } + ); + const createRequestListener = rstest.fn(() => listener); + const next = rstest.fn(); + const middleware = createDevServerMiddleware({ + loadBuild, + createRequestHandler, + createRequestListener, + }); + + await middleware({} as IncomingMessage, {} as ServerResponse, next); + await middleware({} as IncomingMessage, {} as ServerResponse, next); + + expect(createRequestHandler).toHaveBeenCalledTimes(1); + expect(createRequestHandler).toHaveBeenCalledWith( + loadBuild, + 'development' + ); + expect(createRequestListener).toHaveBeenCalledTimes(1); + expect(listener).toHaveBeenCalledTimes(2); + expect(loadBuild).toHaveBeenCalledTimes(2); + expect(next).not.toHaveBeenCalled(); + }); + + it('forwards listener failures to the next middleware', async () => { + const error = new Error('request failed'); + const next = rstest.fn(); + const middleware = createDevServerMiddleware({ + loadBuild: () => Promise.resolve(build), + createRequestHandler: () => () => Promise.reject(error), + createRequestListener: handler => async () => { + await handler(new Request('http://localhost/')); + }, + }); + + await middleware({} as IncomingMessage, {} as ServerResponse, next); + + expect(next).toHaveBeenCalledWith(error); + }); + + it('does not pass node-fetch client info as React Router load context', async () => { + const requestHandler = rstest.fn(async () => new Response()); + const middleware = createDevServerMiddleware({ + loadBuild: () => Promise.resolve(build), + createRequestHandler: () => requestHandler, + createRequestListener: handler => async () => { + await ( + handler as ( + request: Request, + client: { address: string; family: string; port: number } + ) => Response | Promise + )(new Request('http://localhost/'), { + address: '127.0.0.1', + family: 'IPv4', + port: 1234, + }); + }, + }); + + await middleware({} as IncomingMessage, {} as ServerResponse, rstest.fn()); + + expect(requestHandler).toHaveBeenCalledTimes(1); + expect(requestHandler.mock.calls[0]).toHaveLength(1); + }); +}); diff --git a/tests/export-utils.test.ts b/tests/export-utils.test.ts new file mode 100644 index 0000000..4dc18ce --- /dev/null +++ b/tests/export-utils.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from '@rstest/core'; +import { getExportNamesAndExportAll } from '../src/export-utils'; + +describe('getExportNamesAndExportAll', () => { + it('collects runtime exports and export-all modules', async () => { + await expect( + getExportNamesAndExportAll(` + export type LoaderData = { value: string }; + export interface RouteHandle { title: string } + export type * from './types'; + export type * as typeHelpers from './type-helpers'; + export * from './shared'; + export * as helpers from './helpers'; + export const loader = () => null; + export default function Route() { return null; } + `) + ).resolves.toEqual({ + exportNames: ['helpers', 'loader', 'default'], + exportAllModules: ['./shared'], + }); + }); + + it('collects exported TypeScript enums as runtime exports', async () => { + await expect( + getExportNamesAndExportAll(`export enum Status { Active = 'active' }`) + ).resolves.toEqual({ + exportNames: ['Status'], + exportAllModules: [], + }); + }); + + it('ignores erased default interfaces', async () => { + await expect( + getExportNamesAndExportAll( + `export default interface RouteType { value: string }` + ) + ).resolves.toEqual({ exportNames: [], exportAllModules: [] }); + }); + + it('ignores erased ambient declarations', async () => { + await expect( + getExportNamesAndExportAll(` + export declare function loader(): void; + export declare const action: () => void; + export declare class ServerOnly {} + export const clientLoader = () => null; + `) + ).resolves.toEqual({ + exportNames: ['clientLoader'], + exportAllModules: [], + }); + }); +}); diff --git a/tests/features.test.ts b/tests/features.test.ts index ad1e3bf..cd0744a 100644 --- a/tests/features.test.ts +++ b/tests/features.test.ts @@ -1,6 +1,10 @@ import { createStubRsbuild } from '@scripts/test-helper'; import { describe, expect, it, rstest } from '@rstest/core'; +import { rspack } from '@rsbuild/core'; +import * as fs from 'node:fs'; +import path from 'node:path'; import { pluginReactRouter } from '../src'; +import { getVirtualModuleFilePath } from '../src/virtual-modules'; describe('pluginReactRouter', () => { describe('basic configuration', () => { @@ -85,10 +89,57 @@ describe('pluginReactRouter', () => { const plugins = config.tools?.rspack?.plugins || []; const virtualModulePlugin = plugins.find( - (p) => p.constructor.name === 'RspackVirtualModulePlugin' + (p: any) => p.constructor.name === 'VirtualModulesPlugin' ); expect(virtualModulePlugin).toBeDefined(); + + const compiler = { + context: '/virtual/project', + hooks: { + afterEnvironment: { + tap: (_name: string, handler: () => void) => handler(), + }, + }, + } as any; + virtualModulePlugin.apply(compiler); + + const virtualFiles = + rspack.experiments.VirtualModulesPlugin.__internal__take_virtual_files( + compiler + ); + const virtualFilePaths = virtualFiles?.map(file => file.path) || []; + const virtualModulePath = (id: string) => + path.join(compiler.context, getVirtualModuleFilePath(id)); + + expect(virtualFilePaths).toContain( + virtualModulePath('virtual/react-router/browser-manifest') + ); + expect(virtualFilePaths).toContain( + virtualModulePath('virtual/react-router/server-build') + ); + expect(virtualFilePaths).toContain( + virtualModulePath('virtual/react-router/with-props') + ); + expect(virtualFilePaths).not.toContain( + '/virtual/project/virtual/react-router/browser-manifest' + ); + }); + + it('should map bare React Router virtual module ids to resolvable files', () => { + expect( + getVirtualModuleFilePath('virtual/react-router/browser-manifest') + ).toBe('node_modules/virtual/react-router/browser-manifest.js'); + expect( + getVirtualModuleFilePath('virtual/react-router/server-build-edge') + ).toBe('node_modules/virtual/react-router/server-build-edge.js'); + + expect(() => + getVirtualModuleFilePath('virtual/react-router/../server-build') + ).toThrow('Invalid virtual module id'); + expect(() => + getVirtualModuleFilePath('virtual/other/server-build') + ).toThrow('Virtual module id must start'); }); }); @@ -109,13 +160,21 @@ describe('pluginReactRouter', () => { expect(routeTransform).toBeDefined(); }); - it('should register build and dot file transforms', async () => { + it('should register build, dot file, and route chunk transforms by default', async () => { + const readFileSync = rstest + .spyOn(fs, 'readFileSync') + .mockReturnValue('export default function Route() { return null; }'); const rsbuild = await createStubRsbuild({ + action: 'build', rsbuildConfig: {}, }); - const plugin = pluginReactRouter(); - await plugin.setup(rsbuild as any); + try { + const plugin = pluginReactRouter(); + await plugin.setup(rsbuild as any); + } finally { + readFileSync.mockRestore(); + } const calls = (rsbuild.transform as any).mock.calls.map( (call: any[]) => call[0] @@ -129,16 +188,64 @@ describe('pluginReactRouter', () => { ).toBe(true); expect( - calls.some((call: any) => call.test?.toString().includes('\\.server')) + calls.some( + (call: any) => + call.resourceQuery?.toString().includes('route-chunk=') && + call.environments?.includes('web') + ) + ).toBe(true); + + const splitRouteExportsTransform = calls.find( + (call: any) => + typeof call.test === 'function' && + call.resourceQuery?.not?.toString().includes('route-chunk=') && + call.environments?.includes('web') + ); + expect(splitRouteExportsTransform).toBeDefined(); + expect( + splitRouteExportsTransform.test(path.resolve('app/routes/index.tsx')) + ).toBe(true); + expect(splitRouteExportsTransform.test(path.resolve('app/other.tsx'))).toBe( + false + ); + + expect( + calls.some( + (call: any) => + call.test?.toString().includes('\\.server') && + call.environments?.includes('web') + ) ).toBe(true); expect( - calls.some((call: any) => call.test?.toString().includes('\\.client')) + calls.some( + (call: any) => + call.test?.toString().includes('\\.client') && + call.environments?.includes('node') + ) ).toBe(true); }); }); describe('asset handling', () => { + it('should treat non-CSS ?url imports as emitted assets in web and node builds', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + const getRules = (name: 'web' | 'node') => + config.environments?.[name]?.tools?.rspack?.module?.rules ?? []; + const hasUrlAssetRule = (rule: any) => + rule.resourceQuery?.toString().includes('url') && + rule.exclude?.test('app/styles.css') && + rule.type === 'asset/resource'; + + expect(getRules('web').some(hasUrlAssetRule)).toBe(true); + expect(getRules('node').some(hasUrlAssetRule)).toBe(true); + }); + it('should emit package.json for node environment', async () => { const rsbuild = await createStubRsbuild({ rsbuildConfig: {}, diff --git a/tests/fixtures/dev-runtime/app/entry.client.tsx b/tests/fixtures/dev-runtime/app/entry.client.tsx new file mode 100644 index 0000000..58f3534 --- /dev/null +++ b/tests/fixtures/dev-runtime/app/entry.client.tsx @@ -0,0 +1,7 @@ +import { startTransition } from 'react'; +import { hydrateRoot } from 'react-dom/client'; +import { HydratedRouter } from 'react-router/dom'; + +startTransition(() => { + hydrateRoot(document, ); +}); diff --git a/tests/fixtures/dev-runtime/app/entry.server.tsx b/tests/fixtures/dev-runtime/app/entry.server.tsx new file mode 100644 index 0000000..0249f81 --- /dev/null +++ b/tests/fixtures/dev-runtime/app/entry.server.tsx @@ -0,0 +1,20 @@ +import { renderToString } from 'react-dom/server'; +import { ServerRouter, type EntryContext } from 'react-router'; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + routerContext: EntryContext +) { + responseHeaders.set('Content-Type', 'text/html'); + return new Response( + `${renderToString( + + )}`, + { + headers: responseHeaders, + status: responseStatusCode, + } + ); +} diff --git a/tests/fixtures/dev-runtime/app/root.tsx b/tests/fixtures/dev-runtime/app/root.tsx new file mode 100644 index 0000000..1dbb6b6 --- /dev/null +++ b/tests/fixtures/dev-runtime/app/root.tsx @@ -0,0 +1,14 @@ +import { Outlet } from 'react-router'; + +export default function Root() { + return ( + + + + + + + + + ); +} diff --git a/tests/fixtures/dev-runtime/app/routes.ts b/tests/fixtures/dev-runtime/app/routes.ts new file mode 100644 index 0000000..c26fe55 --- /dev/null +++ b/tests/fixtures/dev-runtime/app/routes.ts @@ -0,0 +1,6 @@ +import { index, route, type RouteConfig } from '@react-router/dev/routes'; + +export default [ + index('routes/index.tsx'), + route('other', 'routes/other.tsx'), +] satisfies RouteConfig; diff --git a/tests/fixtures/dev-runtime/app/routes/index.tsx b/tests/fixtures/dev-runtime/app/routes/index.tsx new file mode 100644 index 0000000..5b5390d --- /dev/null +++ b/tests/fixtures/dev-runtime/app/routes/index.tsx @@ -0,0 +1,5 @@ +export const handle = { marker: 'fixture' }; + +export default function IndexRoute() { + return

fixture

; +} diff --git a/tests/fixtures/dev-runtime/app/routes/other.tsx b/tests/fixtures/dev-runtime/app/routes/other.tsx new file mode 100644 index 0000000..18db7e8 --- /dev/null +++ b/tests/fixtures/dev-runtime/app/routes/other.tsx @@ -0,0 +1,3 @@ +export default function OtherRoute() { + return

other

; +} diff --git a/tests/fixtures/dev-runtime/react-router.config.ts b/tests/fixtures/dev-runtime/react-router.config.ts new file mode 100644 index 0000000..8520505 --- /dev/null +++ b/tests/fixtures/dev-runtime/react-router.config.ts @@ -0,0 +1,8 @@ +export default { + appDirectory: 'app', + buildDirectory: 'build', + routeDiscovery: { mode: 'lazy' }, + serverBundles: async ({ branch }) => + branch.at(-1)?.path === 'other' ? 'other' : 'index', + ssr: true, +}; diff --git a/tests/fixtures/dev-runtime/server/index.ts b/tests/fixtures/dev-runtime/server/index.ts new file mode 100644 index 0000000..4c3cb2a --- /dev/null +++ b/tests/fixtures/dev-runtime/server/index.ts @@ -0,0 +1 @@ +export const customServerMarker = 'custom-server-entry'; diff --git a/tests/index.test.ts b/tests/index.test.ts index 47c3e4b..c8cfe4b 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,6 +1,22 @@ import { createStubRsbuild } from '@scripts/test-helper'; -import { describe, expect, it } from '@rstest/core'; -import { pluginReactRouter } from '../src'; +import { describe, expect, it, rstest } from '@rstest/core'; +import * as fs from 'node:fs'; +import { pluginReactRouter, shouldParallelizeEnvironmentBuilds } from '../src'; + +const captureEnv = (keys: string[]) => { + const previousValues = new Map( + keys.map(key => [key, process.env[key]] as const) + ); + return () => { + for (const [key, value] of previousValues) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + }; +}; describe('pluginReactRouter', () => { it('should configure basic plugin options', async () => { @@ -15,6 +31,154 @@ describe('pluginReactRouter', () => { expect(config.dev.hmr).toBe(true); expect(config.dev.liveReload).toBe(true); expect(config.dev.writeToDisk).toBe(true); + expect(config.dev.lazyCompilation).toBeUndefined(); + }); + + it('adds the committed custom-server build entry only in development', async () => { + const devRsbuild = await createStubRsbuild({ rsbuildConfig: {} }); + devRsbuild.addPlugins([pluginReactRouter({ customServer: true })]); + const devConfig = await devRsbuild.unwrapConfig(); + + expect( + devConfig.environments.node.source.entry[ + 'static/js/react-router-server-build' + ] + ).toBe('virtual/react-router/server-build'); + + const buildRsbuild = await createStubRsbuild({ + action: 'build', + rsbuildConfig: {}, + }); + buildRsbuild.addPlugins([pluginReactRouter({ customServer: true })]); + const buildConfig = await buildRsbuild.unwrapConfig(); + + expect( + buildConfig.environments.node.source.entry[ + 'static/js/react-router-server-build' + ] + ).toBeUndefined(); + expect(buildRsbuild.onBeforeDevCompile).not.toHaveBeenCalled(); + expect(buildRsbuild.onAfterDevCompile).not.toHaveBeenCalled(); + expect(buildRsbuild.onAfterCreateCompiler).not.toHaveBeenCalled(); + }); + + it('should restart the dev server when route entries are added', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: { + dev: { + watchFiles: { + paths: 'custom.config.ts', + type: 'reload-server', + }, + }, + }, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.watchFiles).toEqual( + expect.arrayContaining([ + { + paths: 'custom.config.ts', + type: 'reload-server', + }, + { + paths: expect.stringMatching( + /react-router\.config\.[cm]?[jt]sx?$/ + ), + type: 'reload-server', + }, + { + paths: expect.stringMatching(/app\/routes\.[cm]?[jt]sx?$/), + type: 'reload-server', + }, + { + paths: expect.stringMatching( + /build\/client\/\.react-router\/route-watch$/ + ), + type: 'reload-server', + }, + ]) + ); + }); + + it('watches all supported config filenames when the config does not exist yet', async () => { + const existsSyncMock = fs.existsSync as unknown as { + mockImplementation: (implementation: (path: unknown) => boolean) => void; + mockReturnValue: (value: boolean) => void; + }; + existsSyncMock.mockImplementation( + path => !String(path).includes('react-router.config') + ); + + try { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + const configWatch = config.dev.watchFiles.find( + (watchFile: { paths: unknown }) => Array.isArray(watchFile.paths) + ); + + expect(configWatch).toMatchObject({ + paths: expect.arrayContaining([ + expect.stringMatching(/react-router\.config\.tsx$/), + expect.stringMatching(/react-router\.config\.ts$/), + expect.stringMatching(/react-router\.config\.jsx$/), + expect.stringMatching(/react-router\.config\.js$/), + expect.stringMatching(/react-router\.config\.mjs$/), + expect.stringMatching(/react-router\.config\.mts$/), + ]), + type: 'reload-server', + }); + } finally { + existsSyncMock.mockReturnValue(true); + } + }); + + it('lets custom route topology callbacks own route restart handling', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([ + pluginReactRouter({ + onRouteTopologyChange: () => {}, + }), + ]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.watchFiles).toEqual( + expect.arrayContaining([ + { + paths: expect.stringMatching( + /react-router\.config\.[cm]?[jt]sx?$/ + ), + type: 'reload-server', + }, + ]) + ); + expect(config.dev.watchFiles).not.toEqual( + expect.arrayContaining([ + { + paths: expect.stringMatching(/app\/routes\.[cm]?[jt]sx?$/), + type: 'reload-server', + }, + ]) + ); + expect(config.dev.watchFiles).not.toEqual( + expect.arrayContaining([ + { + paths: expect.stringMatching( + /build\/client\/\.react-router\/route-watch$/ + ), + type: 'reload-server', + }, + ]) + ); }); it('should respect server output format', async () => { @@ -31,6 +195,209 @@ describe('pluginReactRouter', () => { expect(nodeConfig.output.module).toBe(false); }); + it('configures web entries to avoid unnecessary entry IIFEs', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect( + config.environments?.web?.tools?.rspack?.optimization?.avoidEntryIife + ).toBe(true); + }); + + it('reduces file size reporting overhead for medium split route builds by default', async () => { + const restoreEnv = captureEnv([ + 'RR_TEST_SPLIT_ROUTE_MODULES', + 'RR_TEST_ROUTE_COUNT', + ]); + process.env.RR_TEST_SPLIT_ROUTE_MODULES = 'true'; + process.env.RR_TEST_ROUTE_COUNT = '256'; + const readFileSync = rstest + .spyOn(fs, 'readFileSync') + .mockReturnValue('export default function Route() { return null; }'); + try { + const rsbuild = await createStubRsbuild({ + action: 'build', + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.performance?.printFileSize).toEqual({ + total: true, + detail: false, + compressed: false, + }); + } finally { + readFileSync.mockRestore(); + restoreEnv(); + } + }); + + it('reduces file size reporting overhead for medium route builds by default', async () => { + const restoreEnv = captureEnv(['RR_TEST_ROUTE_COUNT']); + process.env.RR_TEST_ROUTE_COUNT = '256'; + const readFileSync = rstest + .spyOn(fs, 'readFileSync') + .mockReturnValue('export default function Route() { return null; }'); + try { + const rsbuild = await createStubRsbuild({ + action: 'build', + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.performance?.printFileSize).toEqual({ + total: true, + detail: false, + compressed: false, + }); + } finally { + readFileSync.mockRestore(); + restoreEnv(); + } + }); + + it('keeps explicit object file size reporting config for large split route builds', async () => { + const restoreEnv = captureEnv([ + 'RR_TEST_SPLIT_ROUTE_MODULES', + 'RR_TEST_ROUTE_COUNT', + ]); + process.env.RR_TEST_SPLIT_ROUTE_MODULES = 'true'; + process.env.RR_TEST_ROUTE_COUNT = '1024'; + const readFileSync = rstest + .spyOn(fs, 'readFileSync') + .mockReturnValue('export default function Route() { return null; }'); + try { + const rsbuild = await createStubRsbuild({ + action: 'build', + rsbuildConfig: { + performance: { + printFileSize: { + detail: true, + compressed: true, + }, + }, + }, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.performance?.printFileSize).toEqual({ + detail: true, + compressed: true, + }); + } finally { + readFileSync.mockRestore(); + restoreEnv(); + } + }); + + it('should forward lazy compilation when explicitly configured', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([ + pluginReactRouter({ + lazyCompilation: { + entries: true, + imports: true, + }, + }), + ]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.lazyCompilation).toMatchObject({ + entries: true, + imports: true, + }); + expect( + config.dev.lazyCompilation.test({ + resource: '/project/app/root.tsx?__react-router-build-client-route', + }) + ).toBe(false); + expect( + config.dev.lazyCompilation.test({ + resource: '/project/app/components/card.tsx', + }) + ).toBe(true); + }); + + it('should allow lazy compilation to be enabled with a boolean', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter({ lazyCompilation: true })]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.lazyCompilation).toMatchObject({ + entries: true, + imports: true, + }); + expect( + config.dev.lazyCompilation.test({ + resource: `${process.cwd()}/app/entry.client.tsx`, + }) + ).toBe(false); + }); + + it('guards direct Rsbuild lazy compilation config for React Router hydration entries', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: { + dev: { + lazyCompilation: { + entries: true, + imports: false, + test: /app/, + }, + }, + }, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.lazyCompilation).toMatchObject({ + entries: true, + imports: false, + }); + expect( + config.dev.lazyCompilation.test({ + resource: '/project/app/routes/home.tsx?__react-router-build-client-route', + }) + ).toBe(false); + expect( + config.dev.lazyCompilation.test({ + resource: '/project/app/components/card.tsx', + }) + ).toBe(true); + expect( + config.dev.lazyCompilation.test({ + resource: '/project/vendor/react.tsx', + }) + ).toBe(false); + }); + + it('should allow lazy compilation to be disabled', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter({ lazyCompilation: false })]); + const config = await rsbuild.unwrapConfig(); + + expect(config.dev.lazyCompilation).toBe(false); + }); + it('should configure web environment correctly', async () => { const rsbuild = await createStubRsbuild({ rsbuildConfig: {}, @@ -44,6 +411,18 @@ describe('pluginReactRouter', () => { expect(webConfig.externalsType).toBe('module'); expect(webConfig.output.chunkFormat).toBe('module'); expect(webConfig.output.module).toBe(true); + + const webEntries = config.environments?.web?.source?.entry; + expect(webEntries['entry.client']).toEqual( + expect.stringMatching(/entry\.client/) + ); + expect(webEntries['virtual/react-router/browser-manifest']).toEqual({ + import: 'virtual/react-router/browser-manifest', + html: false, + }); + expect(webEntries['routes/index']).toMatchObject({ + html: false, + }); }); it('should configure node environment correctly', async () => { @@ -59,6 +438,45 @@ describe('pluginReactRouter', () => { expect(nodeConfig.experiments.outputModule).toBe(true); }); + it('should apply the resolved development compiler dependency policy', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + const nodeConfig = config.environments?.node?.tools?.rspack; + expect(nodeConfig.dependencies).toEqual( + shouldParallelizeEnvironmentBuilds({ isBuild: false }) + ? undefined + : ['web'] + ); + }); + + it.each([ + [{ isBuild: false, spareCoreCount: 4 }, true], + [{ isBuild: false, spareCoreCount: 3 }, false], + [{ isBuild: false, spareCoreCount: 1 }, false], + [{ isBuild: false, spareCoreCount: 0 }, false], + [{ isBuild: true, spareCoreCount: 8 }, false], + ])('should resolve parallel environment build mode', (options, expected) => { + expect(shouldParallelizeEnvironmentBuilds(options)).toBe(expected); + }); + + it('should keep the node compiler dependent on web during production builds', async () => { + const rsbuild = await createStubRsbuild({ + action: 'build', + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + const nodeConfig = config.environments?.node?.tools?.rspack; + expect(nodeConfig.dependencies).toEqual(['web']); + }); + it('should use async-node target for federation builds', async () => { const rsbuild = await createStubRsbuild({ rsbuildConfig: { diff --git a/tests/lazy-compilation.test.ts b/tests/lazy-compilation.test.ts new file mode 100644 index 0000000..699bc3d --- /dev/null +++ b/tests/lazy-compilation.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, it } from '@rstest/core'; +import { guardReactRouterLazyCompilation } from '../src/lazy-compilation'; + +describe('guardReactRouterLazyCompilation', () => { + const entryClientPath = '/project/app/entry.client.tsx'; + + it('leaves disabled and unspecified lazy compilation unchanged', () => { + expect( + guardReactRouterLazyCompilation({ + lazyCompilation: undefined, + entryClientPath, + }) + ).toBeUndefined(); + expect( + guardReactRouterLazyCompilation({ + lazyCompilation: false, + entryClientPath, + }) + ).toBe(false); + }); + + it('keeps boolean lazy compilation enabled while guarding hydration modules', () => { + const guarded = guardReactRouterLazyCompilation({ + lazyCompilation: true, + entryClientPath, + }); + + expect(guarded).toMatchObject({ + entries: true, + imports: true, + }); + expect( + guarded?.test?.({ + resource: `${entryClientPath}!lazy-compilation-proxy`, + }) + ).toBe(false); + expect( + guarded?.test?.({ + resource: '/project/app/components/card.tsx', + }) + ).toBe(true); + }); + + it('preserves user tests for non-React Router hydration modules', () => { + const guarded = guardReactRouterLazyCompilation({ + lazyCompilation: { + entries: true, + imports: false, + test: /app/g, + }, + entryClientPath, + }); + + expect(guarded).toMatchObject({ + entries: true, + imports: false, + }); + expect( + guarded?.test?.({ + resource: '/project/app/root.tsx?__react-router-build-client-route', + }) + ).toBe(false); + expect( + guarded?.test?.({ + resource: '/project/app/components/card.tsx', + }) + ).toBe(true); + expect( + guarded?.test?.({ + resource: '/project/vendor/react.tsx', + }) + ).toBe(false); + }); + + it('guards all React Router hydration-critical module shapes', () => { + const guarded = guardReactRouterLazyCompilation({ + lazyCompilation: { + entries: true, + imports: true, + }, + entryClientPath, + }); + + expect( + guarded?.test?.({ + request: 'virtual/react-router/browser-manifest', + }) + ).toBe(false); + expect( + guarded?.test?.({ + identifier: () => + '/project/app/routes/home.tsx?__react-router-build-client-route', + }) + ).toBe(false); + expect( + guarded?.test?.({ + nameForCondition: () => + '/project/app/routes/home.tsx?react-router-route', + }) + ).toBe(false); + }); +}); diff --git a/tests/manifest-split-route-modules.test.ts b/tests/manifest-split-route-modules.test.ts index 8c0579e..1e2107b 100644 --- a/tests/manifest-split-route-modules.test.ts +++ b/tests/manifest-split-route-modules.test.ts @@ -3,9 +3,36 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from '@rstest/core'; import { getReactRouterManifestForDev } from '../src/manifest'; -import { getRouteChunkEntryName } from '../src/route-chunks'; +import { + getRouteChunkEntryName, + routeChunkExportNames, + type RouteChunkExportName, +} from '../src/route-chunks'; -const createTempApp = () => { +const clientExportFixtures: Record = { + clientAction: `export async function clientAction() { return {}; }`, + clientLoader: `export async function clientLoader() { return {}; }`, + clientMiddleware: `export async function clientMiddleware() { return null; }`, + HydrateFallback: `export function HydrateFallback() { return null; }`, +}; + +type ManifestModuleField = + | 'clientActionModule' + | 'clientLoaderModule' + | 'clientMiddlewareModule' + | 'hydrateFallbackModule'; + +const moduleFieldByExportName: Record< + RouteChunkExportName, + ManifestModuleField +> = { + clientAction: 'clientActionModule', + clientLoader: 'clientLoaderModule', + clientMiddleware: 'clientMiddlewareModule', + HydrateFallback: 'hydrateFallbackModule', +}; + +const createTempApp = (routeCode?: string, rootCode?: string) => { const root = mkdtempSync(join(tmpdir(), 'rr-manifest-')); const appDir = join(root, 'app'); const routesDir = join(appDir, 'routes'); @@ -13,102 +40,156 @@ const createTempApp = () => { writeFileSync( join(appDir, 'root.tsx'), - `export default function Root() { return null; }` + rootCode ?? `export default function Root() { return null; }` ); writeFileSync( join(routesDir, 'clients.tsx'), - `export async function clientAction() { return {}; } - export async function clientLoader() { return {}; } - export default function Clients() { return null; }` + routeCode ?? + `export async function clientAction() { return {}; } + export async function clientLoader() { return {}; } + export default function Clients() { return null; }` ); - return { root, appDir, routesDir }; + return { root, appDir }; +}; + +const routes = { + root: { id: 'root', file: 'root.tsx', path: '' }, + 'routes/clients': { + id: 'routes/clients', + parentId: 'root', + file: 'routes/clients.tsx', + path: 'clients', + }, +}; + +const createClientStats = (routeId = 'routes/clients') => { + const assetsByChunkName: Record = { + 'entry.client': ['static/js/entry.client.js'], + [routeId]: [`static/js/${routeId}.js`], + }; + for (const exportName of routeChunkExportNames) { + assetsByChunkName[getRouteChunkEntryName(routeId, exportName)] = [ + `static/js/${getRouteChunkEntryName(routeId, exportName)}.js`, + ]; + } + return { assetsByChunkName }; }; +const getManifest = async ( + appDir: string, + splitRouteModules: boolean | 'enforce', + isBuild = true +) => + getReactRouterManifestForDev(routes, {}, createClientStats(), appDir, '/', { + splitRouteModules, + rootRouteFile: 'root.tsx', + isBuild, + cache: new Map(), + }); + describe('manifest split route modules', () => { - it('includes clientActionModule when split route modules are enabled for build', async () => { + it.each(routeChunkExportNames)( + 'includes %sModule when the export is splittable in build mode', + async (exportName: RouteChunkExportName) => { + const { root, appDir } = createTempApp(` + ${clientExportFixtures[exportName]} + export default function Clients() { return null; } + `); + try { + const manifest = await getManifest(appDir, true); + const field = moduleFieldByExportName[exportName]; + + expect(manifest.routes['routes/clients'][field]).toBe( + `/static/js/${getRouteChunkEntryName('routes/clients', exportName)}.js` + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } + } + ); + + it('omits split route module fields in dev mode', async () => { const { root, appDir } = createTempApp(); try { - const routes = { - root: { id: 'root', file: 'root.tsx', path: '' }, - 'routes/clients': { - id: 'routes/clients', - parentId: 'root', - file: 'routes/clients.tsx', - path: 'clients', - }, - }; - - const clientActionEntry = getRouteChunkEntryName( - 'routes/clients', - 'clientAction' - ); + const manifest = await getManifest(appDir, true, false); - const clientStats: { assetsByChunkName: Record } = { - assetsByChunkName: { - 'routes/clients': ['static/js/routes/clients.js'], - [clientActionEntry]: ['static/js/routes/clients-client-action.js'], - }, - }; - - const manifest = await getReactRouterManifestForDev( - routes, - {}, - clientStats, - appDir, - '/', - { - splitRouteModules: true, - rootRouteFile: 'root.tsx', - isBuild: true, - } - ); + expect(manifest.routes['routes/clients'].clientActionModule).toBeUndefined(); + expect(manifest.routes['routes/clients'].clientLoaderModule).toBeUndefined(); + expect( + manifest.routes['routes/clients'].clientMiddlewareModule + ).toBeUndefined(); + expect( + manifest.routes['routes/clients'].hydrateFallbackModule + ).toBeUndefined(); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('omits a module field for a client export that is present but not splittable', async () => { + const { root, appDir } = createTempApp(` + const shared = () => null; + export default function Clients() { return shared(); } + export async function clientAction() { return shared(); } + `); + try { + const manifest = await getManifest(appDir, true); expect(manifest.routes['routes/clients'].hasClientAction).toBe(true); - expect(manifest.routes['routes/clients'].clientActionModule).toBe( - '/static/js/routes/clients-client-action.js' - ); + expect(manifest.routes['routes/clients'].clientActionModule).toBeUndefined(); } finally { rmSync(root, { recursive: true, force: true }); } }); - it('omits split route module fields in dev mode', async () => { - const { root, appDir } = createTempApp(); + it('throws in enforce mode when a present client export is not splittable', async () => { + const { root, appDir } = createTempApp(` + const shared = () => null; + export default function Clients() { return shared(); } + export async function clientAction() { return shared(); } + `); try { - const routes = { - root: { id: 'root', file: 'root.tsx', path: '' }, - 'routes/clients': { - id: 'routes/clients', - parentId: 'root', - file: 'routes/clients.tsx', - path: 'clients', - }, - }; - - const clientStats: { assetsByChunkName: Record } = { - assetsByChunkName: { - 'routes/clients': ['static/js/routes/clients.js'], - }, - }; - - const manifest = await getReactRouterManifestForDev( - routes, - {}, - clientStats, - appDir, - '/', - { - splitRouteModules: true, - rootRouteFile: 'root.tsx', - isBuild: false, - } + await expect(getManifest(appDir, 'enforce')).rejects.toThrowError( + /Error splitting route module[\s\S]*clientAction/ ); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it('does not throw outside enforce mode when a present client export is not splittable', async () => { + const { root, appDir } = createTempApp(` + const shared = () => null; + export default function Clients() { return shared(); } + export async function clientAction() { return shared(); } + `); + try { + const manifest = await getManifest(appDir, true); + + expect(manifest.routes['routes/clients'].hasClientAction).toBe(true); expect(manifest.routes['routes/clients'].clientActionModule).toBeUndefined(); - expect(manifest.routes['routes/clients'].clientLoaderModule).toBeUndefined(); } finally { rmSync(root, { recursive: true, force: true }); } }); -}); + + it('does not add route chunk module fields for the root route', async () => { + const { root, appDir } = createTempApp( + `export default function Clients() { return null; }`, + `export async function clientAction() { return {}; } + export default function Root() { return null; }` + ); + try { + const manifest = await getManifest(appDir, true); + + expect(manifest.routes.root.hasClientAction).toBe(true); + expect(manifest.routes.root.clientActionModule).toBeUndefined(); + expect(manifest.routes.root.clientLoaderModule).toBeUndefined(); + expect(manifest.routes.root.clientMiddlewareModule).toBeUndefined(); + expect(manifest.routes.root.hydrateFallbackModule).toBeUndefined(); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); \ No newline at end of file diff --git a/tests/manifest.test.ts b/tests/manifest.test.ts index a17204e..17b8ead 100644 --- a/tests/manifest.test.ts +++ b/tests/manifest.test.ts @@ -1,7 +1,159 @@ +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import { describe, expect, it } from '@rstest/core'; -import { configRoutesToRouteManifest } from '../src/manifest'; +import { + createReactRouterManifestStats, + configRoutesToRouteManifest, + configRoutesToRouteManifestEntries, + generateReactRouterManifestForDev, + getReactRouterManifestForDev, + getReactRouterManifestChunkNames, +} from '../src/manifest'; + +const createTempApp = (routeCode: string) => { + const root = mkdtempSync(join(tmpdir(), 'rr-manifest-')); + const appDir = join(root, 'app'); + const routesDir = join(appDir, 'routes'); + mkdirSync(routesDir, { recursive: true }); + + writeFileSync( + join(appDir, 'root.tsx'), + `export default function Root() { return null; }` + ); + writeFileSync(join(routesDir, 'page.tsx'), routeCode); + + return { root, appDir }; +}; + +const routes = { + root: { id: 'root', file: 'root.tsx', path: '' }, + 'routes/page': { + id: 'routes/page', + parentId: 'root', + file: 'routes/page.tsx', + path: 'page', + }, +}; + +const clientStats = { + assetsByChunkName: { + 'entry.client': ['static/js/entry.client.js'], + root: ['static/js/root.js'], + 'routes/page': ['static/js/routes/page.js'], + }, +}; describe('manifest', () => { + it('creates manifest stats from named chunks without stats JSON', () => { + const compilation = { + namedChunks: new Map([ + [ + 'runtime', + { + files: new Set(['static/js/runtime.js']), + }, + ], + [ + 'entry.client', + { + files: new Set([ + 'static/js/entry.client.js', + 'static/css/entry.client.css', + ]), + }, + ], + [ + 'routes/page', + { + files: new Set(['static/js/routes/page.js']), + }, + ], + ]), + }; + + expect(createReactRouterManifestStats(compilation)).toEqual({ + assetsByChunkName: { + runtime: ['static/js/runtime.js'], + 'entry.client': [ + 'static/js/entry.client.js', + 'static/css/entry.client.css', + ], + 'routes/page': ['static/js/routes/page.js'], + }, + }); + }); + + it('filters manifest stats to requested chunk names', () => { + const compilation = { + namedChunks: new Map([ + ['runtime', { files: new Set(['static/js/runtime.js']) }], + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['routes/page', { files: new Set(['static/js/routes/page.js']) }], + ['vendor', { files: new Set(['static/js/vendor.js']) }], + ]), + }; + + expect( + createReactRouterManifestStats( + compilation, + new Set(['entry.client', 'routes/page']) + ) + ).toEqual({ + assetsByChunkName: { + 'entry.client': ['static/js/entry.client.js'], + 'routes/page': ['static/js/routes/page.js'], + }, + }); + }); + + it('uses direct named chunk lookup for filtered manifest stats when available', () => { + const chunks = new Map([ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['routes/page', { files: new Set(['static/js/routes/page.js']) }], + ]); + const compilation = { + namedChunks: { + get: (chunkName: string) => chunks.get(chunkName), + *[Symbol.iterator](): IterableIterator< + [string, { files: Set }] + > { + throw new Error('filtered manifest stats should not scan all chunks'); + }, + }, + }; + + expect( + createReactRouterManifestStats( + compilation, + new Set(['entry.client', 'routes/page']) + ) + ).toEqual({ + assetsByChunkName: { + 'entry.client': ['static/js/entry.client.js'], + 'routes/page': ['static/js/routes/page.js'], + }, + }); + }); + + it('collects only manifest-readable chunk names', () => { + expect(Array.from(getReactRouterManifestChunkNames(routes, false))).toEqual( + ['entry.client', 'root', 'routes/page'] + ); + + expect(getReactRouterManifestChunkNames(routes, true)).toEqual( + new Set([ + 'entry.client', + 'root', + 'routes/page', + 'routes/page-client-action', + 'routes/page-client-loader', + 'routes/page-client-middleware', + 'routes/page-hydrate-fallback', + ]) + ); + }); + describe('configRoutesToRouteManifest', () => { it('should convert simple route config to manifest', () => { const routeConfig = [ @@ -21,6 +173,25 @@ describe('manifest', () => { expect(result['routes/home'].index).toBe(true); }); + it('preserves declaration order in route manifest entries', () => { + const routeConfig = [ + { + id: '2', + file: 'routes/two.tsx', + path: ':value', + }, + { + id: '1', + file: 'routes/one.tsx', + path: ':value', + }, + ]; + + const result = configRoutesToRouteManifestEntries('app', routeConfig); + + expect(result.map(([id]) => id)).toEqual(['2', '1']); + }); + it('should handle nested routes with parentId', () => { const routeConfig = [ { @@ -172,4 +343,67 @@ describe('manifest', () => { expect(item).toHaveProperty('hasClientMiddleware', false); expect(item).toHaveProperty('hasDefaultExport', false); }); + + it('tracks route exports outside the manifest payload', async () => { + const { root, appDir } = createTempApp(` + export function headers() { return {}; } + export async function action() { return null; } + export async function loader() { return null; } + export default function Page() { return null; } + `); + try { + const { manifest, moduleExportsByRouteId } = + await generateReactRouterManifestForDev( + routes, + {}, + clientStats, + appDir, + '/', + { + isBuild: true, + rootRouteFile: 'root.tsx', + splitRouteModules: false, + } + ); + + const routeManifest = manifest.routes['routes/page']; + expect(routeManifest).toMatchObject({ + hasAction: true, + hasLoader: true, + }); + expect(moduleExportsByRouteId['routes/page']).toEqual( + expect.arrayContaining(['headers', 'action', 'loader', 'default']) + ); + expect(routeManifest).not.toHaveProperty('headers'); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('preserves dev css fallback when route analysis uses transformed code', async () => { + const { root, appDir } = createTempApp(` + import './page.css'; + export default function Page() { return

Page

; } + `); + try { + const manifest = await getReactRouterManifestForDev( + routes, + {}, + clientStats, + appDir, + '/', + { + isBuild: false, + rootRouteFile: 'root.tsx', + splitRouteModules: false, + } + ); + + expect(manifest.routes['routes/page'].css).toEqual([ + '/static/css/routes/page.css', + ]); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); }); diff --git a/tests/modify-browser-manifest.test.ts b/tests/modify-browser-manifest.test.ts new file mode 100644 index 0000000..848d35d --- /dev/null +++ b/tests/modify-browser-manifest.test.ts @@ -0,0 +1,422 @@ +import { createHash } from 'node:crypto'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { describe, expect, it } from '@rstest/core'; +import { registerModifyBrowserManifestAssets } from '../src/modify-browser-manifest'; + +const BROWSER_MANIFEST_PATH = + 'static/js/virtual/react-router/browser-manifest.js'; +const PLACEHOLDER_MANIFEST_SOURCE = + 'window.__reactRouterManifest="PLACEHOLDER";'; + +const createTempApp = () => { + const root = mkdtempSync(join(tmpdir(), 'rr-modify-manifest-')); + const appDir = join(root, 'app'); + mkdirSync(join(appDir, 'routes'), { recursive: true }); + writeFileSync( + join(appDir, 'root.tsx'), + `export default function Root() { return null; }` + ); + writeFileSync( + join(appDir, 'routes/page.tsx'), + `export default function Page() { return null; }` + ); + return { root, appDir }; +}; + +const createAsset = (source: string) => ({ + source: () => source, + size: () => source.length, +}); + +class RawSource { + constructor(private readonly value: string) {} + source() { + return this.value; + } + size() { + return this.value.length; + } +} + +type Asset = ReturnType; +type ProcessAssetsContext = { + assets: Record; + compilation: unknown; +}; +type ProcessAssetsDescriptor = { + stage: string; + environments?: string[]; +}; +type ProcessAssetsHandler = ( + context: ProcessAssetsContext & { sources: { RawSource: typeof RawSource } } +) => Promise | void; +type ProcessAssetsRegistration = { + descriptor: ProcessAssetsDescriptor; + handler: ProcessAssetsHandler; +}; + +const createProcessAssetsHarness = () => { + const registrations: ProcessAssetsRegistration[] = []; + + return { + api: { + processAssets( + processAssetsDescriptor: ProcessAssetsDescriptor, + processAssetsHandler: ProcessAssetsHandler + ) { + registrations.push({ + descriptor: processAssetsDescriptor, + handler: processAssetsHandler, + }); + }, + }, + getDescriptor: () => registrations[0]?.descriptor, + getDescriptors: () => + registrations.map(registration => registration.descriptor), + run(context: ProcessAssetsContext) { + const registration = registrations[0]; + expect(registration).toBeDefined(); + return registration!.handler({ ...context, sources: { RawSource } }); + }, + runStage(stage: string, context: ProcessAssetsContext) { + const registration = registrations.find( + registration => registration.descriptor.stage === stage + ); + expect(registration).toBeDefined(); + return registration!.handler({ ...context, sources: { RawSource } }); + }, + }; +}; + +const createCompilation = ( + namedChunks: Array<[string, unknown]>, + assets: Record = {}, + entrypoints: Array<[string, unknown]> = [] +) => ({ + namedChunks: new Map(namedChunks), + entrypoints: new Map(entrypoints), + assets, + getAsset(name: string) { + const asset = assets[name]; + return asset ? { name, source: asset } : undefined; + }, + updateAsset(name: string, source: Asset) { + assets[name] = source; + }, + emitAsset(name: string, source: Asset) { + assets[name] = source; + }, + getAssets() { + return Object.entries(assets).map(([name, source]) => ({ name, source })); + }, +}); + +const rootRoute = { id: 'root', file: 'root.tsx', path: '' }; + +const createRoutesWithPage = () => ({ + root: rootRoute, + 'routes/page': { + id: 'routes/page', + parentId: 'root', + file: 'routes/page.tsx', + path: 'page', + }, +}); + +const createBrowserManifestAssets = () => ({ + [BROWSER_MANIFEST_PATH]: createAsset(PLACEHOLDER_MANIFEST_SOURCE), +}); + +const createSriHash = (source: string) => + `sha384-${createHash('sha384').update(source).digest('base64')}`; + +describe('modify browser manifest plugin', () => { + it('registers browser manifest mutation with Rsbuild processAssets', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const assets = createBrowserManifestAssets(); + const compilation = createCompilation( + [ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['root', { files: new Set(['static/js/root.js']) }], + ], + assets + ); + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + { root: rootRoute }, + {}, + appDir + ); + + expect(harness.getDescriptor()).toEqual({ + stage: 'additions', + environments: ['web'], + }); + await harness.run({ assets, compilation }); + + expect(assets[BROWSER_MANIFEST_PATH].source()).toContain('routes'); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('reports the exact compilation that produced the manifest', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const assets = createBrowserManifestAssets(); + const compilation = createCompilation( + [ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['root', { files: new Set(['static/js/root.js']) }], + ], + assets + ); + let reportedCompilation: unknown; + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + { root: rootRoute }, + {}, + appDir, + '/', + undefined, + { + onManifest(_manifest, _sri, _exports, context) { + reportedCompilation = context.compilation; + }, + } + ); + + await harness.run({ assets, compilation }); + + expect(reportedCompilation).toBe(compilation); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('hashes build SRI after later asset stages mutate JavaScript', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const originalEntrySource = 'console.log("before optimize");'; + const optimizedEntrySource = 'console.log("after optimize");'; + const assets = { + ...createBrowserManifestAssets(), + 'static/js/entry.client.js': createAsset(originalEntrySource), + 'static/js/root.js': createAsset('console.log("root");'), + }; + const compilation = createCompilation( + [ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['root', { files: new Set(['static/js/root.js']) }], + ], + assets + ); + let reportedSri: Record | undefined; + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + { root: rootRoute }, + {}, + appDir, + '/', + { isBuild: true }, + { + future: { unstable_subResourceIntegrity: true }, + onManifest(_manifest, sri) { + reportedSri = sri; + }, + } + ); + + expect(harness.getDescriptors()).toEqual([ + { stage: 'additions', environments: ['web'] }, + { stage: 'report', environments: ['web'] }, + ]); + + await harness.runStage('additions', { assets, compilation }); + expect(reportedSri).toBeUndefined(); + + compilation.updateAsset( + 'static/js/entry.client.js', + createAsset(optimizedEntrySource) + ); + await harness.runStage('report', { assets, compilation }); + + expect(reportedSri?.['/static/js/entry.client.js']).toBe( + createSriHash(optimizedEntrySource) + ); + expect(reportedSri?.['/static/js/entry.client.js']).not.toBe( + createSriHash(originalEntrySource) + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('rejects the promise hook when build route analysis fails', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + writeFileSync(join(appDir, 'routes/page.tsx'), 'export const = broken;'); + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + createRoutesWithPage(), + {}, + appDir, + '/', + { isBuild: true } + ); + + await expect( + harness.run({ + assets: {}, + compilation: createCompilation([]), + }) + ).rejects.toThrow(); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('does not read ignored chunk files while creating manifest stats', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const assets = createBrowserManifestAssets(); + const ignoredChunk = {}; + Object.defineProperty(ignoredChunk, 'files', { + get() { + throw new Error('ignored chunk files should not be read'); + }, + }); + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + createRoutesWithPage(), + {}, + appDir + ); + + await harness.run({ + assets, + compilation: createCompilation([ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['root', { files: new Set(['static/js/root.js']) }], + ['routes/page', { files: new Set(['static/js/routes/page.js']) }], + ['vendor', ignoredChunk], + ]), + }); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('uses actual manifest chunk names instead of theoretical split route chunks', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const assets = createBrowserManifestAssets(); + const theoreticalSplitChunk = {}; + Object.defineProperty(theoreticalSplitChunk, 'files', { + get() { + throw new Error('theoretical split chunk files should not be read'); + }, + }); + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + createRoutesWithPage(), + {}, + appDir, + '/', + { + splitRouteModules: true, + rootRouteFile: 'root.tsx', + isBuild: true, + }, + { + manifestChunkNames: new Set(['entry.client', 'root', 'routes/page']), + } + ); + + await harness.run({ + assets, + compilation: createCompilation([ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['root', { files: new Set(['static/js/root.js']) }], + ['routes/page', { files: new Set(['static/js/routes/page.js']) }], + ['routes/page-client-loader', theoreticalSplitChunk], + ]), + }); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('adds transitive entrypoint CSS without adding transitive JavaScript preloads', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const assets = createBrowserManifestAssets(); + let manifest: unknown; + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + { root: rootRoute }, + {}, + appDir, + '/', + { isBuild: true }, + { + onManifest(nextManifest) { + manifest = nextManifest; + }, + } + ); + + await harness.run({ + assets, + compilation: createCompilation( + [ + ['entry.client', { files: new Set(['static/js/entry.client.js']) }], + ['vendor', { files: new Set(['static/js/vendor.js']) }], + ], + assets, + [ + [ + 'entry.client', + { + getFiles: () => [ + 'static/js/entry.client.js', + 'static/js/vendor.js', + 'static/css/reset.css', + 'static/css/route.css', + ], + }, + ], + ] + ), + }); + + expect(manifest).toMatchObject({ + entry: { + imports: ['/static/js/entry.client.js'], + css: ['/static/css/reset.css', '/static/css/route.css'], + }, + }); + expect((manifest as { entry: { imports: string[] } }).entry.imports).not + .toContain('/static/js/vendor.js'); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/parallel-route-transforms.test.ts b/tests/parallel-route-transforms.test.ts new file mode 100644 index 0000000..fdc4fe5 --- /dev/null +++ b/tests/parallel-route-transforms.test.ts @@ -0,0 +1,368 @@ +import { describe, expect, it } from '@rstest/core'; +import { mapWithConcurrency } from '../src/concurrency'; +import { getExportNames } from '../src/export-utils'; +import { + executeRouteTransformTask, + type RouteModuleTransformTask, +} from '../src/route-transform-tasks'; +import { + createRouteTransformExecutorForTesting, + createRouteTransformExecutor, + getDefaultWorkerCount, +} from '../src/parallel-route-transforms'; +import type { + WorkerRequest, + WorkerResponse, +} from '../src/parallel-route-transform-protocol'; +import type { RouteChunkConfig } from '../src/route-chunks'; + +const routeChunkConfig: RouteChunkConfig = { + splitRouteModules: true, + appDirectory: '/app', + rootRouteFile: 'root.tsx', +}; + +const disabledRouteChunkConfig: RouteChunkConfig = { + ...routeChunkConfig, + splitRouteModules: false, +}; + +const resourcePath = '/app/routes/demo.tsx'; +const createRouteModuleTask = ( + overrides: Partial> = {} +): RouteModuleTransformTask => ({ + kind: 'routeModule' as const, + code: ` + import { serverValue } from '../server-data.server'; + export async function loader() { return serverValue; } + export default function Route() { return null; } + `, + resource: `${resourcePath}?react-router-route`, + resourcePath, + environmentName: 'web', + sourceMaps: true, + ssr: true, + isBuild: false, + isSpaMode: false, + rootRoutePath: '/app/root.tsx', + ...overrides, +}); + +type FakeWorkerHandler = (value: any) => void; + +class FakeRouteTransformWorker { + readonly messages: WorkerRequest[] = []; + readonly handlers = new Map(); + terminateCalls = 0; + + on(event: string, handler: FakeWorkerHandler): this { + this.handlers.set(event, [...(this.handlers.get(event) ?? []), handler]); + return this; + } + + postMessage(message: WorkerRequest): void { + this.messages.push(message); + } + + async terminate(): Promise { + this.terminateCalls += 1; + return 0; + } + + emit(event: string, value: unknown): void { + for (const handler of this.handlers.get(event) ?? []) { + handler(value); + } + } +} + +describe('parallel route transforms', () => { + it.each([ + [1, 0], + [2, 0], + [3, 1], + [4, 1], + [5, 3], + [6, 4], + [8, 4], + [10, 4], + [12, 4], + [24, 4], + ])('caps default worker count by available CPUs', (cpus, workers) => { + expect(getDefaultWorkerCount(cpus)).toBe(workers); + }); + + it('maps work with a concurrency cap while preserving result order', async () => { + let active = 0; + let maxActive = 0; + const result = await mapWithConcurrency([3, 1, 2], 2, async value => { + active += 1; + maxActive = Math.max(maxActive, active); + await new Promise(resolve => setTimeout(resolve, value)); + active -= 1; + return value * 2; + }); + + expect(result).toEqual([6, 2, 4]); + expect(maxActive).toBeLessThanOrEqual(2); + }); + + it('rejects invalid explicit worker counts', () => { + expect(() => + createRouteTransformExecutor({ + parallelTransforms: 1.5, + }) + ).toThrow('must be false or a positive integer'); + }); + + it('honors an explicit worker count', async () => { + const executor = createRouteTransformExecutor({ + parallelTransforms: 2, + }); + + try { + const result = await executor.run(createRouteModuleTask()); + + expect(result.code).toContain('export default _withComponentProps'); + expect(result.code).not.toContain('loader'); + } finally { + await executor.close(); + } + }); + + it('runs route builds inline when parallel transforms are disabled', async () => { + const executor = createRouteTransformExecutor({ + parallelTransforms: false, + }); + + try { + const result = await executor.run(createRouteModuleTask()); + + expect(result.code).toContain('export default _withComponentProps'); + expect(result.code).not.toContain('loader'); + } finally { + await executor.close(); + } + }); + + it('does not create route transform workers until work is scheduled', async () => { + let createdWorkers = 0; + const worker = new FakeRouteTransformWorker(); + const executor = createRouteTransformExecutorForTesting( + { + parallelTransforms: 1, + }, + () => { + createdWorkers += 1; + return worker; + } + ); + + expect(createdWorkers).toBe(0); + + const pending = executor.run(createRouteModuleTask()); + expect(createdWorkers).toBe(1); + worker.emit('message', { + id: worker.messages[0]!.id, + ok: true, + result: { code: 'created lazily' }, + } satisfies WorkerResponse); + await expect(pending).resolves.toEqual({ code: 'created lazily' }); + + await executor.close(); + expect(worker.terminateCalls).toBe(1); + }); + + it('executes route client entry tasks through the shared task executor', async () => { + await expect( + executeRouteTransformTask({ + kind: 'routeClientEntry', + code: ` + export async function loader() { return null; } + export async function clientLoader() { return null; } + export default function Route() { return null; } + `, + resourcePath, + environmentName: 'web', + isBuild: false, + routeChunkConfig: disabledRouteChunkConfig, + }) + ).resolves.toEqual({ + code: `export { clientLoader, default } from "${resourcePath}?react-router-route";`, + }); + }); + + it('can execute route module tasks through worker-backed parallelism', async () => { + const executor = createRouteTransformExecutor({ + parallelTransforms: 2, + }); + + try { + const result = await executor.run(createRouteModuleTask()); + + expect(result.code).toContain('export default _withComponentProps'); + expect(result.code).not.toContain('loader'); + } finally { + await executor.close(); + } + }); + + it('produces identical build route modules when environments need the same output', async () => { + const executor = createRouteTransformExecutor({ + parallelTransforms: 2, + splitRouteModules: true, + }); + const task = createRouteModuleTask({ + code: ` + export async function clientLoader() { return null; } + export default function Route() { return null; } + `, + environmentName: 'node', + isBuild: true, + }); + + try { + const nodeResult = await executor.run(task); + const webResult = await executor.run({ + ...task, + environmentName: 'web', + }); + + expect(webResult).toEqual(nodeResult); + } finally { + await executor.close(); + } + }); + + it('keeps environment-specific build route module output isolated', async () => { + const executor = createRouteTransformExecutor({ + parallelTransforms: 2, + splitRouteModules: true, + }); + const task = createRouteModuleTask({ + environmentName: 'node', + isBuild: true, + }); + + try { + const nodeResult = await executor.run(task); + const webResult = await executor.run({ + ...task, + environmentName: 'web', + }); + + await expect(getExportNames(nodeResult.code)).resolves.toContain( + 'loader' + ); + await expect(getExportNames(webResult.code)).resolves.not.toContain( + 'loader' + ); + } finally { + await executor.close(); + } + }); + + it('isolates escaped server exports across build environments', async () => { + const executor = createRouteTransformExecutor({ + parallelTransforms: 2, + splitRouteModules: true, + }); + const task = createRouteModuleTask({ + code: String.raw` + const implementation = async () => null; + export { implementation as lo\u0061der }; + export default function Route() { return null; } + `, + environmentName: 'node', + isBuild: true, + }); + + try { + const nodeResult = await executor.run(task); + const webResult = await executor.run({ + ...task, + environmentName: 'web', + }); + + expect(nodeResult.code).toContain('loader'); + expect(webResult.code).not.toContain('loader'); + } finally { + await executor.close(); + } + }); + + it('preserves runtime TypeScript for the downstream Rsbuild SWC stage', async () => { + const result = await executeRouteTransformTask( + createRouteModuleTask({ + code: ` + export enum Status { Active } + export default function Route() { return Status.Active; } + `, + environmentName: 'node', + isBuild: true, + }) + ); + + expect(result.code).toContain('enum Status'); + expect(result.code).toContain('Status.Active'); + }); + + it('preserves value imports when web route modules have no server-only exports', async () => { + const result = await executeRouteTransformTask( + createRouteModuleTask({ + code: ` + import { setup } from './side-effect'; + export default function Route() { return null; } + `, + environmentName: 'web', + ssr: false, + isBuild: true, + }) + ); + + expect(result.code).toContain(`import { setup } from './side-effect';`); + }); + + it('rejects invalid SPA route module exports from the route transform AST', async () => { + await expect( + executeRouteTransformTask( + createRouteModuleTask({ + code: ` + export async function action() { return null; } + export default function Route() { return null; } + `, + ssr: false, + isSpaMode: true, + }) + ) + ).rejects.toThrow('SPA Mode: 1 invalid route export'); + }); + + it('generates route module source maps when the environment requests them', async () => { + const task = createRouteModuleTask({ + code: ` + export async function loader() { return null; } + export default function Route() { return null; } + `, + }); + + const buildResult = await executeRouteTransformTask({ + ...task, + isBuild: true, + }); + expect(buildResult.map).not.toBeNull(); + + const devResult = await executeRouteTransformTask({ + ...task, + isBuild: false, + }); + + expect(devResult.map).not.toBeNull(); + + const withoutSourceMaps = await executeRouteTransformTask({ + ...task, + sourceMaps: false, + }); + expect(withoutSourceMaps.map).toBeNull(); + }); +}); diff --git a/tests/performance.test.ts b/tests/performance.test.ts new file mode 100644 index 0000000..ab9179d --- /dev/null +++ b/tests/performance.test.ts @@ -0,0 +1,276 @@ +import { describe, expect, it } from '@rstest/core'; +import { createReactRouterPerformanceProfiler } from '../src/performance'; + +const parsePerformanceReport = (message: string) => { + const prefix = '[react-router:performance] '; + expect(message.startsWith(prefix)).toBe(true); + return JSON.parse(message.slice(prefix.length)); +}; + +describe('React Router performance profiler', () => { + it('aggregates operation timings by environment and logs structured JSON', async () => { + const logs: string[] = []; + const profiler = createReactRouterPerformanceProfiler({ + enabled: true, + log: message => logs.push(message), + }); + + await profiler.record('web', 'route:client-entry', 'app/routes/a.tsx', async () => { + return 'client-entry'; + }); + await profiler.record('web', 'route:client-entry', 'app/routes/b.tsx', async () => { + return 'client-entry'; + }); + await profiler.record('node', 'route:module', 'app/routes/a.tsx', async () => { + return 'route-module'; + }); + profiler.recordSync('web', 'manifest:stage', 'virtual/react-router/browser-manifest', () => { + return 'manifest'; + }); + + profiler.flush('web', { compilerLifecycleMs: 123.4 }); + + expect(logs).toHaveLength(1); + expect(logs[0]).toContain('[react-router:performance]'); + + const report = parsePerformanceReport(logs[0]); + expect(report.environment).toBe('web'); + expect(report.compilerLifecycleMs).toBe(123.4); + expect(report.operations['route:client-entry'].count).toBe(2); + expect(report.operations['route:client-entry'].slowest).toHaveLength(2); + expect(report.operations['manifest:stage'].count).toBe(1); + expect(report.operations['route:module']).toBeUndefined(); + + await profiler.record('web', 'route:client-entry', 'app/routes/c.tsx', async () => { + return 'client-entry'; + }); + profiler.flush('web'); + + expect(logs).toHaveLength(2); + const secondReport = parsePerformanceReport(logs[1]); + expect(secondReport.operations['route:client-entry'].count).toBe(1); + expect(secondReport.operations['manifest:stage']).toBeUndefined(); + }); + + it('reports interval-union wall time without changing summed timing fields', async () => { + const logs: string[] = []; + const originalNow = performance.now; + let now = 0; + let resolveFirst: (value: string) => void = () => {}; + let resolveSecond: (value: string) => void = () => {}; + const profiler = createReactRouterPerformanceProfiler({ + enabled: true, + log: message => logs.push(message), + }); + + try { + performance.now = () => now; + + const first = profiler.record('web', 'route:module', 'app/routes/a.tsx', () => { + return new Promise(resolve => { + resolveFirst = resolve; + }); + }); + + now = 10; + const second = profiler.record('web', 'route:module', 'app/routes/b.tsx', () => { + return new Promise(resolve => { + resolveSecond = resolve; + }); + }); + + now = 25; + resolveSecond('second'); + await second; + + now = 40; + resolveFirst('first'); + await first; + + profiler.flush('web'); + + const report = parsePerformanceReport(logs[0]); + expect(report.operations['route:module']).toMatchObject({ + count: 2, + totalMs: 55, + wallMs: 40, + maxMs: 40, + }); + expect(report.operations['route:module'].slowest).toEqual([ + { durationMs: 40, resource: 'app/routes/a.tsx' }, + { durationMs: 15, resource: 'app/routes/b.tsx' }, + ]); + } finally { + performance.now = originalNow; + } + }); + + it('keeps only the five slowest operation entries in descending order', () => { + const logs: string[] = []; + const originalNow = performance.now; + const times = [ + 0, 3, 3, 12, 12, 14, 14, 20, 20, 21, 21, 29, 29, 33, + ]; + const profiler = createReactRouterPerformanceProfiler({ + enabled: true, + log: message => logs.push(message), + }); + + try { + performance.now = () => { + const time = times.shift(); + if (time === undefined) { + throw new Error('unexpected timer read'); + } + return time; + }; + + for (const resource of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) { + profiler.recordSync('web', 'route:module', resource, () => resource); + } + profiler.flush('web'); + + const report = parsePerformanceReport(logs[0]); + expect(report.operations['route:module'].slowest).toEqual([ + { durationMs: 9, resource: 'b' }, + { durationMs: 8, resource: 'f' }, + { durationMs: 6, resource: 'd' }, + { durationMs: 4, resource: 'g' }, + { durationMs: 3, resource: 'a' }, + ]); + } finally { + performance.now = originalNow; + } + }); + + it('rounds reported operation timings when flushing', () => { + const logs: string[] = []; + const originalNow = performance.now; + const times = [0, 1.04, 1.04, 1.16]; + const profiler = createReactRouterPerformanceProfiler({ + enabled: true, + log: message => logs.push(message), + }); + + try { + performance.now = () => { + const time = times.shift(); + if (time === undefined) { + throw new Error('unexpected timer read'); + } + return time; + }; + + profiler.recordSync('web', 'route:module', 'app/routes/a.tsx', () => {}); + profiler.recordSync('web', 'route:module', 'app/routes/b.tsx', () => {}); + profiler.flush('web'); + + const report = parsePerformanceReport(logs[0]); + expect(report.operations['route:module']).toMatchObject({ + totalMs: 1.2, + wallMs: 1.2, + maxMs: 1, + }); + expect(report.operations['route:module'].slowest).toEqual([ + { durationMs: 1, resource: 'app/routes/a.tsx' }, + { durationMs: 0.1, resource: 'app/routes/b.tsx' }, + ]); + } finally { + performance.now = originalNow; + } + }); + + it('records async operations without Promise finally overhead', async () => { + const logs: string[] = []; + const profiler = createReactRouterPerformanceProfiler({ + enabled: true, + log: message => logs.push(message), + }); + const operation = Promise.resolve('route-module'); + operation.finally = () => { + throw new Error('profiler should avoid Promise.prototype.finally'); + }; + + await expect( + profiler.record('web', 'route:module', 'app/routes/a.tsx', () => { + return operation; + }) + ).resolves.toBe('route-module'); + profiler.flush('web'); + + const report = parsePerformanceReport(logs[0]); + expect(report.operations['route:module'].count).toBe(1); + }); + + it('does not evaluate timers or log output when disabled', async () => { + const logs: string[] = []; + const originalNow = performance.now; + const nowCalls: string[] = []; + const profiler = createReactRouterPerformanceProfiler({ + enabled: false, + log: message => logs.push(message), + }); + + try { + performance.now = () => { + nowCalls.push('now'); + throw new Error('disabled profiler should not read timers'); + }; + + const asyncResult = await profiler.record( + 'web', + 'route:module', + 'app/routes/a.tsx', + async () => 'unchanged' + ); + const syncResult = profiler.recordSync( + 'web', + 'manifest:stage', + 'virtual/react-router/browser-manifest', + () => 'sync-unchanged' + ); + profiler.flush('web'); + + expect(asyncResult).toBe('unchanged'); + expect(syncResult).toBe('sync-unchanged'); + expect(nowCalls).toEqual([]); + expect(logs).toEqual([]); + } finally { + performance.now = originalNow; + } + }); + + it('returns a rejected promise for synchronous record failures when disabled', async () => { + const profiler = createReactRouterPerformanceProfiler({ + enabled: false, + log: () => {}, + }); + + await expect( + profiler.record('web', 'route:module', 'app/routes/a.tsx', () => { + throw new Error('sync failure'); + }) + ).rejects.toThrow('sync failure'); + }); + + it('flushes timings recorded before an environment is known', async () => { + const logs: string[] = []; + const profiler = createReactRouterPerformanceProfiler({ + enabled: true, + log: message => logs.push(message), + }); + + await profiler.record( + undefined, + 'route:module', + 'app/routes/a.tsx', + async () => 'route-module' + ); + profiler.flush(undefined); + + expect(logs).toHaveLength(1); + const report = parsePerformanceReport(logs[0]); + expect(report.environment).toBe('unknown'); + expect(report.operations['route:module'].count).toBe(1); + }); +}); diff --git a/tests/plugin-utils.test.ts b/tests/plugin-utils.test.ts index 8c85258..31e0b1a 100644 --- a/tests/plugin-utils.test.ts +++ b/tests/plugin-utils.test.ts @@ -1,12 +1,20 @@ import { describe, expect, it } from '@rstest/core'; +import { generate, parse } from '../src/yuku'; import { combineURLs, stripFileExtension, createRouteId, generateWithProps, normalizeAssetPrefix, + transformRoute, } from '../src/plugin-utils'; +const transformRouteCode = (code: string) => { + const ast = parse(code, { sourceType: 'module' }); + transformRoute(ast); + return generate(ast).code; +}; + describe('plugin-utils', () => { describe('combineURLs', () => { it('should combine base and relative URLs', () => { @@ -121,4 +129,184 @@ describe('plugin-utils', () => { expect(normalizeAssetPrefix('/assets/')).toBe('/assets/'); }); }); + + describe('transformRoute', () => { + it('preserves bundler directives while transforming routes', () => { + const result = transformRouteCode(` + export default function Route() { + return import(/* webpackChunkName: "route-data" */ './data'); + } + `); + + expect(result).toContain('webpackChunkName'); + }); + + it('preserves named default class bindings', () => { + const result = transformRouteCode(` + export default class Route {} + Route.displayName = 'Route'; + `); + + expect(result).toMatch(/class Route/); + expect(result).toMatch(/export default _withComponentProps\(Route\)/); + expect(result).toContain(`Route.displayName = 'Route'`); + }); + + it('wraps default class exports with component props', () => { + const result = transformRouteCode(` + export default class Route {} + `); + + expect(result).toContain('withComponentProps'); + expect(result).toMatch(/class Route/); + expect(result).toMatch(/export default _withComponentProps\(Route\)/); + }); + + it('wraps named class component exports', () => { + const result = transformRouteCode(` + export class ErrorBoundary {} + `); + + expect(result).toContain('withErrorBoundaryProps'); + expect(result).toMatch( + /export const ErrorBoundary = _withErrorBoundaryProps\(class ErrorBoundary/ + ); + }); + + it('wraps component exports declared through export specifiers', () => { + const result = transformRouteCode(` + function Boundary() { + return null; + } + export { Boundary as ErrorBoundary }; + `); + + expect(result).toContain('withErrorBoundaryProps'); + expect(result).toMatch( + /const _ErrorBoundary = _withErrorBoundaryProps\(Boundary\)/ + ); + expect(result).toMatch(/export \{ _ErrorBoundary as ErrorBoundary \}/); + }); + + it('wraps component exports re-exported from another module', () => { + const result = transformRouteCode(` + export { Boundary as ErrorBoundary } from './boundary'; + `); + + expect(result).toMatch( + /import \{ Boundary as _ErrorBoundarySource \} from ["']\.\/boundary["']/ + ); + expect(result).toMatch( + /const _ErrorBoundary = _withErrorBoundaryProps\(_ErrorBoundarySource\)/ + ); + expect(result).toMatch(/export \{ _ErrorBoundary as ErrorBoundary \}/); + }); + + it('wraps default route component re-exports', () => { + const result = transformRouteCode(` + export { default } from './Route'; + `); + + expect(result).toContain('withComponentProps'); + expect(result).not.toContain('withdefaultProps'); + expect(result).toContain('export { _default as default }'); + }); + + it('keeps directives before generated HOC imports', () => { + const result = transformRouteCode(` + "use client"; + function Route() { + return null; + } + export { Route as default }; + `); + + expect(result.indexOf("'use client'")).toBeLessThan( + result.indexOf('virtual/react-router/with-props') + ); + expect(result).toContain('withComponentProps'); + expect(result).not.toContain('withdefaultProps'); + }); + + it('preserves side-effect import order before wrapped source re-exports', () => { + const result = transformRouteCode(` + import './setup'; + export { Boundary as ErrorBoundary } from './boundary'; + `); + + expect(result.indexOf("import './setup'")).toBeLessThan( + result.search( + /import\s*\{\s*Boundary as _ErrorBoundarySource\s*\}\s*from ['"]\.\/boundary['"]/ + ) + ); + }); + + it('does not turn type-only exports into runtime component wrappers', () => { + const result = transformRouteCode(` + type Boundary = { message: string }; + export type { Boundary as ErrorBoundary }; + `); + + expect(result).not.toContain('withErrorBoundaryProps'); + expect(result).not.toContain('const _ErrorBoundary'); + }); + + it('does not wrap erased default interface exports', () => { + const result = transformRouteCode(` + export default interface Route { + value: string; + } + `); + + expect(result).not.toContain('withComponentProps'); + expect(result).toContain('export default interface Route'); + }); + + it('avoids top-level generated helper name collisions', () => { + const result = transformRouteCode(` + const _withComponentProps = 'reserved'; + export default function Route() { return null; } + `); + + expect(result).toContain('withComponentProps as _withComponentProps2'); + expect(result).toContain('export default _withComponentProps2'); + }); + + it('does not reserve generated helper names used only in local scopes', () => { + const result = transformRouteCode(` + export default function Route() { + const _withComponentProps = 'local'; + return _withComponentProps; + } + `); + + expect(result).toContain('withComponentProps as _withComponentProps'); + expect(result).toContain('function Route'); + expect(result).toContain('export default _withComponentProps(Route)'); + expect(result).not.toContain('_withComponentProps2'); + }); + + it('does not reserve generated helper names from re-export specifiers', () => { + const result = transformRouteCode(` + export { foo as _withComponentProps } from './foo'; + export default function Route() { return null; } + `); + + expect(result).toContain('withComponentProps as _withComponentProps'); + expect(result).toContain('export default _withComponentProps'); + expect(result).not.toContain('_withComponentProps2'); + }); + + it('avoids top-level generated helper name collisions with enums', () => { + const result = transformRouteCode(` + enum _withComponentProps { + reserved = 'reserved' + } + export default function Route() { return null; } + `); + + expect(result).toContain('withComponentProps as _withComponentProps2'); + expect(result).toContain('export default _withComponentProps2(Route)'); + }); + }); }); diff --git a/tests/prerender.test.ts b/tests/prerender.test.ts index 6b88d2e..a9ae9db 100644 --- a/tests/prerender.test.ts +++ b/tests/prerender.test.ts @@ -1,9 +1,13 @@ import { describe, expect, it } from '@rstest/core'; import { + createPrerenderRoutes, getPrerenderConcurrency, getStaticPrerenderPaths, + getSsrFalsePrerenderExportErrors, + normalizePrerenderMatchPath, resolvePrerenderPaths, validatePrerenderConfig, + withBuildRequest, } from '../src/prerender'; import type { RouteConfigEntry } from '@react-router/dev/routes'; @@ -100,7 +104,207 @@ describe('prerender helpers', () => { expect( getPrerenderConcurrency({ paths: ['/'], unstable_concurrency: 3 }) ).toBe(3); - expect(getPrerenderConcurrency({ paths: ['/'] })).toBe(1); + expect(getPrerenderConcurrency({ paths: ['/'] }, 24)).toBe(1); + expect(getPrerenderConcurrency({ paths: ['/'] }, 3)).toBe(1); + expect(getPrerenderConcurrency({ paths: ['/'] }, 2)).toBe(1); + }); + + it('creates React Router match routes from a route manifest', () => { + expect( + createPrerenderRoutes({ + root: { id: 'root', file: 'root.tsx', path: '' }, + layout: { + id: 'layout', + parentId: 'root', + file: 'routes/layout.tsx', + path: 'dashboard', + }, + index: { + id: 'index', + parentId: 'layout', + file: 'routes/index.tsx', + index: true, + }, + }) + ).toEqual([ + { + id: 'root', + path: '', + children: [ + { + id: 'layout', + path: 'dashboard', + children: [{ id: 'index', path: undefined, index: true }], + }, + ], + }, + ]); + }); + + it('normalizes prerender paths for React Router matching', () => { + expect(normalizePrerenderMatchPath('/')).toBe('/'); + expect(normalizePrerenderMatchPath('about')).toBe('/about/'); + expect(normalizePrerenderMatchPath('/about')).toBe('/about/'); + }); + + it('aborts build request signals after the handler settles', async () => { + let signal: AbortSignal | undefined; + + const result = await withBuildRequest( + 'http://localhost/about', + { + headers: { + 'x-test': 'yes', + }, + }, + async request => { + signal = request.signal; + expect(request.headers.get('x-test')).toBe('yes'); + expect(signal.aborted).toBe(false); + return 'handled'; + } + ); + + expect(result).toBe('handled'); + expect(signal?.aborted).toBe(true); + }); + + it('returns no ssr:false prerender export errors for valid prerendered routes', () => { + const manifestRoutes = { + root: { id: 'root', file: 'root.tsx', path: '' }, + dashboard: { + id: 'dashboard', + parentId: 'root', + file: 'routes/dashboard.tsx', + path: 'dashboard', + hasLoader: true, + hasClientLoader: true, + }, + }; + + expect( + getSsrFalsePrerenderExportErrors({ + routes: manifestRoutes, + manifestRoutes, + routeExports: { + dashboard: ['clientLoader'], + }, + prerenderPaths: ['/dashboard'], + }) + ).toEqual([]); + }); + + it('rejects ssr:false prerender paths that do not match routes', () => { + expect(() => + getSsrFalsePrerenderExportErrors({ + routes: { + root: { id: 'root', file: 'root.tsx', path: '' }, + }, + manifestRoutes: { + root: { id: 'root', file: 'root.tsx', path: '' }, + }, + routeExports: {}, + prerenderPaths: ['/missing'], + }) + ).toThrow('Unable to prerender path because it does not match any routes'); + }); + + it('reports invalid ssr:false prerender action and headers exports', () => { + const manifestRoutes = { + root: { id: 'root', file: 'root.tsx', path: '' }, + dashboard: { + id: 'dashboard', + parentId: 'root', + file: 'routes/dashboard.tsx', + path: 'dashboard', + }, + }; + + expect( + getSsrFalsePrerenderExportErrors({ + routes: manifestRoutes, + manifestRoutes, + routeExports: { + dashboard: ['action', 'headers'], + }, + prerenderPaths: ['/dashboard'], + }) + ).toEqual([ + expect.stringContaining( + '`dashboard` when pre-rendering with `ssr:false`: `headers`, `action`' + ), + ]); + }); + + it('reports loader exports on routes outside the ssr:false prerender set', () => { + const manifestRoutes = { + root: { id: 'root', file: 'root.tsx', path: '' }, + dashboard: { + id: 'dashboard', + parentId: 'root', + file: 'routes/dashboard.tsx', + path: 'dashboard', + hasLoader: true, + }, + reports: { + id: 'reports', + parentId: 'dashboard', + file: 'routes/reports.tsx', + path: 'reports', + }, + about: { + id: 'about', + parentId: 'root', + file: 'routes/about.tsx', + path: 'about', + }, + }; + + expect( + getSsrFalsePrerenderExportErrors({ + routes: manifestRoutes, + manifestRoutes, + routeExports: { + reports: ['loader'], + }, + prerenderPaths: ['/about'], + }) + ).toEqual([ + expect.stringContaining('`reports` when pre-rendering'), + expect.stringContaining('`dashboard` when pre-rendering'), + ]); + }); + + it('reports root loaders for unprerendered ssr:false descendants', () => { + const manifestRoutes = { + root: { + id: 'root', + file: 'root.tsx', + path: '', + hasLoader: true, + }, + dashboard: { + id: 'dashboard', + parentId: 'root', + file: 'routes/dashboard.tsx', + path: 'dashboard', + }, + about: { + id: 'about', + parentId: 'root', + file: 'routes/about.tsx', + path: 'about', + }, + }; + + expect( + getSsrFalsePrerenderExportErrors({ + routes: manifestRoutes, + manifestRoutes, + routeExports: {}, + prerenderPaths: ['/about'], + }) + ).toEqual([expect.stringContaining('`root` when pre-rendering')]); }); it('validates stable prerender concurrency config', () => { diff --git a/tests/react-router-config.test.ts b/tests/react-router-config.test.ts index cfd700b..6269041 100644 --- a/tests/react-router-config.test.ts +++ b/tests/react-router-config.test.ts @@ -31,6 +31,41 @@ describe('resolveReactRouterConfig', () => { expect(buildEndCalls).toBe(2); }); + it('preserves server bundle selection in SSR mode', async () => { + const serverBundles = async () => 'bundle'; + + const result = await resolveReactRouterConfig({ + ssr: true, + serverBundles, + }); + + expect(result.resolved.serverBundles).toBe(serverBundles); + }); + + it('distinguishes an explicit server module format from its default', async () => { + const defaultResult = await resolveReactRouterConfig({}); + const configuredResult = await resolveReactRouterConfig({ + serverModuleFormat: 'cjs', + }); + + expect(defaultResult.hasConfiguredServerModuleFormat).toBe(false); + expect(configuredResult.hasConfiguredServerModuleFormat).toBe(true); + }); + + it('defaults route module splitting on and respects the stable top-level option', async () => { + const defaultResult = await resolveReactRouterConfig({}); + const disabledResult = await resolveReactRouterConfig({ + splitRouteModules: false, + } as any); + const enforcedResult = await resolveReactRouterConfig({ + splitRouteModules: 'enforce', + } as any); + + expect(defaultResult.resolved.splitRouteModules).toBe(true); + expect(disabledResult.resolved.splitRouteModules).toBe(false); + expect(enforcedResult.resolved.splitRouteModules).toBe('enforce'); + }); + it('resolves stable config fields required by React Router 8', async () => { const defaultResult = await resolveReactRouterConfig({}); const stableResult = await resolveReactRouterConfig({ @@ -52,7 +87,7 @@ describe('resolveReactRouterConfig', () => { }, }); - expect(defaultResult.resolved.splitRouteModules).toBe(false); + expect(defaultResult.resolved.splitRouteModules).toBe(true); expect(defaultResult.resolved.subResourceIntegrity).toBe(false); expect(stableResult.resolved.splitRouteModules).toBe('enforce'); expect(stableResult.resolved.subResourceIntegrity).toBe(true); diff --git a/tests/remove-exports.test.ts b/tests/remove-exports.test.ts index e907ca1..e7f8a0c 100644 --- a/tests/remove-exports.test.ts +++ b/tests/remove-exports.test.ts @@ -1,24 +1,40 @@ import { describe, expect, it } from '@rstest/core'; -import { parse, traverse } from '../src/babel'; +import { generate, parse } from '../src/yuku'; import { removeExports, removeUnusedImports } from '../src/plugin-utils'; -function hasTopLevelAssignment(ast: any, textIncludes: string): boolean { - let found = false; - traverse(ast, { - ExpressionStatement(path) { - if (!path.parentPath.isProgram()) return; - const expr = path.node.expression; - if (expr.type !== 'AssignmentExpression') return; - const raw = path.toString(); - if (raw.includes(textIncludes)) { - found = true; +describe('removeExports', () => { + it('returns false when no matching export can be removed', () => { + const code = ` + export const clientLoader = async () => null; + export default function Route() { + return null; } - }, + `; + + const ast = parse(code, { sourceType: 'module' }); + const removed = removeExports(ast, ['loader', 'action']); + + expect(removed).toBe(false); + expect(generate(ast).code).toContain('clientLoader'); + }); + + it('returns true when a matching export is removed', () => { + const code = ` + export async function loader() { + return null; + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + const removed = removeExports(ast, ['loader']); + + expect(removed).toBe(true); + expect(generate(ast).code).not.toContain('loader'); }); - return found; -} -describe('removeExports', () => { it('removes top-level property assignment when removed export is referenced by local name', () => { const code = ` const local = () => {}; @@ -31,7 +47,7 @@ describe('removeExports', () => { removeExports(ast, ['loader']); // The export specifier should be gone and the assignment too. - expect(hasTopLevelAssignment(ast, 'local.hydrate')).toBe(false); + expect(generate(ast).code).not.toContain('local.hydrate'); }); it('removes top-level property assignment when default export is removed', () => { @@ -44,7 +60,7 @@ describe('removeExports', () => { const ast = parse(code, { sourceType: 'module' }); removeExports(ast, ['default']); - expect(hasTopLevelAssignment(ast, 'Root.displayName')).toBe(false); + expect(generate(ast).code).not.toContain('Root.displayName'); }); it('removes unused imports after removing server-only exports', () => { @@ -62,15 +78,354 @@ describe('removeExports', () => { removeExports(ast, ['action']); removeUnusedImports(ast); - let hasThemeImport = false; - traverse(ast, { - ImportDeclaration(path) { - if (path.node.source.value === './theme.server') { - hasThemeImport = true; - } - }, - }); + expect(generate(ast).code).not.toContain('./theme.server'); + }); + + it('rejects unaliased export-all declarations when removing named exports', () => { + const code = ` + export * from './data.server'; + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + + expect(() => removeExports(ast, ['loader'])).toThrowError( + 'Cannot remove named exports from `export *`; use explicit named re-exports.' + ); + }); + + it('keeps lowercase JSX member imports after removing server exports', () => { + const code = ` + import { motion } from 'framer-motion'; + export async function loader() { return null; } + export default function Route() { + return ; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + removeUnusedImports(ast); + + const result = generate(ast).code; + expect(result).toContain("import { motion } from 'framer-motion'"); + expect(result).toContain(' { + const code = ` + import { + loaderDependency as dependency, + unrelated as loaderDependency, + } from './data.server'; + export function loader() { + return dependency(); + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + removeUnusedImports(ast); + + expect(generate(ast).code).not.toContain('./data.server'); + }); + + it('keeps top-level declarations referenced from JSX after removing exports', () => { + const code = ` + export function loader() { + return null; + } + + function ProgressBar() { + return null; + } + + export default function Route() { + return ; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + + expect(result).toContain('function ProgressBar'); + expect(result).toContain(' { + const code = ` + const leaf = 1, middle = leaf; + export function loader() { + return middle; + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + expect(result).not.toContain('leaf'); + expect(result).not.toContain('middle'); + expect(result).not.toContain('loader'); + expect(result).toContain('Route'); + }); + + it('rejects destructured defaults for removed server-only exports', () => { + const code = ` + const route = { loader: async () => null }; + export const { loader = async () => null } = route; + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + + expect(() => removeExports(ast, ['loader'])).toThrowError( + 'Cannot remove destructured export "loader"' + ); + }); + + it('rejects nested destructured bindings for removed exports', () => { + const code = ` + const route = { data: { nested: { loader: async () => null } } }; + export const { data: { nested: { loader } } } = route; + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + + expect(() => removeExports(ast, ['loader'])).toThrowError( + 'Cannot remove destructured export "loader"' + ); + }); + + it('rejects rest identifiers for removed exports', () => { + const code = ` + const route = { action: async () => null, loader: async () => null }; + export const { action, ...loader } = route; + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + + expect(() => removeExports(ast, ['loader'])).toThrowError( + 'Cannot remove destructured export "loader"' + ); + }); + + it('checks aliased destructuring by local binding name', () => { + const code = ` + const route = { loader: async () => null }; + export const { loader: clientLoader } = route; + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + + expect(() => removeExports(ast, ['loader'])).not.toThrow(); + expect(() => removeExports(ast, ['clientLoader'])).toThrowError( + 'Cannot remove destructured export "clientLoader"' + ); + }); + + it('ignores array holes and default initializer references', () => { + const code = ` + const route = [undefined, async () => loader()]; + export const [, action = loader] = route; + function loader() { + return null; + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + + expect(() => removeExports(ast, ['loader'])).not.toThrow(); + expect(() => removeExports(ast, ['action'])).toThrowError( + 'Cannot remove destructured export "action"' + ); + }); + + it('removes every declaration in a deep dead dependency chain', () => { + const helperCount = 64; + const helpers = Array.from({ length: helperCount }, (_, index) => { + const value = index === helperCount - 1 ? '1' : `helper${index + 1}()`; + return `const helper${index} = () => ${value};`; + }).join('\n'); + const code = ` + ${helpers} + export function loader() { + return helper0(); + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + expect(result).not.toMatch(/\bhelper\d+\b/); + expect(result).toContain('Route'); + }); + + it('preserves declarations that were already unused before export removal', () => { + const code = ` + import { register } from './registry'; + const registration = register(); + export function loader() { + return null; + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + removeUnusedImports(ast); + + const result = generate(ast).code; + expect(result).toContain("import { register } from './registry'"); + expect(result).toContain('const registration = register()'); + expect(result).not.toContain('loader'); + }); + + it('removes pre-existing unused declarations that reference removed export locals', () => { + const code = ` + const leaked = loader; + export function loader() { + return null; + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + expect(result).not.toContain('leaked'); + expect(result).not.toContain('loader'); + expect(result).toContain('Route'); + }); + + it('removes pre-existing unused declarations that retain server-only imports', () => { + const code = ` + import { readSecret } from './data.server'; + const leaked = readSecret(); + export function loader() { + return readSecret(); + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + removeUnusedImports(ast); + + const result = generate(ast).code; + expect(result).not.toContain('./data.server'); + expect(result).not.toContain('leaked'); + expect(result).not.toContain('readSecret'); + expect(result).toContain('Route'); + }); + + it('removes multiple pre-existing unused declarations through shared removed export dependencies', () => { + const code = ` + const shared = () => loader(); + const first = () => shared(); + const second = () => shared(); + export function loader() { + return null; + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + expect(result).not.toContain('shared'); + expect(result).not.toContain('first'); + expect(result).not.toContain('second'); + expect(result).not.toContain('loader'); + expect(result).toContain('Route'); + }); + + it('does not treat an exported alias as a reference to its exported name', () => { + const code = ` + const loader = register(); + const implementation = () => null; + export { implementation as loader }; + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + expect(result).toContain('const loader = register()'); + expect(result).not.toContain('implementation'); + }); + + it('removes a dead declaration cycle reached only by a removed export', () => { + const code = ` + const first = () => second(); + const second = () => first(); + export function loader() { + return first(); + } + export default function Route() { + return null; + } + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['loader']); + + const result = generate(ast).code; + expect(result).not.toContain('first'); + expect(result).not.toContain('second'); + expect(result).toContain('Route'); + }); + + it('removes dependencies of an anonymous default export', () => { + const code = ` + const render = () => null; + export default () => render(); + `; + + const ast = parse(code, { sourceType: 'module' }); + removeExports(ast, ['default']); - expect(hasThemeImport).toBe(false); + expect(generate(ast).code).not.toContain('render'); }); }); diff --git a/tests/route-artifacts.test.ts b/tests/route-artifacts.test.ts new file mode 100644 index 0000000..f32d094 --- /dev/null +++ b/tests/route-artifacts.test.ts @@ -0,0 +1,218 @@ +import { describe, expect, it } from '@rstest/core'; +import { + createRouteChunkArtifact, + createRouteClientEntryArtifact, +} from '../src/route-artifacts'; +import { + emptyRouteChunkSnippet, + getRouteChunkIfEnabled, + getRouteChunkModuleId, + type RouteChunkCache, + type RouteChunkConfig, + type RouteChunkName, +} from '../src/route-chunks'; + +const routeChunkConfig: RouteChunkConfig = { + splitRouteModules: true, + appDirectory: '/app', + rootRouteFile: 'root.tsx', +}; + +const disabledRouteChunkConfig: RouteChunkConfig = { + ...routeChunkConfig, + splitRouteModules: false, +}; + +const enforceRouteChunkConfig: RouteChunkConfig = { + ...routeChunkConfig, + splitRouteModules: 'enforce', +}; + +const resourcePath = '/app/routes/demo.tsx'; +const routeRequest = `${resourcePath}?react-router-route`; + +const createRouteChunk = async ( + source: string, + chunkName: RouteChunkName, + options: { + config?: RouteChunkConfig; + cache?: RouteChunkCache; + isBuild?: boolean; + } = {} +) => + createRouteChunkArtifact({ + code: source, + resource: getRouteChunkModuleId(resourcePath, chunkName), + resourcePath, + routeChunkConfig: options.config ?? routeChunkConfig, + routeChunkCache: options.cache, + isBuild: options.isBuild ?? true, + }); + +describe('route artifact helpers', () => { + describe('createRouteClientEntryArtifact', () => { + it('generates web route reexports that filter server-only exports', async () => { + const result = await createRouteClientEntryArtifact({ + code: ` + export async function loader() { return null; } + export async function clientLoader() { return null; } + export { meta as meta }; + const meta = () => []; + export default function Route() { return null; } + `, + resourcePath, + environmentName: 'web', + isBuild: false, + routeChunkConfig: disabledRouteChunkConfig, + }); + + expect(result).toEqual({ + code: `export { clientLoader, default, meta } from ${JSON.stringify( + routeRequest + )};`, + }); + }); + + it('includes server-only route exports for node route entries', async () => { + const result = await createRouteClientEntryArtifact({ + code: ` + export async function loader() { return null; } + export async function action() { return null; } + export async function clientLoader() { return null; } + export default function Route() { return null; } + `, + resourcePath, + environmentName: 'node', + isBuild: true, + routeChunkConfig, + }); + + expect(result).toEqual({ + code: `export { action, clientLoader, default, loader } from ${JSON.stringify( + routeRequest + )};`, + }); + }); + + it('excludes split client exports from web build route entries', async () => { + const result = await createRouteClientEntryArtifact({ + code: ` + export const clientAction = async () => {}; + export async function clientLoader() { return null; } + export default function Route() { return null; } + `, + resourcePath, + environmentName: 'web', + isBuild: true, + routeChunkConfig, + }); + + expect(result).toEqual({ + code: `export { default } from ${JSON.stringify(routeRequest)};`, + }); + }); + + it('does not run split analysis for root route client entries', async () => { + const rootResourcePath = '/app/root.tsx'; + const result = await createRouteClientEntryArtifact({ + code: ` + export async function clientLoader() { return null; } + export function HydrateFallback() { return null; } + export default function Root() { return null; } + `, + resourcePath: rootResourcePath, + environmentName: 'web', + isBuild: true, + routeChunkConfig, + }); + + expect(result).toEqual({ + code: `export { HydrateFallback, clientLoader, default } from ${JSON.stringify( + `${rootResourcePath}?react-router-route` + )};`, + }); + }); + }); + + describe('createRouteChunkArtifact', () => { + it('returns the disabled split-route empty snippet with a null map', async () => { + await expect( + createRouteChunk( + `export const clientLoader = async () => {};`, + 'clientLoader', + { + config: disabledRouteChunkConfig, + isBuild: true, + } + ) + ).resolves.toEqual({ + code: emptyRouteChunkSnippet(), + map: null, + }); + }); + + it('rejects invalid route chunk names before generating code', async () => { + await expect( + createRouteChunkArtifact({ + code: `export const clientLoader = async () => {};`, + resource: `${resourcePath}?route-chunk=invalid`, + resourcePath, + routeChunkConfig, + isBuild: true, + }) + ).rejects.toThrow( + `Invalid route chunk name in "${resourcePath}?route-chunk=invalid"` + ); + }); + + it('generates the expected route chunk code from source', async () => { + const source = ` + export const clientAction = async () => {}; + export default function Route() { return null; } + `; + const cache: RouteChunkCache = new Map(); + const expectedCode = await getRouteChunkIfEnabled( + cache, + routeChunkConfig, + resourcePath, + 'clientAction', + source + ); + + const result = await createRouteChunk(source, 'clientAction', { cache }); + + expect(result).toEqual({ code: expectedCode, map: null }); + }); + + it('skips ESM transforms for named chunks when no route chunk exports exist', async () => { + await expect( + createRouteChunkArtifact({ + code: `export default function Route() { return null; }`, + resource: getRouteChunkModuleId(resourcePath, 'clientLoader'), + resourcePath: '/app/routes/demo.cts', + routeChunkConfig, + isBuild: true, + }) + ).resolves.toEqual({ + code: emptyRouteChunkSnippet(), + map: null, + }); + }); + + it('validates enforce-mode main chunks against generated chunk exports', async () => { + await expect( + createRouteChunk( + ` + const shared = () => null; + export const clientAction = async () => shared(); + export default function Route() { return shared(); } + `, + 'main', + { + config: enforceRouteChunkConfig, + } + ) + ).rejects.toThrow('Error splitting route module: routes/demo.tsx'); + }); + }); +}); diff --git a/tests/route-chunks-cache.test.ts b/tests/route-chunks-cache.test.ts new file mode 100644 index 0000000..a84b4f0 --- /dev/null +++ b/tests/route-chunks-cache.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it } from '@rstest/core'; +import { + detectRouteChunksIfEnabled, + getRouteChunkIfEnabled, + routeChunkNames, + type RouteChunkCache, + type RouteChunkConfig, + type RouteChunkInfo, + type RouteChunkName, +} from '../src/route-chunks'; + +const config: RouteChunkConfig = { + splitRouteModules: true, + appDirectory: '/app', + rootRouteFile: 'root.tsx', +}; + +const routeId = '/app/routes/demo.tsx'; + +const chunkableCode = ` + const actionHelper = () => null; + const loaderHelper = () => null; + const middlewareHelper = () => null; + const fallbackHelper = () => null; + export const clientAction = async () => actionHelper(); + export const clientLoader = async () => loaderHelper(); + export const clientMiddleware = async () => middlewareHelper(); + export function HydrateFallback() { return fallbackHelper(); } + export async function action() { return null; } + export default function Route() { return null; } +`; + +const nonChunkableCode = ` + const shared = () => null; + export default function Route() { return shared(); } + export const clientAction = async () => shared(); +`; + +const collectRouteChunkOracle = async ( + cache: RouteChunkCache | undefined, + code = chunkableCode +) => { + const info = await detectRouteChunksIfEnabled(cache, config, routeId, code); + const chunks = Object.fromEntries( + await Promise.all( + routeChunkNames.map(async chunkName => [ + chunkName, + await getRouteChunkIfEnabled(cache, config, routeId, chunkName, code), + ]) + ) + ) as Record; + + return { info, chunks }; +}; + +const expectAllRouteChunks = (info: RouteChunkInfo) => { + expect(info.hasRouteChunks).toBe(true); + expect(info.chunkedExports).toEqual([ + 'clientAction', + 'clientLoader', + 'clientMiddleware', + 'HydrateFallback', + ]); + expect(info.hasRouteChunkByExportName).toEqual({ + clientAction: true, + clientLoader: true, + clientMiddleware: true, + HydrateFallback: true, + }); +}; + +describe('route chunk cache', () => { + it('invalidates cached detection when the same route id receives changed code', async () => { + const cache = new Map(); + + const first = await detectRouteChunksIfEnabled( + cache, + config, + routeId, + chunkableCode + ); + const second = await detectRouteChunksIfEnabled( + cache, + config, + routeId, + nonChunkableCode + ); + + expectAllRouteChunks(first); + expect(second.hasRouteChunks).toBe(false); + expect(second.hasRouteChunkByExportName.clientAction).toBe(false); + }); + + it('returns identical route chunk info and generated chunks across repeated cached calls', async () => { + const cache = new Map(); + + const first = await collectRouteChunkOracle(cache); + const second = await collectRouteChunkOracle(cache); + + expect(second).toEqual(first); + expectAllRouteChunks(first.info); + expect(first.chunks.main).not.toContain('clientAction'); + expect(first.chunks.clientAction).toContain('clientAction'); + expect(first.chunks.clientLoader).toContain('clientLoader'); + expect(first.chunks.clientMiddleware).toContain('clientMiddleware'); + expect(first.chunks.HydrateFallback).toContain('HydrateFallback'); + }); + + it('computes the same route chunk oracle with and without an explicit cache', async () => { + const cached = await collectRouteChunkOracle(new Map()); + const uncached = await collectRouteChunkOracle(undefined); + + expect(uncached).toEqual(cached); + }); + + it('stores the Yuku route chunk analysis entries for repeated chunk generation', async () => { + const cache = new Map(); + + await collectRouteChunkOracle(cache); + + expect(Array.from(cache.keys()).sort()).toEqual([ + 'routes/demo.tsx::analyzeCode', + 'routes/demo.tsx::getChunkableExportMap', + 'routes/demo.tsx::getChunkedExport::HydrateFallback', + 'routes/demo.tsx::getChunkedExport::clientAction', + 'routes/demo.tsx::getChunkedExport::clientLoader', + 'routes/demo.tsx::getChunkedExport::clientMiddleware', + 'routes/demo.tsx::getExportDependencies', + 'routes/demo.tsx::omitChunkedExports::clientAction,clientLoader,clientMiddleware,HydrateFallback', + ]); + }); + + it('precomputes sibling named chunk entries for repeated chunk generation', async () => { + const cache = new Map(); + + await getRouteChunkIfEnabled( + cache, + config, + routeId, + 'clientAction', + chunkableCode + ); + + expect(Array.from(cache.keys()).sort()).toEqual([ + 'routes/demo.tsx::analyzeCode', + 'routes/demo.tsx::getChunkableExportMap', + 'routes/demo.tsx::getChunkedExport::HydrateFallback', + 'routes/demo.tsx::getChunkedExport::clientAction', + 'routes/demo.tsx::getChunkedExport::clientLoader', + 'routes/demo.tsx::getChunkedExport::clientMiddleware', + 'routes/demo.tsx::getExportDependencies', + ]); + }); +}); diff --git a/tests/route-chunks.test.ts b/tests/route-chunks.test.ts index f6a7799..4b53e43 100644 --- a/tests/route-chunks.test.ts +++ b/tests/route-chunks.test.ts @@ -1,87 +1,633 @@ import { describe, expect, it } from '@rstest/core'; +import { getExportNames } from '../src/export-utils'; import { detectRouteChunksIfEnabled, + getRouteChunkCode, + getRouteChunkEntryName, + getRouteChunkIfEnabled, + getRouteChunkModuleId, + getRouteChunkNameFromModuleId, + isRouteChunkModuleId, + routeChunkExportNames, + type RouteChunkConfig, + type RouteChunkExportName, + type RouteChunkInfo, validateRouteChunks, } from '../src/route-chunks'; -const config = { - splitRouteModules: true as const, +const config: RouteChunkConfig = { + splitRouteModules: true, appDirectory: '/app', rootRouteFile: 'root.tsx', }; -const enforceConfig = { - splitRouteModules: 'enforce' as const, - appDirectory: '/app', - rootRouteFile: 'root.tsx', +const disabledConfig: RouteChunkConfig = { + ...config, + splitRouteModules: false, +}; + +const enforceConfig: RouteChunkConfig = { + ...config, + splitRouteModules: 'enforce', +}; + +const routeId = '/app/routes/demo.tsx'; +const rootRouteId = '/app/root.tsx'; + +const emptyChunkInfo: RouteChunkInfo = { + exportNames: [], + chunkedExports: [], + hasRouteChunks: false, + hasRouteChunkByExportName: { + clientAction: false, + clientLoader: false, + clientMiddleware: false, + HydrateFallback: false, + }, +}; + +const clientExportFixtures: Record = { + clientAction: `export const clientAction = async () => {};`, + clientLoader: `export const clientLoader = async () => {};`, + clientMiddleware: `export const clientMiddleware = async () => {};`, + HydrateFallback: `export function HydrateFallback() { return null; }`, +}; + +const codeWithClientAction = ` + export const clientAction = async () => {}; + export default function Route() { return null; } +`; + +const codeWithClientActionSharedWithDefault = ` + const helper = () => null; + export default function Route() { return helper(); } + export const clientAction = async () => helper(); +`; + +const codeWithActionAndDefault = ` + import { json } from 'react-router'; + export async function action() { return json({}); } + export default function Route() { return null; } +`; + +const detect = (code: string, id = routeId) => + detectRouteChunksIfEnabled(new Map(), config, id, code); + +const expectOnlyChunkedExport = ( + result: RouteChunkInfo, + exportName: RouteChunkExportName +) => { + expect(result.hasRouteChunks).toBe(true); + expect(result.chunkedExports).toEqual([exportName]); + for (const name of routeChunkExportNames) { + expect(result.hasRouteChunkByExportName[name]).toBe(name === exportName); + } +}; + +const expectNoRouteChunks = ( + result: RouteChunkInfo, + exportNames: string[] = [] +) => { + expect(result).toEqual({ ...emptyChunkInfo, exportNames }); +}; + +const expectExports = async ( + code: string | null, + expectedExports: string[], + unexpectedExports: string[] = [] +) => { + expect(code).not.toBeNull(); + const exports = await getExportNames(code ?? ''); + for (const exportName of expectedExports) { + expect(exports).toContain(exportName); + } + for (const exportName of unexpectedExports) { + expect(exports).not.toContain(exportName); + } }; describe('route chunks', () => { - it('detects chunkable client exports', async () => { - const code = ` - export const clientAction = async () => {}; - export const clientLoader = async () => {}; - export const clientMiddleware = async () => {}; - export function HydrateFallback() { return null; } - export default function Route() { return null; } - `; - - const result = await detectRouteChunksIfEnabled( - undefined, - config, - '/app/routes/demo.tsx', - code + describe('detect route chunks', () => { + it.each(routeChunkExportNames)( + 'detects a splittable %s export independently', + async exportName => { + const code = ` + ${clientExportFixtures[exportName]} + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectOnlyChunkedExport(result, exportName); + } ); - expect(result.hasRouteChunks).toBe(true); - expect(result.hasRouteChunkByExportName.clientAction).toBe(true); - expect(result.hasRouteChunkByExportName.clientLoader).toBe(true); - expect(result.hasRouteChunkByExportName.clientMiddleware).toBe(true); - expect(result.hasRouteChunkByExportName.HydrateFallback).toBe(true); + it('detects all four client exports as independently splittable', async () => { + const code = ` + const actionHelper = () => null; + const loaderHelper = () => null; + const middlewareHelper = () => null; + const fallbackHelper = () => null; + export const clientAction = async () => actionHelper(); + export const clientLoader = async () => loaderHelper(); + export const clientMiddleware = async () => middlewareHelper(); + export function HydrateFallback() { return fallbackHelper(); } + export default function Route() { return null; } + `; + + const result = await detect(code); + + expect(result.hasRouteChunks).toBe(true); + expect(result.hasRouteChunkByExportName).toEqual({ + clientAction: true, + clientLoader: true, + clientMiddleware: true, + HydrateFallback: true, + }); + expect(result.chunkedExports).toEqual(routeChunkExportNames); + }); + + it('returns runtime export names from route chunk analysis', async () => { + const result = await detectRouteChunksIfEnabled( + new Map(), + config, + routeId, + ` + export type LoaderData = { value: string }; + export type * from './types'; + export * from './shared'; + export * as helpers from './helpers'; + export const clientAction = async () => {}; + export async function loader() { return null; } + export default function Route() { return null; } + ` + ); + + expect(result.exportNames).toEqual([ + 'helpers', + 'clientAction', + 'loader', + 'default', + ]); + }); + + it('allows client exports to depend on imports', async () => { + const code = ` + import { json } from 'react-router'; + export const clientLoader = async () => json({}); + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectOnlyChunkedExport(result, 'clientLoader'); + }); + + it('does not split two client exports that share a top-level helper', async () => { + const code = ` + const shared = () => {}; + export const clientAction = async () => shared(); + export const clientLoader = async () => shared(); + `; + + const result = await detect(code); + + expectNoRouteChunks(result, ['clientAction', 'clientLoader']); + }); + + it('does not split a client export that shares top-level code with the default export', async () => { + const result = await detect(codeWithClientActionSharedWithDefault); + + expectNoRouteChunks(result, ['default', 'clientAction']); + }); + + it('splits a single-binding destructured client export', async () => { + const code = ` + function make() { return { clientAction: async () => {} }; } + export const { clientAction } = make(); + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectOnlyChunkedExport(result, 'clientAction'); + }); + + it('does not split a multi-binding destructured client export sharing a declarator', async () => { + const code = ` + function make() { return { clientAction: async () => {}, foo: 1 }; } + export const { clientAction, foo } = make(); + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectNoRouteChunks(result, ['clientAction', 'foo', 'default']); + }); + + it('splits an isolated client export while leaving a non-splittable sibling unsplit', async () => { + const code = ` + const actionHelper = () => null; + const shared = () => null; + export const clientAction = async () => actionHelper(); + export const clientLoader = async () => shared(); + export default function Route() { return shared(); } + `; + + const result = await detect(code); + + expect(result.hasRouteChunks).toBe(true); + expect(result.chunkedExports).toEqual(['clientAction']); + expect(result.hasRouteChunkByExportName.clientAction).toBe(true); + expect(result.hasRouteChunkByExportName.clientLoader).toBe(false); + }); + + it('does not scan sibling declarators from shared export statements as dependencies', async () => { + const code = ` + const serverOnly = () => null; + export const clientAction = async () => null, helper = serverOnly(); + export default function Route() { return helper; } + `; + + const result = await detect(code); + + expectOnlyChunkedExport(result, 'clientAction'); + }); + + it('orders chunkedExports by routeChunkExportNames, not source order', async () => { + const code = ` + export function HydrateFallback() { return null; } + export const clientLoader = async () => {}; + export const clientAction = async () => {}; + export default function Route() { return null; } + `; + + const result = await detect(code); + + expect(result.chunkedExports).toEqual([ + 'clientAction', + 'clientLoader', + 'HydrateFallback', + ]); + }); + + it('keeps top-level side effects in the main chunk while splitting independent client exports', async () => { + const code = ` + import './polyfill'; + initialize(); + export const clientAction = async () => {}; + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectOnlyChunkedExport(result, 'clientAction'); + }); + + it('keeps side-effect imports in the main chunk while splitting independent client exports', async () => { + const code = ` + import './polyfill'; + export const clientAction = async () => {}; + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectOnlyChunkedExport(result, 'clientAction'); + }); }); - it('skips splitting for the root route', async () => { - const code = `export const clientAction = async () => {};`; + describe('generate route chunk code', () => { + it('omits chunkable client exports from the main chunk while retaining default and server exports', async () => { + const code = ` + import { json } from 'react-router'; + export async function action() { return json({}); } + export const clientAction = async () => {}; + export default function Route() { return null; } + `; - const result = await detectRouteChunksIfEnabled( - undefined, - config, - '/app/root.tsx', - code - ); + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'main', + code + ); + + await expectExports(chunk, ['default', 'action'], ['clientAction']); + }); + + it('returns main chunk code without analysis when no route chunk exports exist', async () => { + const cache = new Map(); + const code = `export default function Route() { return null; }`; + + const chunk = await getRouteChunkIfEnabled( + cache, + config, + routeId, + 'main', + code + ); + + expect(chunk).toBe(code); + expect(cache.size).toBe(0); + }); + + it('generates an individual client chunk with only that client export', async () => { + const code = ` + import { json } from 'react-router'; + export async function action() { return json({}); } + export const clientAction = async () => {}; + export default function Route() { return null; } + `; + + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'clientAction', + code + ); + + await expectExports(chunk, ['clientAction'], ['default', 'action']); + }); + + it('keeps only import specifiers used by an individual client chunk', async () => { + const code = ` + import { json, useFetcher } from 'react-router'; + export const clientLoader = async () => json({}); + export default function Route() { return useFetcher(); } + `; + + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'clientLoader', + code + ); + + expect(chunk).toMatch(/import\s*\{\s*json\s*\}\s*from/); + expect(chunk).not.toContain('useFetcher'); + await expectExports(chunk, ['clientLoader'], ['default']); + }); + + it('keeps side-effect imports in the main chunk and omits them from individual client chunks', async () => { + const code = ` + import './polyfill'; + export const clientAction = async () => {}; + export default function Route() { return null; } + `; + + const mainChunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'main', + code + ); + const clientActionChunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'clientAction', + code + ); + + expect(mainChunk).toContain("import './polyfill'"); + await expectExports(mainChunk, ['default'], ['clientAction']); + expect(clientActionChunk).not.toContain('polyfill'); + await expectExports(clientActionChunk, ['clientAction'], ['default']); + }); + + it('returns null for the main chunk when only client exports exist', async () => { + const code = ` + export const clientAction = async () => {}; + export const clientLoader = async () => {}; + `; + + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'main', + code + ); + + expect(chunk).toBeNull(); + }); - expect(result.hasRouteChunks).toBe(false); - expect(result.hasRouteChunkByExportName.clientAction).toBe(false); + it('returns null for a non-chunkable individual client export', async () => { + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'clientAction', + codeWithClientActionSharedWithDefault + ); + + expect(chunk).toBeNull(); + }); + + it('returns the full main chunk when a module has no chunkable exports', async () => { + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + routeId, + 'main', + codeWithActionAndDefault + ); + + await expectExports(chunk, ['default', 'action'], ['clientAction']); + }); + + it('dispatches main and named chunk generation through getRouteChunkCode', async () => { + const cache = new Map(); + const mainChunk = getRouteChunkCode( + codeWithClientAction, + 'main', + cache, + 'routes/demo.tsx' + ); + const clientActionChunk = getRouteChunkCode( + codeWithClientAction, + 'clientAction', + cache, + 'routes/demo.tsx' + ); + + await expectExports(mainChunk ?? null, ['default'], ['clientAction']); + await expectExports(clientActionChunk ?? null, ['clientAction'], ['default']); + }); + + it('round-trips route chunk module ids and entry names', () => { + const moduleId = getRouteChunkModuleId( + '/app/routes/r.tsx', + 'clientAction' + ); + + expect(moduleId).toBe('/app/routes/r.tsx?route-chunk=clientAction'); + expect(isRouteChunkModuleId(moduleId)).toBe(true); + expect(getRouteChunkNameFromModuleId(moduleId)).toBe('clientAction'); + expect( + isRouteChunkModuleId('/app/routes/r.tsx?route-chunk=clientAction&foo=1') + ).toBe(true); + expect( + getRouteChunkNameFromModuleId( + '/app/routes/r.tsx?route-chunk=clientAction&foo=1' + ) + ).toBe('clientAction'); + expect(getRouteChunkNameFromModuleId('/app/routes/r.tsx?route-chunk=main')).toBe( + 'main' + ); + expect(getRouteChunkNameFromModuleId('/app/routes/r.tsx')).toBeNull(); + expect( + getRouteChunkNameFromModuleId('/app/routes/r.tsx?route-chunk=bogus') + ).toBeNull(); + expect(getRouteChunkEntryName('routes/clients', 'clientAction')).toBe( + 'routes/clients-client-action' + ); + }); }); - it('throws when enforce is enabled and chunks cannot be split', async () => { - const code = ` - const shared = () => {}; - export const clientAction = async () => shared(); - export const clientLoader = async () => shared(); - `; - - const result = await detectRouteChunksIfEnabled( - undefined, - enforceConfig, - '/app/routes/shared.tsx', - code + describe('mode + early-exit', () => { + it('returns no route chunks without parsing when splitRouteModules is disabled or absent', async () => { + const invalidCode = `export const clientAction = ;`; + const absentConfig: RouteChunkConfig = { + ...config, + splitRouteModules: undefined, + }; + + await expect( + detectRouteChunksIfEnabled(new Map(), disabledConfig, routeId, invalidCode) + ).resolves.toEqual(emptyChunkInfo); + await expect( + detectRouteChunksIfEnabled(new Map(), absentConfig, routeId, invalidCode) + ).resolves.toEqual(emptyChunkInfo); + }); + + it('early-exits when no client export name substring appears', async () => { + const result = await detect(codeWithActionAndDefault); + + expectNoRouteChunks(result); + }); + + it('does not create a chunk from a client export name mentioned only in a comment', async () => { + const code = ` + // clientAction is mentioned here, but no such export exists. + export default function Route() { return null; } + `; + + const result = await detect(code); + + expectNoRouteChunks(result, ['default']); + }); + + it('returns null when route chunk generation is disabled', async () => { + await expect( + getRouteChunkIfEnabled( + new Map(), + disabledConfig, + routeId, + 'main', + codeWithClientAction + ) + ).resolves.toBeNull(); + }); + }); + + describe('root route', () => { + it.each([ + ['/app/root.tsx', true], + ['/app/./root.tsx', true], + ['/app/root.tsx?react-router-route', true], + ['/app/routes/root.tsx', false], + ])( + 'detects root route identity for %s', + async (id, isRootRoute) => { + const result = await detect(codeWithClientAction, id); + + expect(result.hasRouteChunks).toBe(!isRootRoute); + expect(result.hasRouteChunkByExportName.clientAction).toBe(!isRootRoute); + } ); - expect(result.hasRouteChunkByExportName.clientAction).toBe(false); - expect(result.hasRouteChunkByExportName.clientLoader).toBe(false); - - expect(() => - validateRouteChunks({ - config: enforceConfig, - id: '/app/routes/shared.tsx', - valid: { - clientAction: false, - clientLoader: false, - clientMiddleware: true, - HydrateFallback: true, - }, - }) - ).toThrowError(/Error splitting route module/); + it('generates a named chunk for the root route because generation has no root guard', async () => { + const chunk = await getRouteChunkIfEnabled( + new Map(), + config, + rootRouteId, + 'clientAction', + codeWithClientAction + ); + + await expectExports(chunk, ['clientAction'], ['default']); + }); + + it('does not enforce route chunk validity for the root route', () => { + expect(() => + validateRouteChunks({ + config: enforceConfig, + id: rootRouteId, + valid: { + clientAction: false, + clientLoader: false, + clientMiddleware: false, + HydrateFallback: false, + }, + }) + ).not.toThrow(); + }); + }); + + describe('enforce mode', () => { + it('allows all valid route chunks', () => { + expect(() => + validateRouteChunks({ + config: enforceConfig, + id: routeId, + valid: { + clientAction: true, + clientLoader: true, + clientMiddleware: true, + HydrateFallback: true, + }, + }) + ).not.toThrow(); + }); + + it('throws a singular guidance message for one invalid route chunk', () => { + expect(() => + validateRouteChunks({ + config: enforceConfig, + id: routeId, + valid: { + clientAction: false, + clientLoader: true, + clientMiddleware: true, + HydrateFallback: true, + }, + }) + ).toThrowError( + /Error splitting route module:[\s\S]*clientAction[\s\S]*This export[\s\S]*its own chunk[\s\S]*it shares/ + ); + }); + + it('throws a plural guidance message listing every invalid route chunk', () => { + expect(() => + validateRouteChunks({ + config: enforceConfig, + id: routeId, + valid: { + clientAction: false, + clientLoader: false, + clientMiddleware: true, + HydrateFallback: true, + }, + }) + ).toThrowError( + /Error splitting route module:[\s\S]*clientAction[\s\S]*clientLoader[\s\S]*These exports[\s\S]*their own chunks[\s\S]*they share/ + ); + }); }); }); diff --git a/tests/route-watch.test.ts b/tests/route-watch.test.ts new file mode 100644 index 0000000..315f741 --- /dev/null +++ b/tests/route-watch.test.ts @@ -0,0 +1,392 @@ +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { describe, expect, it, rstest } from '@rstest/core'; +import { + createRouteManifestSnapshot, + createRouteTopologyWatcher, + ensureDevRestartMarker, + getRouteRestartMarkerPath, +} from '../src/route-watch'; + +describe('route watch restart marker', () => { + it('allows a topology callback to await watcher shutdown', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + const markerPath = join(root, 'build/.react-router-route-watch'); + const watchedDirectory = join(root, 'app'); + mkdirSync(watchedDirectory, { recursive: true }); + let topology = new Set(['initial']); + let triggerChange!: () => void; + let close!: () => Promise; + let callbackCompleted = false; + + try { + close = await createRouteTopologyWatcher({ + watchDirectory: watchedDirectory, + restartMarkerPath: markerPath, + getRouteTopology: async () => topology, + onRouteTopologyChange: async () => { + await close(); + callbackCompleted = true; + }, + onError: error => { + throw error; + }, + watchDirectoryEntry: (_directory, onChange) => { + triggerChange = onChange; + return { close: () => {} }; + }, + }); + + topology = new Set(['changed']); + triggerChange(); + + await expect.poll(() => callbackCompleted, { timeout: 2000 }).toBe(true); + } finally { + await close?.(); + rmSync(root, { recursive: true, force: true }); + } + }); + + it('does not recreate watchers or touch the marker after close', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + const markerPath = join(root, 'build/.react-router-route-watch'); + const watchedDirectory = join(root, 'app'); + mkdirSync(watchedDirectory, { recursive: true }); + await ensureDevRestartMarker(markerPath); + const initialMarker = readFileSync(markerPath, 'utf8'); + let topologyReads = 0; + let releaseRescan!: () => void; + const rescanReleased = new Promise(resolve => { + releaseRescan = resolve; + }); + let markRescanStarted!: () => void; + const rescanStarted = new Promise(resolve => { + markRescanStarted = resolve; + }); + let triggerChange!: () => void; + const closeWatcher = rstest.fn(); + + try { + const close = await createRouteTopologyWatcher({ + watchDirectory: watchedDirectory, + restartMarkerPath: markerPath, + onError: error => { + throw error; + }, + getRouteTopology: async () => { + topologyReads += 1; + if (topologyReads === 1) { + return new Set(['initial']); + } + markRescanStarted(); + await rescanReleased; + return new Set(['changed']); + }, + watchDirectoryEntry: (_directory, onChange) => { + triggerChange = onChange; + return { close: closeWatcher }; + }, + }); + + triggerChange(); + await rescanStarted; + const closePromise = close(); + releaseRescan(); + await closePromise; + + expect(readFileSync(markerPath, 'utf8')).toBe(initialMarker); + expect(closeWatcher).toHaveBeenCalled(); + } finally { + releaseRescan(); + rmSync(root, { recursive: true, force: true }); + } + }); + + it('places the restart marker in the client build output', () => { + expect(getRouteRestartMarkerPath('/project/build/client')).toBe( + resolve('/project/build/client', '.react-router/route-watch') + ); + }); + + it('creates the restart marker when missing', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + try { + const markerPath = join(root, 'build/.react-router-route-watch'); + + await ensureDevRestartMarker(markerPath); + + expect(readFileSync(markerPath, 'utf8')).not.toBe(''); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('does not rewrite an existing restart marker on dev server startup', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + try { + const markerPath = join(root, 'build/.react-router-route-watch'); + mkdirSync(join(root, 'build'), { recursive: true }); + writeFileSync(markerPath, 'existing'); + + await ensureDevRestartMarker(markerPath); + + expect(readFileSync(markerPath, 'utf8')).toBe('existing'); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('reuses discovered topology when initial topology is already current', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + const markerPath = join(root, 'build/.react-router-route-watch'); + const watchedDirectory = join(root, 'app'); + mkdirSync(watchedDirectory, { recursive: true }); + let topologyReads = 0; + + try { + const close = await createRouteTopologyWatcher({ + watchDirectory: watchedDirectory, + restartMarkerPath: markerPath, + initialRouteTopology: new Set(['current']), + getRouteTopology: async () => { + topologyReads += 1; + return new Set(['current']); + }, + onError: error => { + throw error; + }, + watchDirectoryEntry: () => ({ close: () => {} }), + }); + await close(); + + expect(topologyReads).toBe(1); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('uses discovered topology to notify when initial topology is stale', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + const markerPath = join(root, 'build/.react-router-route-watch'); + const watchedDirectory = join(root, 'app'); + mkdirSync(watchedDirectory, { recursive: true }); + let topologyReads = 0; + const onRouteTopologyChange = rstest.fn(); + + try { + const close = await createRouteTopologyWatcher({ + watchDirectory: watchedDirectory, + restartMarkerPath: markerPath, + initialRouteTopology: new Set(['stale']), + getRouteTopology: async () => { + topologyReads += 1; + return new Set(['current']); + }, + onRouteTopologyChange, + onError: error => { + throw error; + }, + watchDirectoryEntry: () => ({ close: () => {} }), + }); + await close(); + + expect(topologyReads).toBe(1); + expect(onRouteTopologyChange).toHaveBeenCalledTimes(1); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('advances topology before reporting synchronous notification failures', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + const markerPath = join(root, 'build/.react-router-route-watch'); + const watchedDirectory = join(root, 'app'); + mkdirSync(watchedDirectory, { recursive: true }); + let topology = new Set(['initial']); + let triggerChange!: () => void; + const onRouteTopologyChange = rstest.fn(() => { + throw new Error('topology notification failed'); + }); + const onError = rstest.fn(); + + try { + const close = await createRouteTopologyWatcher({ + watchDirectory: watchedDirectory, + restartMarkerPath: markerPath, + getRouteTopology: async () => topology, + onRouteTopologyChange, + onError, + watchDirectoryEntry: (_directory, onChange) => { + triggerChange = onChange; + return { close: () => {} }; + }, + }); + + topology = new Set(['changed']); + triggerChange(); + await expect + .poll(() => onError.mock.calls.length, { timeout: 2000 }) + .toBe(1); + + triggerChange(); + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(onRouteTopologyChange).toHaveBeenCalledTimes(1); + await close(); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('retains discovered recovery directories when startup topology evaluation fails', async () => { + const root = mkdtempSync(join(tmpdir(), 'rr-route-watch-')); + const markerPath = join(root, 'build/.react-router-route-watch'); + const watchedDirectory = join(root, 'app'); + const helperDirectory = join(watchedDirectory, 'helpers'); + mkdirSync(helperDirectory, { recursive: true }); + const watchedDirectories: string[] = []; + const onError = rstest.fn(); + + try { + const close = await createRouteTopologyWatcher({ + watchDirectory: watchedDirectory, + restartMarkerPath: markerPath, + initialRouteTopology: new Set(['last-good']), + getRouteTopology: async () => { + throw new Error('route config failed'); + }, + onError, + watchDirectoryEntry: directory => { + watchedDirectories.push(directory); + return { close: () => {} }; + }, + }); + await close(); + + expect(onError).toHaveBeenCalledTimes(1); + expect(watchedDirectories).toEqual( + expect.arrayContaining([watchedDirectory, helperDirectory]) + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); + +describe('route watch topology snapshot', () => { + it('changes when route topology changes but route files stay the same', () => { + const baseRoutes = { + root: { id: 'root', path: '', file: 'root.tsx' }, + 'routes/demo': { + id: 'routes/demo', + parentId: 'root', + path: 'demo', + file: 'routes/demo.tsx', + }, + }; + + const changedRoutes = { + ...baseRoutes, + 'routes/demo': { + ...baseRoutes['routes/demo'], + path: 'renamed-demo', + }, + }; + + expect(createRouteManifestSnapshot(baseRoutes)).not.toEqual( + createRouteManifestSnapshot(changedRoutes) + ); + }); + + it('changes when sibling declaration order changes', () => { + const first = createRouteManifestSnapshot({ + root: { id: 'root', path: '', file: 'root.tsx' }, + 'routes/a': { + id: 'routes/a', + parentId: 'root', + path: ':value', + file: 'routes/a.tsx', + }, + 'routes/b': { + id: 'routes/b', + parentId: 'root', + path: ':value', + file: 'routes/b.tsx', + }, + }); + + const second = createRouteManifestSnapshot({ + root: { id: 'root', path: '', file: 'root.tsx' }, + 'routes/b': { + id: 'routes/b', + parentId: 'root', + path: ':value', + file: 'routes/b.tsx', + }, + 'routes/a': { + id: 'routes/a', + parentId: 'root', + path: ':value', + file: 'routes/a.tsx', + }, + }); + + expect(second).not.toEqual(first); + }); + + it('preserves ordered entries for numeric-like route IDs', () => { + const first = createRouteManifestSnapshot([ + ['root', { id: 'root', path: '', file: 'root.tsx' }], + [ + '2', + { + id: '2', + parentId: 'root', + path: ':value', + file: 'routes/two.tsx', + }, + ], + [ + '1', + { + id: '1', + parentId: 'root', + path: ':value', + file: 'routes/one.tsx', + }, + ], + ]); + + const second = createRouteManifestSnapshot([ + ['root', { id: 'root', path: '', file: 'root.tsx' }], + [ + '1', + { + id: '1', + parentId: 'root', + path: ':value', + file: 'routes/one.tsx', + }, + ], + [ + '2', + { + id: '2', + parentId: 'root', + path: ':value', + file: 'routes/two.tsx', + }, + ], + ]); + + expect(second).not.toEqual(first); + }); +}); diff --git a/tests/server-build-plan.test.ts b/tests/server-build-plan.test.ts new file mode 100644 index 0000000..9d6b39c --- /dev/null +++ b/tests/server-build-plan.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from '@rstest/core'; +import { + createReactRouterNodeEntries, + createReactRouterServerBuildPlan, +} from '../src/server-build-plan'; + +describe('React Router server build plan', () => { + it('creates a default-only plan when no server bundles are configured', () => { + expect( + createReactRouterServerBuildPlan({ + routesByServerBundleId: {}, + serverBuildFile: undefined, + defaultEntryName: 'static/js/app', + }) + ).toEqual({ + defaultEntryName: 'static/js/app', + entryNames: ['static/js/app'], + serverBundleEntries: [], + }); + }); + + it('creates deterministic bundle entries from the configured server build file', () => { + expect( + createReactRouterServerBuildPlan({ + routesByServerBundleId: { + admin: { + root: { id: 'root', file: 'root.tsx', path: '' }, + }, + empty: {}, + shop: { + root: { id: 'root', file: 'root.tsx', path: '' }, + }, + }, + serverBuildFile: 'server-entry.js', + defaultEntryName: 'static/js/app', + }) + ).toEqual({ + defaultEntryName: 'static/js/app', + entryNames: [ + 'static/js/app', + 'admin/server-entry', + 'shop/server-entry', + ], + serverBundleEntries: [ + { bundleId: 'admin', entryName: 'admin/server-entry' }, + { bundleId: 'shop', entryName: 'shop/server-entry' }, + ], + }); + }); + + it('rejects bundle entries that collide with reserved node entries', () => { + expect(() => + createReactRouterServerBuildPlan({ + routesByServerBundleId: { + 'static/js': { + root: { id: 'root', file: 'root.tsx', path: '' }, + }, + }, + serverBuildFile: 'app.js', + defaultEntryName: 'static/js/react-router-server-build', + }) + ).toThrow('conflicts with a reserved node entry'); + }); +}); + +describe('React Router node entries', () => { + const serverBundleEntries = [ + { bundleId: 'admin', entryName: 'admin/index' }, + ]; + + it('uses the generated server build as the app entry without a custom server', () => { + expect( + createReactRouterNodeEntries({ + hasServerApp: false, + isBuild: false, + serverAppPath: '/project/server/index.ts', + entryServerPath: '/project/app/entry.server.tsx', + defaultEntryName: 'static/js/app', + serverBundleEntries, + }) + ).toEqual({ + 'static/js/app': 'virtual/react-router/server-build', + 'static/js/entry.server': '/project/app/entry.server.tsx', + 'admin/index': 'virtual/react-router/server-build-admin', + }); + }); + + it('adds a private generated server build only for custom-server development', () => { + expect( + createReactRouterNodeEntries({ + hasServerApp: true, + isBuild: false, + serverAppPath: '/project/server/index.ts', + entryServerPath: '/project/app/entry.server.tsx', + defaultEntryName: 'static/js/react-router-server-build', + serverBundleEntries, + }) + ).toEqual({ + 'static/js/app': '/project/server/index.ts', + 'static/js/react-router-server-build': + 'virtual/react-router/server-build', + 'static/js/entry.server': '/project/app/entry.server.tsx', + 'admin/index': 'virtual/react-router/server-build-admin', + }); + }); + + it('omits the private generated server build during custom-server production builds', () => { + expect( + createReactRouterNodeEntries({ + hasServerApp: true, + isBuild: true, + serverAppPath: '/project/server/index.ts', + entryServerPath: '/project/app/entry.server.tsx', + defaultEntryName: 'static/js/react-router-server-build', + serverBundleEntries, + }) + ).toEqual({ + 'static/js/app': '/project/server/index.ts', + 'static/js/entry.server': '/project/app/entry.server.tsx', + 'admin/index': 'virtual/react-router/server-build-admin', + }); + }); +}); diff --git a/tests/server-utils.test.ts b/tests/server-utils.test.ts new file mode 100644 index 0000000..5547ffe --- /dev/null +++ b/tests/server-utils.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from '@rstest/core'; +import type { ServerBuild } from 'react-router'; +import { resolveReactRouterServerBuild } from '../src'; + +const createBuild = (version: string): ServerBuild => + ({ + entry: { module: { default: () => new Response() } }, + routes: {}, + assets: { routes: {}, version }, + assetsBuildDirectory: '/app/build/client', + basename: '/', + future: {}, + isSpaMode: false, + prerender: [], + publicPath: '/', + routeDiscovery: { mode: 'initial' }, + ssr: true, + }) as unknown as ServerBuild; + +describe('resolveReactRouterServerBuild', () => { + it('accepts a direct ESM server build', async () => { + const build = createBuild('esm'); + + await expect(resolveReactRouterServerBuild(build)).resolves.toMatchObject({ + assets: { version: 'esm' }, + }); + }); + + it('accepts lazy route discovery with its optional manifest path omitted', async () => { + const build = { + ...createBuild('lazy'), + routeDiscovery: { mode: 'lazy' as const }, + }; + + await expect(resolveReactRouterServerBuild(build)).resolves.toMatchObject({ + assets: { version: 'lazy' }, + routeDiscovery: { mode: 'lazy' }, + }); + }); + + it('unwraps CommonJS dynamic-import namespaces', async () => { + const build = createBuild('commonjs'); + + await expect( + resolveReactRouterServerBuild({ + default: build, + 'module.exports': build, + }) + ).resolves.toMatchObject({ assets: { version: 'commonjs' } }); + }); + + it('prefers module.exports when a CommonJS namespace default is not a server build', async () => { + const build = createBuild('module-exports'); + + await expect( + resolveReactRouterServerBuild({ + default: { routes: {} }, + 'module.exports': build, + }) + ).resolves.toMatchObject({ assets: { version: 'module-exports' } }); + }); + + it('resolves recognized asynchronous build exports', async () => { + const build = createBuild('async'); + + await expect( + resolveReactRouterServerBuild({ + ...build, + assets: async () => build.assets, + }) + ).resolves.toMatchObject({ assets: { version: 'async' } }); + }); + + it('rejects modules without a React Router server build', async () => { + await expect( + resolveReactRouterServerBuild({ default: { routes: {} } }) + ).rejects.toThrow('valid React Router ServerBuild'); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts index a860a14..463ed4a 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -10,6 +10,17 @@ rstest.mock('jiti', () => ({ createJiti: () => ({ import: rstest.fn().mockImplementation((path) => { if (path.includes('routes.ts')) { + const routeCount = Number(process.env.RR_TEST_ROUTE_COUNT ?? 0); + if (routeCount > 0) { + const childRouteCount = Math.max(0, routeCount - 1); + return Promise.resolve( + Array.from({ length: childRouteCount }, (_, index) => ({ + id: `routes/route-${index}`, + file: `routes/route-${index}.tsx`, + index: index === 0, + })) + ); + } return Promise.resolve([ { id: 'routes/index', @@ -18,6 +29,11 @@ rstest.mock('jiti', () => ({ }, ]); } + if (process.env.RR_TEST_SPLIT_ROUTE_MODULES === 'true') { + return Promise.resolve({ + splitRouteModules: true, + }); + } return Promise.resolve({}); }), }), @@ -51,7 +67,7 @@ const deepMerge = (base: any, overrides: any): any => { // Mock the @scripts/test-helper module rstest.mock('@scripts/test-helper', () => ({ - createStubRsbuild: rstest.fn().mockImplementation(async ({ rsbuildConfig = {} } = {}) => { + createStubRsbuild: rstest.fn().mockImplementation(async ({ action = 'dev', rsbuildConfig = {} } = {}) => { const baseConfig = { dev: { // Match Rsbuild defaults so plugin changes are observable in tests. @@ -91,7 +107,7 @@ rstest.mock('@scripts/test-helper', () => ({ tools: { rspack: { plugins: [ - { constructor: { name: 'RspackVirtualModulePlugin' } }, + { constructor: { name: 'VirtualModulesPlugin' } }, ], }, }, @@ -110,8 +126,13 @@ rstest.mock('@scripts/test-helper', () => ({ unwrapConfig: rstest.fn(), processAssets: rstest.fn(), onBeforeStartDevServer: rstest.fn(), + onCloseDevServer: rstest.fn(), + onCloseBuild: rstest.fn(), onBeforeBuild: rstest.fn(), onAfterBuild: rstest.fn(), + onBeforeDevCompile: rstest.fn(), + onAfterDevCompile: rstest.fn(), + onAfterCreateCompiler: rstest.fn(), getNormalizedConfig: rstest.fn().mockImplementation(() => mergedConfig), modifyRsbuildConfig: rstest.fn(), onAfterEnvironmentCompile: rstest.fn(), @@ -127,7 +148,7 @@ rstest.mock('@scripts/test-helper', () => ({ }, context: { rootPath: '/Users/bytedance/dev/rsbuild-plugin-react-router', - action: 'dev', + action, }, compiler: { webpack: { diff --git a/tests/warn-on-client-source-maps.test.ts b/tests/warn-on-client-source-maps.test.ts index bf385bc..40b600b 100644 --- a/tests/warn-on-client-source-maps.test.ts +++ b/tests/warn-on-client-source-maps.test.ts @@ -1,15 +1,23 @@ import { describe, expect, it, rstest } from '@rstest/core'; -import { warnOnClientSourceMaps } from '../src/warnings/warn-on-client-source-maps'; +import type { NormalizedConfig } from '@rsbuild/core'; +import { + isSourceMapEnabled, + warnOnClientSourceMaps, +} from '../src/warnings/warn-on-client-source-maps'; + +const normalizedConfig = ( + config: Record +): NormalizedConfig => config as NormalizedConfig; describe('warnOnClientSourceMaps', () => { it('does not warn in non-production mode', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'development', output: { sourceMap: { js: 'source-map', css: false } }, environments: {}, - } as any, + }), warn ); expect(warn).not.toHaveBeenCalled(); @@ -18,11 +26,11 @@ describe('warnOnClientSourceMaps', () => { it('warns when web environment source maps are enabled in production', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'production', output: { sourceMap: false }, environments: { web: { output: { sourceMap: { js: 'source-map' } } } }, - } as any, + }), warn ); expect(warn).toHaveBeenCalledTimes(1); @@ -34,11 +42,11 @@ describe('warnOnClientSourceMaps', () => { it('warns when output.sourceMap is true in production', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'production', output: { sourceMap: true }, environments: {}, - } as any, + }), warn ); expect(warn).toHaveBeenCalledTimes(1); @@ -47,24 +55,29 @@ describe('warnOnClientSourceMaps', () => { it('warns when output.sourceMap is a string in production', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'production', output: { sourceMap: 'source-map' }, environments: {}, - } as any, + }), warn ); expect(warn).toHaveBeenCalledTimes(1); }); + it('treats string output.sourceMap values as enabled', () => { + expect(isSourceMapEnabled('source-map')).toBe(true); + expect(isSourceMapEnabled('hidden-source-map')).toBe(true); + }); + it('does not warn when source maps are disabled in production', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'production', output: { sourceMap: false }, environments: { web: { output: { sourceMap: false } } }, - } as any, + }), warn ); expect(warn).not.toHaveBeenCalled(); @@ -72,12 +85,12 @@ describe('warnOnClientSourceMaps', () => { it('warns when rspack devtool enables source maps in production', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'production', output: { sourceMap: false }, tools: { rspack: { devtool: 'source-map' } }, environments: {}, - } as any, + }), warn ); expect(warn).toHaveBeenCalledTimes(1); @@ -86,13 +99,13 @@ describe('warnOnClientSourceMaps', () => { it('warns when web environment devtool enables source maps in production', () => { const warn = rstest.fn(); warnOnClientSourceMaps( - { + normalizedConfig({ mode: 'production', output: { sourceMap: false }, environments: { web: { tools: { rspack: { devtool: 'inline-source-map' } } }, }, - } as any, + }), warn ); expect(warn).toHaveBeenCalledTimes(1);