From 438a3a19b792046a9d3290b65ce13fe87079ac76 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:11:48 +0000 Subject: [PATCH 01/15] feat: support react router 8 compatibility --- .changeset/fresh-router-matrix.md | 9 + package.json | 9 +- pnpm-lock.yaml | 2 +- scripts/test-react-router-version-matrix.mjs | 216 +++++++++++++++++++ src/index.ts | 4 +- src/modify-browser-manifest.ts | 5 +- src/prerender.ts | 16 +- src/react-router-config.ts | 58 ++++- tests/modify-browser-manifest.test.ts | 46 ++++ tests/prerender.test.ts | 56 +++++ tests/react-router-config.test.ts | 35 ++- 11 files changed, 432 insertions(+), 24 deletions(-) create mode 100644 .changeset/fresh-router-matrix.md create mode 100644 scripts/test-react-router-version-matrix.mjs diff --git a/.changeset/fresh-router-matrix.md b/.changeset/fresh-router-matrix.md new file mode 100644 index 00000000..5575a7a9 --- /dev/null +++ b/.changeset/fresh-router-matrix.md @@ -0,0 +1,9 @@ +--- +'rsbuild-plugin-react-router': minor +--- + +Add React Router 8 compatibility while preserving React Router 7 behavior. +Stable `subResourceIntegrity` and `prerender.concurrency` config fields are now +supported alongside React Router 7 aliases, prerender data requests default to +the correct React Router major-version format, and the package test suite now +packs the plugin and smoke-builds real React Router 7 and 8 apps. diff --git a/package.json b/package.json index 5cf15b28..5c9148a1 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "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 test:package-interop && pnpm --filter './examples/{default-template,spa-mode,prerender,custom-node-server,cloudflare,client-only}' test:e2e", + "e2e": "pnpm build && pnpm test:package-interop && pnpm test:react-router-matrix && 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", @@ -65,6 +65,7 @@ "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", + "test:react-router-matrix": "pnpm build && node scripts/test-react-router-version-matrix.mjs", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", "changeset": "changeset", @@ -73,7 +74,7 @@ "release:local": "pnpm build && changeset version && changeset publish && git add . && git commit -m \"chore: version packages\" && git push && git push --tags" }, "dependencies": { - "@react-router/node": "^7.13.0", + "@react-router/node": "^7.0.0 || ^8.0.0", "@remix-run/node-fetch-server": "^0.13.0", "@rspack/plugin-react-refresh": "^2.0.2", "execa": "^9.6.1", @@ -115,7 +116,9 @@ "typescript": "^5.9.3" }, "peerDependencies": { - "@rsbuild/core": "^2.0.0" + "@react-router/dev": "^7.0.0 || ^8.0.0", + "@rsbuild/core": "^2.0.0", + "react-router": "^7.0.0 || ^8.0.0" }, "publishConfig": { "access": "public", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 625a4e72..aa58b4ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: .: dependencies: '@react-router/node': - specifier: ^7.13.0 + specifier: ^7.0.0 || ^8.0.0 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 diff --git a/scripts/test-react-router-version-matrix.mjs b/scripts/test-react-router-version-matrix.mjs new file mode 100644 index 00000000..1128749f --- /dev/null +++ b/scripts/test-react-router-version-matrix.mjs @@ -0,0 +1,216 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process'; +import { + access, + mkdir, + mkdtemp, + readdir, + rm, + writeFile, +} from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +const rootDir = process.cwd(); +const versions = (process.env.RR_COMPAT_VERSIONS ?? '7.13.0,8.0.1') + .split(',') + .map(version => version.trim()) + .filter(Boolean); + +const run = (command, args, options = {}) => + new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: options.cwd ?? rootDir, + env: { + ...process.env, + COREPACK_ENABLE_DOWNLOAD_PROMPT: '0', + ...options.env, + }, + stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit', + }); + + let stdout = ''; + let stderr = ''; + if (options.capture) { + child.stdout?.on('data', chunk => { + stdout += chunk; + }); + child.stderr?.on('data', chunk => { + stderr += chunk; + }); + } + + child.on('error', reject); + child.on('close', code => { + if (code === 0) { + resolve({ stdout, stderr }); + return; + } + reject( + new Error( + `${command} ${args.join(' ')} failed with exit code ${code}\n${stderr}` + ) + ); + }); + }); + +const writeFixture = async ({ appDir, version, tarball }) => { + await mkdir(path.join(appDir, 'app', 'routes'), { recursive: true }); + + await writeFile( + path.join(appDir, 'package.json'), + `${JSON.stringify( + { + private: true, + type: 'module', + scripts: { + build: 'rsbuild build', + }, + dependencies: { + '@react-router/dev': version, + '@react-router/node': version, + '@rsbuild/core': '2.1.0', + '@rsbuild/plugin-react': '2.1.0', + react: '^19.2.4', + 'react-dom': '^19.2.4', + 'react-router': version, + 'rsbuild-plugin-react-router': `file:${tarball}`, + }, + devDependencies: { + '@types/node': '^25.0.10', + '@types/react': '^19.2.10', + '@types/react-dom': '^19.2.3', + typescript: '^5.9.3', + }, + }, + null, + 2 + )}\n` + ); + + await writeFile( + path.join(appDir, 'rsbuild.config.ts'), + `import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginReactRouter } from 'rsbuild-plugin-react-router'; + +export default defineConfig({ + plugins: [pluginReactRouter(), pluginReact()], +}); +` + ); + + await writeFile( + path.join(appDir, 'react-router.config.ts'), + `export default { + ssr: true, + routeDiscovery: { mode: 'initial' }, + splitRouteModules: true, + subResourceIntegrity: true, + prerender: { paths: ['/'], concurrency: 1 }, +}; +` + ); + + await writeFile( + path.join(appDir, 'app', 'root.tsx'), + `import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from 'react-router'; + +export default function Root() { + return ( + + + + + + + + + + + + ); +} +` + ); + + await writeFile( + path.join(appDir, 'app', 'routes.ts'), + `import { index } from '@react-router/dev/routes'; + +export default [index('routes/index.tsx')]; +` + ); + + await writeFile( + path.join(appDir, 'app', 'routes', 'index.tsx'), + `export function loader() { + return { message: 'React Router ${version}' }; +} + +export default function Index() { + return
React Router ${version} compatibility
; +} +` + ); +}; + +const assertFile = async file => { + try { + await access(file); + } catch { + throw new Error(`Expected ${file} to exist`); + } +}; + +const assertBrowserManifest = async clientDir => { + const jsDir = path.join(clientDir, 'static', 'js'); + const files = await readdir(jsDir, { recursive: true }); + if ( + !files.some( + file => + typeof file === 'string' && + (file.startsWith('manifest-') || + file === path.join('virtual', 'react-router', 'browser-manifest.js')) + ) + ) { + throw new Error(`Expected ${jsDir} to contain a React Router manifest`); + } +}; + +const main = async () => { + const tempRoot = await mkdtemp(path.join(tmpdir(), 'rr-version-matrix-')); + try { + const packResult = await run( + 'npm', + ['pack', '--json', '--pack-destination', tempRoot], + { capture: true } + ); + const [packInfo] = JSON.parse(packResult.stdout); + const tarball = path.join(tempRoot, packInfo.filename); + + for (const version of versions) { + const appDir = path.join(tempRoot, `rr-${version}`); + await mkdir(appDir, { recursive: true }); + await writeFixture({ appDir, version, tarball }); + await run('pnpm', ['install'], { cwd: appDir }); + await run('pnpm', ['build'], { cwd: appDir }); + + await assertFile(path.join(appDir, 'build', 'client', 'index.html')); + await assertFile(path.join(appDir, 'build', 'server', 'index.js')); + await assertBrowserManifest(path.join(appDir, 'build', 'client')); + + console.log(`React Router ${version} package smoke passed.`); + } + } finally { + await rm(tempRoot, { recursive: true, force: true }); + } +}; + +await main(); diff --git a/src/index.ts b/src/index.ts index 84d1b2ce..18d1526c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -266,6 +266,7 @@ export const pluginReactRouter = ( serverModuleFormat, splitRouteModules, buildEnd, + subResourceIntegrity, } = resolvedConfig; const hasExplicitServerOutput = Object.prototype.hasOwnProperty.call( @@ -430,7 +431,7 @@ export const pluginReactRouter = ( resolvedConfigWithRoutes; for (const preset of configPresets) { await preset.reactRouterConfigResolved?.({ - reactRouterConfig: resolvedConfigForPreset, + reactRouterConfig: resolvedConfigForPreset as never, }); } @@ -1004,6 +1005,7 @@ export const pluginReactRouter = ( { subResourceIntegrity: resolvedConfigWithRoutes.subResourceIntegrity, future, + subResourceIntegrity, manifestChunkNames, onManifest: (manifest, sri, moduleExportsByRouteId, context) => stageLatestManifests( diff --git a/src/modify-browser-manifest.ts b/src/modify-browser-manifest.ts index 76ad4689..a66fc489 100644 --- a/src/modify-browser-manifest.ts +++ b/src/modify-browser-manifest.ts @@ -33,6 +33,7 @@ type CompilationWithIntegrityAssets = | Pick; type ModifyBrowserManifestOptions = { + subResourceIntegrity?: boolean; future?: { unstable_subResourceIntegrity?: boolean }; subResourceIntegrity?: boolean; manifestChunkNames?: ReadonlySet; @@ -132,8 +133,8 @@ export function registerModifyBrowserManifestAssets( ); const finalizeSri = Boolean( routeChunkOptions?.isBuild && - (options?.subResourceIntegrity ?? - options?.future?.unstable_subResourceIntegrity) + (options?.subResourceIntegrity ?? + options?.future?.unstable_subResourceIntegrity) ); const generatedManifests = finalizeSri ? new WeakMap() diff --git a/src/prerender.ts b/src/prerender.ts index d8613d6b..e49d00ab 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -13,12 +13,11 @@ type PrerenderPathsConfig = getStaticPaths: () => string[]; }) => boolean | string[] | Promise); -type PrerenderConfigObject = Extract< - NonNullable, - { paths: unknown } -> & { +type PrerenderConfigObject = { + paths?: PrerenderPathsConfig; + concurrency?: number; unstable_concurrency?: number; -}; +} & Record; type PrerenderConfig = ReactRouterPrerenderConfig | PrerenderConfigObject; type PrerenderConcurrencyConfig = @@ -78,7 +77,11 @@ export const createPrerenderRoutes = ( grouped: Record = groupRoutesByParentId(manifest) ): MatchRouteObject[] => { return (grouped[parentId] || []).map(route => { - const common = { id: route.id, path: route.path }; + const common = { + id: route.id, + path: route.path, + caseSensitive: route.caseSensitive, + }; if (route.index) { return { index: true, ...common } as MatchRouteObject; } @@ -142,6 +145,7 @@ export const getSsrFalsePrerenderExportErrors = ({ if (exports.includes('headers')) invalidApis.push('headers'); if (exports.includes('action')) invalidApis.push('action'); + if (exports.includes('middleware')) invalidApis.push('middleware'); if (invalidApis.length > 0) { errors.push( diff --git a/src/react-router-config.ts b/src/react-router-config.ts index 7154ca87..84e68b01 100644 --- a/src/react-router-config.ts +++ b/src/react-router-config.ts @@ -2,9 +2,12 @@ import type { BuildManifest as ReactRouterBuildManifest, Config as ReactRouterConfig, } from '@react-router/dev/config'; +import { createRequire } from 'node:module'; import type { NormalizedConfig } from '@rsbuild/core'; import type { RouteConfigEntry } from '@react-router/dev/routes'; +const require = createRequire(import.meta.url); + export type BuildEndHook = { bivarianceHack(args: { buildManifest: ReactRouterBuildManifest | undefined; @@ -17,10 +20,15 @@ type SplitRouteModulesConfig = boolean | 'enforce'; export type Config = Omit< ReactRouterConfig, - 'buildEnd' | 'future' | 'splitRouteModules' | 'subResourceIntegrity' + | 'buildEnd' + | 'future' + | 'prerender' + | 'splitRouteModules' + | 'subResourceIntegrity' > & { buildEnd?: BuildEndHook; future?: Partial; + prerender?: PrerenderConfig; splitRouteModules?: SplitRouteModulesConfig; subResourceIntegrity?: boolean; }; @@ -34,6 +42,15 @@ type FutureConfig = { v8_viteEnvironmentApi: boolean; }; +type PrerenderConfig = + | ReactRouterConfig['prerender'] + | ({ + paths?: ReactRouterConfig['prerender']; + concurrency?: number; + unstable_concurrency?: number; + } & Record) + | undefined; + type RouteManifestEntry = { id: string; parentId?: string; @@ -45,6 +62,23 @@ type RouteManifestEntry = { type RouteManifest = Record; +const getInstalledReactRouterVersion = (): string | undefined => { + try { + return ( + require('react-router/package.json') as { version?: string | undefined } + ).version; + } catch { + return undefined; + } +}; + +export const getDefaultTrailingSlashAwareDataRequests = ( + reactRouterVersion: string | undefined = getInstalledReactRouterVersion() +): boolean => { + const major = Number(reactRouterVersion?.split('.')[0]); + return Number.isInteger(major) && major >= 8; +}; + export type ResolvedReactRouterConfig = Readonly<{ appDirectory: string; basename: string; @@ -64,6 +98,16 @@ export type ResolvedReactRouterConfig = Readonly<{ unstable_routeConfig: RouteConfigEntry[]; }>; +const createDefaultFutureConfig = (): FutureConfig => ({ + unstable_optimizeDeps: false, + unstable_subResourceIntegrity: false, + unstable_trailingSlashAwareDataRequests: + getDefaultTrailingSlashAwareDataRequests(), + v8_middleware: false, + v8_splitRouteModules: false, + v8_viteEnvironmentApi: false, +}); + const DEFAULT_CONFIG = { appDirectory: 'app', basename: '/', @@ -73,14 +117,7 @@ const DEFAULT_CONFIG = { splitRouteModules: true, subResourceIntegrity: false, ssr: true, - future: { - unstable_optimizeDeps: false, - unstable_subResourceIntegrity: false, - unstable_trailingSlashAwareDataRequests: false, - v8_middleware: false, - v8_splitRouteModules: false, - v8_viteEnvironmentApi: false, - } satisfies FutureConfig, + future: createDefaultFutureConfig(), routeDiscovery: undefined, prerender: undefined, serverBundles: undefined, @@ -165,7 +202,8 @@ export const resolveReactRouterConfig = async ( const { buildEnd: _buildEnd, ...reactRouterUserConfigForPreset } = reactRouterUserConfig; const presetConfig = await preset.reactRouterConfig({ - reactRouterUserConfig: reactRouterUserConfigForPreset, + reactRouterUserConfig: + reactRouterUserConfigForPreset as ReactRouterConfig, }); if (!presetConfig) return null; const { presets: _presets, ...rest } = presetConfig as Config; diff --git a/tests/modify-browser-manifest.test.ts b/tests/modify-browser-manifest.test.ts index eb7a3270..7c27c4f7 100644 --- a/tests/modify-browser-manifest.test.ts +++ b/tests/modify-browser-manifest.test.ts @@ -328,6 +328,52 @@ describe('modify browser manifest plugin', () => { } }); + it('enables SRI from the stable subResourceIntegrity config field', async () => { + const { root, appDir } = createTempApp(); + const harness = createProcessAssetsHarness(); + const optimizedEntrySource = 'console.log("stable sri");'; + const assets = { + ...createBrowserManifestAssets(), + 'static/js/entry.client.js': createAsset(optimizedEntrySource), + }; + const compilation = createCompilation( + [['entry.client', { files: new Set(['static/js/entry.client.js']) }]], + assets + ); + let reportedSri: Record | undefined; + + try { + registerModifyBrowserManifestAssets( + harness.api as never, + { root: rootRoute }, + {}, + appDir, + '/', + { isBuild: true }, + { + 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 }); + await harness.runStage('report', { assets, compilation }); + + expect(reportedSri?.['/static/js/entry.client.js']).toBe( + createSriHash(optimizedEntrySource) + ); + } 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(); diff --git a/tests/prerender.test.ts b/tests/prerender.test.ts index a9ae9db0..529b3406 100644 --- a/tests/prerender.test.ts +++ b/tests/prerender.test.ts @@ -109,6 +109,17 @@ describe('prerender helpers', () => { expect(getPrerenderConcurrency({ paths: ['/'] }, 2)).toBe(1); }); + it('validates stable prerender concurrency config', () => { + expect( + validatePrerenderConfig({ paths: ['/'], concurrency: 1 } as any) + ).toBeNull(); + expect( + validatePrerenderConfig({ paths: ['/'], concurrency: 1.5 } as any) + ).toBe( + 'The `prerender.concurrency` config must be a positive integer if specified.' + ); + }); + it('creates React Router match routes from a route manifest', () => { expect( createPrerenderRoutes({ @@ -236,6 +247,51 @@ describe('prerender helpers', () => { ]); }); + it('reports invalid ssr:false prerender middleware 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: ['middleware'], + }, + prerenderPaths: ['/dashboard'], + }) + ).toEqual([ + expect.stringContaining( + '`dashboard` when pre-rendering with `ssr:false`: `middleware`' + ), + ]); + }); + + it('preserves case sensitivity when matching prerender paths', () => { + const prerenderRoutes = createPrerenderRoutes({ + root: { id: 'root', file: 'root.tsx', path: '' }, + settings: { + id: 'settings', + parentId: 'root', + file: 'routes/settings.tsx', + path: 'Settings', + caseSensitive: true, + }, + }); + + expect(prerenderRoutes[0]?.children?.[0]).toMatchObject({ + id: 'settings', + caseSensitive: true, + }); + }); + it('reports loader exports on routes outside the ssr:false prerender set', () => { const manifestRoutes = { root: { id: 'root', file: 'root.tsx', path: '' }, diff --git a/tests/react-router-config.test.ts b/tests/react-router-config.test.ts index 7f8836aa..bfe32669 100644 --- a/tests/react-router-config.test.ts +++ b/tests/react-router-config.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from '@rstest/core'; -import { resolveReactRouterConfig } from '../src/react-router-config'; +import { + getDefaultTrailingSlashAwareDataRequests, + resolveReactRouterConfig, +} from '../src/react-router-config'; import type { Config } from '../src/react-router-config'; describe('resolveReactRouterConfig', () => { @@ -143,4 +146,34 @@ describe('resolveReactRouterConfig', () => { disabledByTopLevel.resolved.future.unstable_subResourceIntegrity ).toBe(false); }); + + it('keeps React Router 7 future aliases while exposing React Router 8 stable fields', async () => { + const result = await resolveReactRouterConfig({ + subResourceIntegrity: true, + future: { + unstable_subResourceIntegrity: true, + unstable_trailingSlashAwareDataRequests: true, + }, + } as any); + + expect(result.resolved.subResourceIntegrity).toBe(true); + expect(result.resolved.future.unstable_subResourceIntegrity).toBe(true); + expect( + result.resolved.future.unstable_trailingSlashAwareDataRequests + ).toBe(true); + }); + + it('uses the legacy subresource integrity future flag when the stable field is absent', async () => { + const result = await resolveReactRouterConfig({ + future: { unstable_subResourceIntegrity: true }, + } as any); + + expect(result.resolved.subResourceIntegrity).toBe(true); + }); + + it('defaults trailing slash-aware data requests for React Router 8 and newer', () => { + expect(getDefaultTrailingSlashAwareDataRequests('7.13.0')).toBe(false); + expect(getDefaultTrailingSlashAwareDataRequests('8.0.1')).toBe(true); + expect(getDefaultTrailingSlashAwareDataRequests('9.0.0')).toBe(true); + }); }); From faa0bb855369317f3fdf8eeefb2c2ec604b4b26d Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:44:31 +0000 Subject: [PATCH 02/15] chore: simplify react router compatibility helpers --- scripts/test-react-router-version-matrix.mjs | 44 ++++++++++---------- src/prerender.ts | 8 ++-- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/scripts/test-react-router-version-matrix.mjs b/scripts/test-react-router-version-matrix.mjs index 1128749f..a82bc918 100644 --- a/scripts/test-react-router-version-matrix.mjs +++ b/scripts/test-react-router-version-matrix.mjs @@ -56,10 +56,12 @@ const run = (command, args, options = {}) => const writeFixture = async ({ appDir, version, tarball }) => { await mkdir(path.join(appDir, 'app', 'routes'), { recursive: true }); + const writeAppFile = (file, contents) => + writeFile(path.join(appDir, file), contents); - await writeFile( - path.join(appDir, 'package.json'), - `${JSON.stringify( + await writeAppFile( + 'package.json', + JSON.stringify( { private: true, type: 'module', @@ -85,11 +87,11 @@ const writeFixture = async ({ appDir, version, tarball }) => { }, null, 2 - )}\n` + ) + '\n' ); - await writeFile( - path.join(appDir, 'rsbuild.config.ts'), + await writeAppFile( + 'rsbuild.config.ts', `import { defineConfig } from '@rsbuild/core'; import { pluginReact } from '@rsbuild/plugin-react'; import { pluginReactRouter } from 'rsbuild-plugin-react-router'; @@ -100,8 +102,8 @@ export default defineConfig({ ` ); - await writeFile( - path.join(appDir, 'react-router.config.ts'), + await writeAppFile( + 'react-router.config.ts', `export default { ssr: true, routeDiscovery: { mode: 'initial' }, @@ -112,8 +114,8 @@ export default defineConfig({ ` ); - await writeFile( - path.join(appDir, 'app', 'root.tsx'), + await writeAppFile( + path.join('app', 'root.tsx'), `import { Links, Meta, @@ -140,16 +142,16 @@ export default function Root() { ` ); - await writeFile( - path.join(appDir, 'app', 'routes.ts'), + await writeAppFile( + path.join('app', 'routes.ts'), `import { index } from '@react-router/dev/routes'; export default [index('routes/index.tsx')]; ` ); - await writeFile( - path.join(appDir, 'app', 'routes', 'index.tsx'), + await writeAppFile( + path.join('app', 'routes', 'index.tsx'), `export function loader() { return { message: 'React Router ${version}' }; } @@ -172,14 +174,12 @@ const assertFile = async file => { const assertBrowserManifest = async clientDir => { const jsDir = path.join(clientDir, 'static', 'js'); const files = await readdir(jsDir, { recursive: true }); - if ( - !files.some( - file => - typeof file === 'string' && - (file.startsWith('manifest-') || - file === path.join('virtual', 'react-router', 'browser-manifest.js')) - ) - ) { + const hasBrowserManifest = files.some( + file => + file.startsWith('manifest-') || + file === path.join('virtual', 'react-router', 'browser-manifest.js') + ); + if (!hasBrowserManifest) { throw new Error(`Expected ${jsDir} to contain a React Router manifest`); } }; diff --git a/src/prerender.ts b/src/prerender.ts index e49d00ab..bf67bc7d 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -141,11 +141,9 @@ export const getSsrFalsePrerenderExportErrors = ({ 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 (exports.includes('middleware')) invalidApis.push('middleware'); + const invalidApis = ['headers', 'action', 'middleware'].filter(api => + exports.includes(api) + ); if (invalidApis.length > 0) { errors.push( From 698cdb999cb9c7be8526c308fd11b878b47df229 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:52:30 +0000 Subject: [PATCH 03/15] chore: deslop react router config typing --- src/index.ts | 9 ++++++++- tests/react-router-config.test.ts | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 18d1526c..49e38d77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -90,6 +90,12 @@ export { resolveReactRouterServerBuild }; const MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES = 4; +type ReactRouterPresetResolvedConfig = Parameters< + NonNullable< + NonNullable[number]['reactRouterConfigResolved'] + > +>[0]['reactRouterConfig']; + export const shouldParallelizeEnvironmentBuilds = ({ isBuild, spareCoreCount = getDefaultConcurrency(), @@ -431,7 +437,8 @@ export const pluginReactRouter = ( resolvedConfigWithRoutes; for (const preset of configPresets) { await preset.reactRouterConfigResolved?.({ - reactRouterConfig: resolvedConfigForPreset as never, + reactRouterConfig: + resolvedConfigForPreset as ReactRouterPresetResolvedConfig, }); } diff --git a/tests/react-router-config.test.ts b/tests/react-router-config.test.ts index bfe32669..c9b1f033 100644 --- a/tests/react-router-config.test.ts +++ b/tests/react-router-config.test.ts @@ -60,10 +60,10 @@ describe('resolveReactRouterConfig', () => { 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); From 223a9b7b7d9797162f4ce01d3b672c6cd0e791cb Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 29 Jun 2026 00:47:02 +0000 Subject: [PATCH 04/15] test: add React Router 8 default template e2e --- README.md | 1 + examples/react-router-8/.gitignore | 6 + examples/react-router-8/README.md | 5 + examples/react-router-8/app/root.tsx | 19 ++ examples/react-router-8/app/routes.ts | 4 + examples/react-router-8/app/routes/_index.tsx | 16 + examples/react-router-8/env.d.ts | 2 + examples/react-router-8/package.json | 42 +++ examples/react-router-8/playwright.config.ts | 27 ++ examples/react-router-8/public/favicon.ico | Bin 0 -> 15086 bytes .../react-router-8/react-router.config.ts | 6 + examples/react-router-8/rsbuild.config.ts | 7 + .../tests/e2e/react-router-8.test.ts | 44 +++ examples/react-router-8/tsconfig.json | 22 ++ package.json | 2 +- pnpm-lock.yaml | 288 +++++++++++++++++- src/index.ts | 24 ++ src/plugin-utils.ts | 44 +-- src/route-component-transform.ts | 4 +- tests/index.test.ts | 17 ++ tests/plugin-utils.test.ts | 14 +- 21 files changed, 542 insertions(+), 52 deletions(-) create mode 100644 examples/react-router-8/.gitignore create mode 100644 examples/react-router-8/README.md create mode 100644 examples/react-router-8/app/root.tsx create mode 100644 examples/react-router-8/app/routes.ts create mode 100644 examples/react-router-8/app/routes/_index.tsx create mode 100644 examples/react-router-8/env.d.ts create mode 100644 examples/react-router-8/package.json create mode 100644 examples/react-router-8/playwright.config.ts create mode 100644 examples/react-router-8/public/favicon.ico create mode 100644 examples/react-router-8/react-router.config.ts create mode 100644 examples/react-router-8/rsbuild.config.ts create mode 100644 examples/react-router-8/tests/e2e/react-router-8.test.ts create mode 100644 examples/react-router-8/tsconfig.json diff --git a/README.md b/README.md index c1ace555..679631e2 100644 --- a/README.md +++ b/README.md @@ -781,6 +781,7 @@ The repository includes several examples demonstrating different use cases: | [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` | +| [react-router-8](./examples/react-router-8) | React Router 8 framework-mode SSR | 3020 | `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` | diff --git a/examples/react-router-8/.gitignore b/examples/react-router-8/.gitignore new file mode 100644 index 00000000..c08251ce --- /dev/null +++ b/examples/react-router-8/.gitignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +.env +.react-router diff --git a/examples/react-router-8/README.md b/examples/react-router-8/README.md new file mode 100644 index 00000000..d6107db5 --- /dev/null +++ b/examples/react-router-8/README.md @@ -0,0 +1,5 @@ +# React Router 8 Default Template + +This example starts as a direct copy of React Router's `integration/helpers/vite-8-template` and swaps the Vite config/scripts for `rsbuild-plugin-react-router`. + +Run `pnpm --filter react-router-8-default-template test:e2e` to exercise development and production browser flows. diff --git a/examples/react-router-8/app/root.tsx b/examples/react-router-8/app/root.tsx new file mode 100644 index 00000000..b36392b4 --- /dev/null +++ b/examples/react-router-8/app/root.tsx @@ -0,0 +1,19 @@ +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; + +export default function App() { + return ( + + + + + + + + + + + + + + ); +} diff --git a/examples/react-router-8/app/routes.ts b/examples/react-router-8/app/routes.ts new file mode 100644 index 00000000..4c05936c --- /dev/null +++ b/examples/react-router-8/app/routes.ts @@ -0,0 +1,4 @@ +import { type RouteConfig } from "@react-router/dev/routes"; +import { flatRoutes } from "@react-router/fs-routes"; + +export default flatRoutes() satisfies RouteConfig; diff --git a/examples/react-router-8/app/routes/_index.tsx b/examples/react-router-8/app/routes/_index.tsx new file mode 100644 index 00000000..ecfc25c6 --- /dev/null +++ b/examples/react-router-8/app/routes/_index.tsx @@ -0,0 +1,16 @@ +import type { MetaFunction } from "react-router"; + +export const meta: MetaFunction = () => { + return [ + { title: "New React Router App" }, + { name: "description", content: "Welcome to React Router!" }, + ]; +}; + +export default function Index() { + return ( +
+

Welcome to React Router

+
+ ); +} diff --git a/examples/react-router-8/env.d.ts b/examples/react-router-8/env.d.ts new file mode 100644 index 00000000..5e7dfe5d --- /dev/null +++ b/examples/react-router-8/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/react-router-8/package.json b/examples/react-router-8/package.json new file mode 100644 index 00000000..52999308 --- /dev/null +++ b/examples/react-router-8/package.json @@ -0,0 +1,42 @@ +{ + "name": "react-router-8-default-template", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "NODE_OPTIONS=\"--experimental-vm-modules --experimental-global-webcrypto\" rsbuild dev --port 3020 --host 127.0.0.1", + "build": "rsbuild build", + "start": "HOST=127.0.0.1 PORT=3020 react-router-serve ./build/server/static/js/app.js", + "test:e2e": "pnpm run test:e2e:dev && pnpm run build && pnpm run test:e2e:prod", + "test:e2e:dev": "playwright test", + "test:e2e:prod": "cross-env RR8_E2E_MODE=production playwright test", + "typecheck": "react-router typegen && tsc" + }, + "dependencies": { + "@react-router/fs-routes": "^8.0.1", + "@react-router/node": "^8.0.1", + "@react-router/serve": "^8.0.1", + "@vanilla-extract/css": "^1.20.1", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-router": "^8.0.1", + "serialize-javascript": "^6.0.1" + }, + "devDependencies": { + "@playwright/test": "^1.58.0", + "@react-router/dev": "^8.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", + "cross-env": "^10.1.0", + "rsbuild-plugin-react-router": "workspace:*", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-env-only": "^3.0.3" + }, + "engines": { + "node": ">=22.22.0" + } +} diff --git a/examples/react-router-8/playwright.config.ts b/examples/react-router-8/playwright.config.ts new file mode 100644 index 00000000..304b899b --- /dev/null +++ b/examples/react-router-8/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from '@playwright/test'; + +const isProduction = process.env.RR8_E2E_MODE === 'production'; + +export default defineConfig({ + testDir: './tests/e2e', + timeout: 30_000, + expect: { + timeout: 10_000, + }, + use: { + baseURL: 'http://127.0.0.1:3020', + trace: 'on-first-retry', + }, + webServer: { + command: isProduction ? 'pnpm run start' : 'pnpm run dev', + url: 'http://127.0.0.1:3020', + reuseExistingServer: !process.env.CI, + timeout: 60_000, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/examples/react-router-8/public/favicon.ico b/examples/react-router-8/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5dbdfcddcb14182535f6d32d1c900681321b1aa3 GIT binary patch literal 15086 zcmeI33v3ic7{|AFEmuJ-;v>ep_G*NPi6KM`qNryCe1PIJ8siIN1WZ(7qVa)RVtmC% z)Ch?tN+afMKm;5@rvorJk zcXnoOc4q51HBQnQH_jn!cAg&XI1?PlX>Kl^k8qq0;zkha`kY$Fxt#=KNJAE9CMdpW zqr4#g8`nTw191(+H4xW8Tmyru2I^3=J1G3emPxkPXA=3{vvuvse_WWSshqaqls^-m zgB7q8&Vk*aYRe?sn$n53dGH#%3y%^vxv{pL*-h0Z4bmb_(k6{FL7HWIz(V*HT#IcS z-wE{)+0x1U!RUPt3gB97%p}@oHxF4|6S*+Yw=_tLtxZ~`S=z6J?O^AfU>7qOX`JNBbV&8+bO0%@fhQitKIJ^O^ zpgIa__qD_y07t@DFlBJ)8SP_#^j{6jpaXt{U%=dx!qu=4u7^21lWEYHPPY5U3TcoQ zX_7W+lvZi>TapNk_X>k-KO%MC9iZp>1E`N34gHKd9tK&){jq2~7OsJ>!G0FzxQFw6G zm&Vb(2#-T|rM|n3>uAsG_hnbvUKFf3#ay@u4uTzia~NY%XgCHfx4^To4BDU@)HlV? z@EN=g^ymETa1sQK{kRwyE4Ax8?wT&GvaG@ASO}{&a17&^v`y z!oPdiSiia^oov(Z)QhG2&|FgE{M9_4hJROGbnj>#$~ZF$-G^|zPj*QApltKe?;u;uKHJ~-V!=VLkg7Kgct)l7u39f@%VG8e3f$N-B zAu3a4%ZGf)r+jPAYCSLt73m_J3}p>}6Tx0j(wg4vvKhP!DzgiWANiE;Ppvp}P2W@m z-VbYn+NXFF?6ngef5CfY6ZwKnWvNV4z6s^~yMXw2i5mv}jC$6$46g?G|CPAu{W5qF zDobS=zb2ILX9D827g*NtGe5w;>frjanY{f)hrBP_2ehBt1?`~ypvg_Ot4x1V+43P@Ve8>qd)9NX_jWdLo`Zfy zoeam9)@Dpym{4m@+LNxXBPjPKA7{3a&H+~xQvr>C_A;7=JrfK~$M2pCh>|xLz>W6SCs4qC|#V`)# z)0C|?$o>jzh<|-cpf

K7osU{Xp5PG4-K+L2G=)c3f&}H&M3wo7TlO_UJjQ-Oq&_ zjAc9=nNIYz{c3zxOiS5UfcE1}8#iI4@uy;$Q7>}u`j+OU0N<*Ezx$k{x_27+{s2Eg z`^=rhtIzCm!_UcJ?Db~Lh-=_))PT3{Q0{Mwdq;0>ZL%l3+;B&4!&xm#%HYAK|;b456Iv&&f$VQHf` z>$*K9w8T+paVwc7fLfMlhQ4)*zL_SG{~v4QR;IuX-(oRtYAhWOlh`NLoX0k$RUYMi z2Y!bqpdN}wz8q`-%>&Le@q|jFw92ErW-hma-le?S z-@OZt2EEUm4wLsuEMkt4zlyy29_3S50JAcQHTtgTC{P~%-mvCTzrjXOc|{}N`Cz`W zSj7CrXfa7lcsU0J(0uSX6G`54t^7}+OLM0n(|g4waOQ}bd3%!XLh?NX9|8G_|06Ie zD5F1)w5I~!et7lA{G^;uf7aqT`KE&2qx9|~O;s6t!gb`+zVLJyT2T)l*8l(j literal 0 HcmV?d00001 diff --git a/examples/react-router-8/react-router.config.ts b/examples/react-router-8/react-router.config.ts new file mode 100644 index 00000000..f26e5657 --- /dev/null +++ b/examples/react-router-8/react-router.config.ts @@ -0,0 +1,6 @@ +export default { + ssr: true, + routeDiscovery: { mode: 'initial' }, + splitRouteModules: true, + subResourceIntegrity: true, +}; diff --git a/examples/react-router-8/rsbuild.config.ts b/examples/react-router-8/rsbuild.config.ts new file mode 100644 index 00000000..36cc0bc3 --- /dev/null +++ b/examples/react-router-8/rsbuild.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginReactRouter } from 'rsbuild-plugin-react-router'; + +export default defineConfig({ + plugins: [pluginReactRouter(), pluginReact()], +}); diff --git a/examples/react-router-8/tests/e2e/react-router-8.test.ts b/examples/react-router-8/tests/e2e/react-router-8.test.ts new file mode 100644 index 00000000..ea1187b1 --- /dev/null +++ b/examples/react-router-8/tests/e2e/react-router-8.test.ts @@ -0,0 +1,44 @@ +import { expect, test } from '@playwright/test'; + +test('renders the React Router 8 default template without browser errors', async ({ + page, +}) => { + const browserProblems: string[] = []; + page.on('console', message => { + if (message.type() === 'error') { + browserProblems.push(`console error: ${message.text()}`); + } + }); + page.on('pageerror', error => { + browserProblems.push(`page error: ${error.message}`); + }); + page.on('response', response => { + if (response.status() >= 500) { + browserProblems.push(`${response.status()} response: ${response.url()}`); + } + }); + page.on('requestfailed', request => { + if (request.resourceType() !== 'websocket') { + browserProblems.push( + `${request.method()} ${request.url()} failed: ${ + request.failure()?.errorText ?? 'unknown error' + }` + ); + } + }); + + const response = await page.goto('/'); + expect(response?.ok()).toBe(true); + await expect( + page.getByRole('heading', { name: 'Welcome to React Router' }) + ).toBeVisible(); + await expect(page).toHaveTitle('New React Router App'); + await page.waitForFunction( + () => + (window as Window & { __reactRouterRouteModules?: unknown }) + .__reactRouterRouteModules !== undefined + ); + await page.waitForTimeout(250); + + expect(browserProblems).toEqual([]); +}); diff --git a/examples/react-router-8/tsconfig.json b/examples/react-router-8/tsconfig.json new file mode 100644 index 00000000..31a91e13 --- /dev/null +++ b/examples/react-router-8/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["env.d.ts", "**/*.ts", "**/*.tsx", ".react-router/types/**/*"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["node", "vite/client"], + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "esModuleInterop": true, + "jsx": "react-jsx", + "resolveJsonModule": true, + "allowJs": true, + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + "noEmit": true, + "rootDirs": [".", ".react-router/types/"], + "skipLibCheck": true, + "strict": true + } +} diff --git a/package.json b/package.json index 5c9148a1..797a04b1 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "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 test:package-interop && pnpm test:react-router-matrix && 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,react-router-8}' test:e2e", "dev": "rslib build --watch", "test": "rstest run", "test:watch": "rstest watch", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa58b4ab..e5c63cb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,7 @@ importers: version: 2.31.0(@types/node@25.0.10) '@react-router/dev': specifier: ^8.0.1 - 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)) + version: 8.0.1(@react-router/serve@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))(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 @@ -146,7 +146,7 @@ importers: dependencies: '@react-router/express': specifier: ^7.13.0 - 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) + 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.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) @@ -1584,6 +1584,70 @@ importers: specifier: ^6.0.5 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/react-router-8: + dependencies: + '@react-router/fs-routes': + specifier: ^8.0.1 + version: 8.0.1(@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)))(typescript@5.9.3) + '@react-router/node': + specifier: ^8.0.1 + version: 8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@react-router/serve': + specifier: ^8.0.1 + version: 8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@vanilla-extract/css': + specifier: ^1.20.1 + version: 1.21.0 + react: + specifier: ^19.2.4 + version: 19.2.7 + react-dom: + specifier: ^19.2.4 + version: 19.2.7(react@19.2.7) + react-router: + specifier: ^8.0.1 + version: 8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + serialize-javascript: + specifier: ^6.0.1 + version: 6.0.2 + devDependencies: + '@playwright/test': + specifier: ^1.58.0 + version: 1.58.0 + '@react-router/dev': + specifier: ^8.0.1 + version: 8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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/core': + 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.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 + '@types/react': + specifier: ^19.2.10 + version: 19.2.10 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.10) + cross-env: + specifier: ^10.1.0 + version: 10.1.0 + rsbuild-plugin-react-router: + specifier: workspace:* + version: link:../.. + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + 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-env-only: + specifier: ^3.0.3 + 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)) + examples/spa-mode: dependencies: '@react-router/express': @@ -2084,6 +2148,9 @@ packages: '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + '@epic-web/cachified@5.6.1': resolution: {integrity: sha512-+VKwMhqM43l2s+gX28Telcf6bUJk1Zaj0Ix2i8K4R2QW8WgPE0q3THCnr0xZg5chw35/B4SkHS43an2fqKOFnQ==} @@ -4037,6 +4104,27 @@ packages: typescript: optional: true + '@react-router/express@8.0.1': + resolution: {integrity: sha512-FWErptC9nFtaRo3SRsHgO60C1bCpUU35ATDvJulQIYXxDsXUdicyhJWCrl5DeEO2pUeqyPA4taP7l7aWkz2qZQ==} + engines: {node: '>=22.22.0'} + peerDependencies: + express: ^4.22.2 || ^5 + react-router: 8.0.1 + typescript: ^5.1.0 || ^6.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@react-router/fs-routes@8.0.1': + resolution: {integrity: sha512-AvVOgD51NONhWECESN8kATjq7bKRZg/PmHA/vk9DrRIYFxCPFe3xiEvtIIk5q6Ng61lQUHfUzx5e5K95VpIAiA==} + engines: {node: '>=22.22.0'} + peerDependencies: + '@react-router/dev': ^8.0.1 + 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'} @@ -4084,6 +4172,13 @@ packages: peerDependencies: react-router: 7.18.0 + '@react-router/serve@8.0.1': + resolution: {integrity: sha512-7kCZhE4cT0y4JMHpG1bJoIfy9tYWSqDqzZUYylQL9UCLYg9vq84X33UC6Xi9eQB9SRAuDM5iKQtTrGEstIMVKA==} + engines: {node: '>=22.22.0'} + hasBin: true + peerDependencies: + react-router: 8.0.1 + '@remix-run/node-fetch-server@0.13.3': resolution: {integrity: sha512-UfjOXed/DQteaM5VyTfqTeGpHwyL2J5aoRGY6cydip4tt1ehNNeSwuXCC7AEGE0RWBs/7bgKxYkL/B/+UDe4AA==} @@ -5321,6 +5416,12 @@ packages: cpu: [x64] os: [win32] + '@vanilla-extract/css@1.21.0': + resolution: {integrity: sha512-lqdRtP622Z85RprHlJemV5+ipdi+g48J115LaL8nrI64iixIp4SWPlvAEPf3o9pwEZaZPb5/ZfRwiXLE4p3+kQ==} + + '@vanilla-extract/private@1.0.9': + resolution: {integrity: sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==} + '@vitejs/plugin-react@5.1.2': resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6085,6 +6186,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} @@ -6288,6 +6392,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deep-object-diff@1.1.9: + resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -6878,6 +6985,10 @@ packages: resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} engines: {node: '>=16'} + get-port@7.2.0: + resolution: {integrity: sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==} + engines: {node: '>=16'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -7660,6 +7771,9 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-query-parser@2.0.2: + resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -7808,6 +7922,9 @@ packages: mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + modern-ahocorasick@1.1.0: + resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} + module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -8467,6 +8584,9 @@ packages: resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} engines: {node: '>=0.12'} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.0: resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} engines: {node: '>= 0.6'} @@ -8585,6 +8705,16 @@ packages: react-dom: optional: true + react-router@8.0.1: + resolution: {integrity: sha512-5EL/fANovVUhRK50NLS8RYfX0BxrimoKsHWUPPy8v5UEl8i6vzF7e4POo3u+AhPItDwccUAJjMfIOmydxBJmQw==} + engines: {node: '>=22.22.0'} + peerDependencies: + react: '>=19.2.7' + react-dom: '>=19.2.7' + peerDependenciesMeta: + react-dom: + optional: true + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -8987,6 +9117,9 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + seroval-plugins@1.5.4: resolution: {integrity: sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==} engines: {node: '>=10'} @@ -10379,6 +10512,8 @@ snapshots: tslib: 2.8.1 optional: true + '@emotion/hash@0.9.2': {} + '@epic-web/cachified@5.6.1': {} '@epic-web/client-hints@1.3.8': {} @@ -12209,7 +12344,7 @@ snapshots: - tsx - yaml - '@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))': + '@react-router/dev@8.0.1(@react-router/serve@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))(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 @@ -12241,19 +12376,51 @@ snapshots: 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) optionalDependencies: + '@react-router/serve': 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) typescript: 5.9.3 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.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/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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: - '@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) + '@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': 8.0.1(react-router@8.0.1(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 + es-module-lexer: 2.1.0 + exit-hook: 5.1.0 + isbot: 5.1.44 + jsesc: 3.1.0 + lodash: 4.18.1 + p-map: 7.0.4 + pathe: 2.0.3 + picocolors: 1.1.1 + pkg-types: 2.3.1 + prettier: 3.9.1 + react-refresh: 0.18.0 + react-router: 8.0.1(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) optionalDependencies: + '@react-router/serve': 8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) typescript: 5.9.3 + wrangler: 4.105.0(@cloudflare/workers-types@4.20260628.1) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color '@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: @@ -12271,6 +12438,30 @@ snapshots: optionalDependencies: typescript: 5.9.3 + '@react-router/express@8.0.1(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': 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) + express: 5.2.1 + react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + typescript: 5.9.3 + optional: true + + '@react-router/express@8.0.1(express@5.2.1)(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': + dependencies: + '@react-router/node': 8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + express: 5.2.1 + react-router: 8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + optionalDependencies: + typescript: 5.9.3 + + '@react-router/fs-routes@8.0.1(@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)))(typescript@5.9.3)': + dependencies: + '@react-router/dev': 8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)) + minimatch: 10.2.5 + 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 @@ -12292,6 +12483,13 @@ snapshots: optionalDependencies: typescript: 5.9.3 + '@react-router/node@8.0.1(react-router@8.0.1(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: 8.0.1(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.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.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)) @@ -12313,6 +12511,37 @@ snapshots: - supports-color - typescript + '@react-router/serve@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: + '@react-router/express': 8.0.1(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': 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 + compression: 1.8.1 + express: 5.2.1 + get-port: 7.2.0 + morgan: 1.10.1 + 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 + optional: true + + '@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': + dependencies: + '@react-router/express': 8.0.1(express@5.2.1)(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@react-router/node': 8.0.1(react-router@8.0.1(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 + compression: 1.8.1 + express: 5.2.1 + get-port: 7.2.0 + morgan: 1.10.1 + react-router: 8.0.1(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.3': {} '@remix-run/react@2.17.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3)': @@ -13643,6 +13872,24 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true + '@vanilla-extract/css@1.21.0': + dependencies: + '@emotion/hash': 0.9.2 + '@vanilla-extract/private': 1.0.9 + css-what: 6.2.2 + csstype: 3.2.3 + dedent: 1.7.2 + deep-object-diff: 1.1.9 + deepmerge: 4.3.1 + lru-cache: 10.4.3 + media-query-parser: 2.0.2 + modern-ahocorasick: 1.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - babel-plugin-macros + + '@vanilla-extract/private@1.0.9': {} + '@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.29.7 @@ -14396,6 +14643,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@3.1.1: {} + cookie-signature@1.0.7: {} cookie-signature@1.2.2: {} @@ -14576,6 +14825,8 @@ snapshots: deep-is@0.1.4: {} + deep-object-diff@1.1.9: {} + deepmerge@4.3.1: {} defaults@1.0.4: @@ -15381,6 +15632,8 @@ snapshots: get-port@7.1.0: {} + get-port@7.2.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -16117,6 +16370,10 @@ snapshots: mdn-data@2.27.1: {} + media-query-parser@2.0.2: + dependencies: + '@babel/runtime': 7.29.7 + media-typer@0.3.0: {} media-typer@1.1.0: {} @@ -16219,6 +16476,8 @@ snapshots: mkdirp-classic@0.5.3: {} + modern-ahocorasick@1.1.0: {} + module-details-from-path@1.0.4: {} moo@0.5.3: {} @@ -16764,6 +17023,10 @@ snapshots: discontinuous-range: 1.0.0 ret: 0.1.15 + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + range-parser@1.2.0: {} range-parser@1.2.1: {} @@ -16907,6 +17170,13 @@ snapshots: optionalDependencies: react-dom: 19.2.7(react@19.2.7) + react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + cookie-es: 3.1.1 + react: 19.2.7 + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + react-style-singleton@2.2.3(@types/react@19.2.10)(react@19.2.7): dependencies: get-nonce: 1.0.1 @@ -17324,6 +17594,10 @@ snapshots: transitivePeerDependencies: - supports-color + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + seroval-plugins@1.5.4(seroval@1.5.4): dependencies: seroval: 1.5.4 diff --git a/src/index.ts b/src/index.ts index 49e38d77..13761bea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { existsSync, readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; import fsExtra from 'fs-extra'; import type { Config } from './react-router-config.js'; import type { RouteConfigEntry } from '@react-router/dev/routes'; @@ -89,6 +90,24 @@ export { loadReactRouterServerBuild } from './dev-generation.js'; export { resolveReactRouterServerBuild }; const MIN_PARALLEL_ENVIRONMENT_BUILD_SPARE_CORES = 4; +const requireFromApp = createRequire(resolve(process.cwd(), 'package.json')); + +const resolveAppPackagePath = (specifier: string): string | undefined => { + try { + return requireFromApp.resolve(specifier); + } catch { + return undefined; + } +}; + +const createReactRouterPackageAliases = () => { + const reactRouterPath = resolveAppPackagePath('react-router'); + const reactRouterDomPath = resolveAppPackagePath('react-router/dom'); + return { + ...(reactRouterPath ? { 'react-router$': reactRouterPath } : {}), + ...(reactRouterDomPath ? { 'react-router/dom$': reactRouterDomPath } : {}), + }; +}; type ReactRouterPresetResolvedConfig = Parameters< NonNullable< @@ -811,6 +830,7 @@ export const pluginReactRouter = ( routeCount >= 256 && (config.performance?.printFileSize === undefined || config.performance.printFileSize === true); + const reactRouterAliases = createReactRouterPackageAliases(); return mergeRsbuildConfig(config, { ...(shouldCompactFileSizeReport @@ -846,6 +866,10 @@ export const pluginReactRouter = ( }, tools: { rspack: { + resolve: + Object.keys(reactRouterAliases).length > 0 + ? { alias: reactRouterAliases } + : undefined, plugins: [vmodPlugin], }, }, diff --git a/src/plugin-utils.ts b/src/plugin-utils.ts index 51561985..6c5f9adc 100644 --- a/src/plugin-utils.ts +++ b/src/plugin-utils.ts @@ -35,41 +35,15 @@ export function findEntryFile(basePath: string): string { export function generateWithProps() { return ` - import { createElement as h } from "react"; - import { useActionData, useLoaderData, useMatches, useParams, useRouteError } from "react-router"; - - export function withComponentProps(Component) { - return function Wrapped() { - const props = { - params: useParams(), - loaderData: useLoaderData(), - actionData: useActionData(), - matches: useMatches(), - }; - return h(Component, props); - }; - } - - export function withHydrateFallbackProps(HydrateFallback) { - return function Wrapped() { - const props = { - params: useParams(), - }; - return h(HydrateFallback, props); - }; - } - - export function withErrorBoundaryProps(ErrorBoundary) { - return function Wrapped() { - const props = { - params: useParams(), - loaderData: useLoaderData(), - actionData: useActionData(), - error: useRouteError(), - }; - return h(ErrorBoundary, props); - }; - } + import { + UNSAFE_withComponentProps, + UNSAFE_withErrorBoundaryProps, + UNSAFE_withHydrateFallbackProps, + } from "react-router"; + + export const withComponentProps = UNSAFE_withComponentProps; + export const withHydrateFallbackProps = UNSAFE_withHydrateFallbackProps; + export const withErrorBoundaryProps = UNSAFE_withErrorBoundaryProps; `; } diff --git a/src/route-component-transform.ts b/src/route-component-transform.ts index c1dbea68..15205ac3 100644 --- a/src/route-component-transform.ts +++ b/src/route-component-transform.ts @@ -124,7 +124,7 @@ export const transformRoute = (ast: ParseResult | AnyNode): void => { function getHocUid(hocName: string) { const uid = getUid(hocName); - hocs.push([hocName, uid]); + hocs.push([`UNSAFE_${hocName}`, uid]); return identifier(uid); } @@ -301,7 +301,7 @@ export const transformRoute = (ast: ParseResult | AnyNode): void => { 0, importDeclaration( hocs.map(([name, local]) => ({ imported: name, local })), - 'virtual/react-router/with-props' + 'react-router' ) ); } diff --git a/tests/index.test.ts b/tests/index.test.ts index a1df1e72..c170d903 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,6 +1,7 @@ import { createStubRsbuild } from '@scripts/test-helper'; import { describe, expect, it, rstest } from '@rstest/core'; import * as fs from 'node:fs'; +import { createRequire } from 'node:module'; import { pluginReactRouter, shouldParallelizeEnvironmentBuilds } from '../src'; type ReactRouterTestGlobal = typeof globalThis & { @@ -33,6 +34,8 @@ const getLazyCompilationTest = ( return lazyCompilation.test; }; +const requireFromHere = createRequire(import.meta.url); + const captureEnv = (keys: string[]) => { const previousValues = new Map( keys.map(key => [key, process.env[key]] as const) @@ -64,6 +67,20 @@ describe('pluginReactRouter', () => { expect(config.dev.lazyCompilation).toBeUndefined(); }); + it('aliases React Router packages to the app install', async () => { + const rsbuild = await createStubRsbuild({ + rsbuildConfig: {}, + }); + + rsbuild.addPlugins([pluginReactRouter()]); + const config = await rsbuild.unwrapConfig(); + + expect(config.tools.rspack.resolve.alias).toMatchObject({ + 'react-router$': requireFromHere.resolve('react-router'), + 'react-router/dom$': requireFromHere.resolve('react-router/dom'), + }); + }); + it('adds the committed custom-server build entry only in development', async () => { const devRsbuild = await createStubRsbuild({ rsbuildConfig: {} }); devRsbuild.addPlugins([pluginReactRouter({ customServer: true })]); diff --git a/tests/plugin-utils.test.ts b/tests/plugin-utils.test.ts index 31e0b1aa..f2aaa541 100644 --- a/tests/plugin-utils.test.ts +++ b/tests/plugin-utils.test.ts @@ -89,10 +89,7 @@ describe('plugin-utils', () => { it('should generate withComponentProps HOC', () => { const result = generateWithProps(); expect(result).toContain('withComponentProps'); - expect(result).toContain('useLoaderData'); - expect(result).toContain('useActionData'); - expect(result).toContain('useParams'); - expect(result).toContain('useMatches'); + expect(result).toContain('UNSAFE_withComponentProps'); }); it('should generate withHydrateFallbackProps HOC', () => { @@ -103,7 +100,7 @@ describe('plugin-utils', () => { it('should generate withErrorBoundaryProps HOC', () => { const result = generateWithProps(); expect(result).toContain('withErrorBoundaryProps'); - expect(result).toContain('useRouteError'); + expect(result).toContain('UNSAFE_withErrorBoundaryProps'); }); it('should import from react-router', () => { @@ -221,8 +218,11 @@ describe('plugin-utils', () => { export { Route as default }; `); - expect(result.indexOf("'use client'")).toBeLessThan( - result.indexOf('virtual/react-router/with-props') + expect(result.search(/['"]use client['"]/)).toBeLessThan( + result.search(/from ['"]react-router['"]/) + ); + expect(result).toContain( + 'UNSAFE_withComponentProps as _withComponentProps' ); expect(result).toContain('withComponentProps'); expect(result).not.toContain('withdefaultProps'); From 53162189db698c98ace354e7de2ea4efebff8c7f Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 29 Jun 2026 01:06:12 +0000 Subject: [PATCH 05/15] chore: deslop React Router 8 PR --- .changeset/fresh-router-matrix.md | 9 - .changeset/react-router-8-default-template.md | 5 + .changeset/stable-prerender-concurrency.md | 6 - package.json | 1 - scripts/test-react-router-version-matrix.mjs | 216 ------------------ 5 files changed, 5 insertions(+), 232 deletions(-) delete mode 100644 .changeset/fresh-router-matrix.md create mode 100644 .changeset/react-router-8-default-template.md delete mode 100644 .changeset/stable-prerender-concurrency.md delete mode 100644 scripts/test-react-router-version-matrix.mjs diff --git a/.changeset/fresh-router-matrix.md b/.changeset/fresh-router-matrix.md deleted file mode 100644 index 5575a7a9..00000000 --- a/.changeset/fresh-router-matrix.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'rsbuild-plugin-react-router': minor ---- - -Add React Router 8 compatibility while preserving React Router 7 behavior. -Stable `subResourceIntegrity` and `prerender.concurrency` config fields are now -supported alongside React Router 7 aliases, prerender data requests default to -the correct React Router major-version format, and the package test suite now -packs the plugin and smoke-builds real React Router 7 and 8 apps. diff --git a/.changeset/react-router-8-default-template.md b/.changeset/react-router-8-default-template.md new file mode 100644 index 00000000..adef8ec2 --- /dev/null +++ b/.changeset/react-router-8-default-template.md @@ -0,0 +1,5 @@ +--- +'rsbuild-plugin-react-router': minor +--- + +Add React Router 8 compatibility while preserving React Router 7 behavior. The plugin now supports stable React Router 8 config fields, resolves prerender data requests for the installed React Router major version, and includes a copied React Router 8 default-template example with dev and production e2e coverage. diff --git a/.changeset/stable-prerender-concurrency.md b/.changeset/stable-prerender-concurrency.md deleted file mode 100644 index 83c49967..00000000 --- a/.changeset/stable-prerender-concurrency.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'rsbuild-plugin-react-router': patch ---- - -Support React Router's stable `prerender.concurrency` config while preserving -the existing `unstable_concurrency` fallback. diff --git a/package.json b/package.json index 797a04b1..1fe9a04c 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "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", - "test:react-router-matrix": "pnpm build && node scripts/test-react-router-version-matrix.mjs", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", "changeset": "changeset", diff --git a/scripts/test-react-router-version-matrix.mjs b/scripts/test-react-router-version-matrix.mjs deleted file mode 100644 index a82bc918..00000000 --- a/scripts/test-react-router-version-matrix.mjs +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env node -import { spawn } from 'node:child_process'; -import { - access, - mkdir, - mkdtemp, - readdir, - rm, - writeFile, -} from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; - -const rootDir = process.cwd(); -const versions = (process.env.RR_COMPAT_VERSIONS ?? '7.13.0,8.0.1') - .split(',') - .map(version => version.trim()) - .filter(Boolean); - -const run = (command, args, options = {}) => - new Promise((resolve, reject) => { - const child = spawn(command, args, { - cwd: options.cwd ?? rootDir, - env: { - ...process.env, - COREPACK_ENABLE_DOWNLOAD_PROMPT: '0', - ...options.env, - }, - stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit', - }); - - let stdout = ''; - let stderr = ''; - if (options.capture) { - child.stdout?.on('data', chunk => { - stdout += chunk; - }); - child.stderr?.on('data', chunk => { - stderr += chunk; - }); - } - - child.on('error', reject); - child.on('close', code => { - if (code === 0) { - resolve({ stdout, stderr }); - return; - } - reject( - new Error( - `${command} ${args.join(' ')} failed with exit code ${code}\n${stderr}` - ) - ); - }); - }); - -const writeFixture = async ({ appDir, version, tarball }) => { - await mkdir(path.join(appDir, 'app', 'routes'), { recursive: true }); - const writeAppFile = (file, contents) => - writeFile(path.join(appDir, file), contents); - - await writeAppFile( - 'package.json', - JSON.stringify( - { - private: true, - type: 'module', - scripts: { - build: 'rsbuild build', - }, - dependencies: { - '@react-router/dev': version, - '@react-router/node': version, - '@rsbuild/core': '2.1.0', - '@rsbuild/plugin-react': '2.1.0', - react: '^19.2.4', - 'react-dom': '^19.2.4', - 'react-router': version, - 'rsbuild-plugin-react-router': `file:${tarball}`, - }, - devDependencies: { - '@types/node': '^25.0.10', - '@types/react': '^19.2.10', - '@types/react-dom': '^19.2.3', - typescript: '^5.9.3', - }, - }, - null, - 2 - ) + '\n' - ); - - await writeAppFile( - 'rsbuild.config.ts', - `import { defineConfig } from '@rsbuild/core'; -import { pluginReact } from '@rsbuild/plugin-react'; -import { pluginReactRouter } from 'rsbuild-plugin-react-router'; - -export default defineConfig({ - plugins: [pluginReactRouter(), pluginReact()], -}); -` - ); - - await writeAppFile( - 'react-router.config.ts', - `export default { - ssr: true, - routeDiscovery: { mode: 'initial' }, - splitRouteModules: true, - subResourceIntegrity: true, - prerender: { paths: ['/'], concurrency: 1 }, -}; -` - ); - - await writeAppFile( - path.join('app', 'root.tsx'), - `import { - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, -} from 'react-router'; - -export default function Root() { - return ( - - - - - - - - - - - - ); -} -` - ); - - await writeAppFile( - path.join('app', 'routes.ts'), - `import { index } from '@react-router/dev/routes'; - -export default [index('routes/index.tsx')]; -` - ); - - await writeAppFile( - path.join('app', 'routes', 'index.tsx'), - `export function loader() { - return { message: 'React Router ${version}' }; -} - -export default function Index() { - return

React Router ${version} compatibility
; -} -` - ); -}; - -const assertFile = async file => { - try { - await access(file); - } catch { - throw new Error(`Expected ${file} to exist`); - } -}; - -const assertBrowserManifest = async clientDir => { - const jsDir = path.join(clientDir, 'static', 'js'); - const files = await readdir(jsDir, { recursive: true }); - const hasBrowserManifest = files.some( - file => - file.startsWith('manifest-') || - file === path.join('virtual', 'react-router', 'browser-manifest.js') - ); - if (!hasBrowserManifest) { - throw new Error(`Expected ${jsDir} to contain a React Router manifest`); - } -}; - -const main = async () => { - const tempRoot = await mkdtemp(path.join(tmpdir(), 'rr-version-matrix-')); - try { - const packResult = await run( - 'npm', - ['pack', '--json', '--pack-destination', tempRoot], - { capture: true } - ); - const [packInfo] = JSON.parse(packResult.stdout); - const tarball = path.join(tempRoot, packInfo.filename); - - for (const version of versions) { - const appDir = path.join(tempRoot, `rr-${version}`); - await mkdir(appDir, { recursive: true }); - await writeFixture({ appDir, version, tarball }); - await run('pnpm', ['install'], { cwd: appDir }); - await run('pnpm', ['build'], { cwd: appDir }); - - await assertFile(path.join(appDir, 'build', 'client', 'index.html')); - await assertFile(path.join(appDir, 'build', 'server', 'index.js')); - await assertBrowserManifest(path.join(appDir, 'build', 'client')); - - console.log(`React Router ${version} package smoke passed.`); - } - } finally { - await rm(tempRoot, { recursive: true, force: true }); - } -}; - -await main(); From 527d123becf9e04a1dfbe9a5d5a5411ec4f1e4b6 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 29 Jun 2026 03:26:37 +0000 Subject: [PATCH 06/15] test: port React Router framework suite --- package.json | 27 +- pnpm-lock.yaml | 1511 +++++- tests/react-router-framework/README.md | 36 + .../integration/CHANGELOG.md | 14 + .../integration/abort-signal-test.ts | 65 + .../integration/action-test.ts | 232 + .../integration/assets/toupload.txt | 1 + .../integration/assets/touploadtoobig.txt | 1 + .../integration/blocking-test.ts | 113 + .../integration/browser-entry-test.ts | 322 ++ .../integration/bug-report-test.ts | 127 + .../integration/catch-boundary-data-test.ts | 258 + .../integration/catch-boundary-test.ts | 379 ++ .../integration/cli-test.ts | 201 + .../integration/client-data-test.ts | 1772 +++++++ .../integration/custom-entry-server-test.ts | 60 + .../integration/deduped-route-modules-test.ts | 287 + .../integration/defer-loader-test.ts | 110 + .../integration/defer-test.ts | 699 +++ .../integration/error-boundary-test.ts | 1020 ++++ .../integration/error-boundary-v2-test.ts | 257 + .../integration/error-data-request-test.ts | 168 + .../integration/error-sanitization-test.ts | 707 +++ .../integration/fetch-globals-test.ts | 42 + .../integration/fetcher-layout-test.ts | 268 + .../integration/fetcher-test.ts | 593 +++ .../integration/fog-of-war-test.ts | 1798 +++++++ .../integration/form-data-test.ts | 58 + .../integration/form-test.ts | 1142 ++++ .../integration/fs-routes-test.ts | 454 ++ .../integration/headers-test.ts | 472 ++ .../integration/helpers/cleanup.mjs | 27 + .../integration/helpers/create-fixture.ts | 592 +++ .../integration/helpers/express.ts | 52 + .../integration/helpers/fixtures.ts | 146 + .../integration/helpers/playwright-fixture.ts | 364 ++ .../integration/helpers/rsbuild-adapter.ts | 197 + .../helpers/rsc-vite-framework/.gitignore | 2 + .../helpers/rsc-vite-framework/app/root.tsx | 18 + .../helpers/rsc-vite-framework/app/routes.ts | 4 + .../rsc-vite-framework/app/routes/_index.tsx | 16 + .../helpers/rsc-vite-framework/package.json | 40 + .../rsc-vite-framework/public/favicon.ico | Bin 0 -> 15086 bytes .../helpers/rsc-vite-framework/start.js | 18 + .../helpers/rsc-vite-framework/tsconfig.json | 17 + .../helpers/rsc-vite-framework/vite.config.ts | 13 + .../integration/helpers/rsc-vite/.gitignore | 2 + .../integration/helpers/rsc-vite/package.json | 32 + .../helpers/rsc-vite/public/favicon.ico | Bin 0 -> 15086 bytes .../integration/helpers/rsc-vite/server.js | 26 + .../helpers/rsc-vite/src/config/basename.ts | 2 + .../rsc-vite/src/config/get-context.ts | 2 + .../rsc-vite/src/config/request-context.ts | 2 + .../helpers/rsc-vite/src/entry.browser.tsx | 45 + .../helpers/rsc-vite/src/entry.rsc.tsx | 40 + .../helpers/rsc-vite/src/entry.ssr.tsx | 32 + .../helpers/rsc-vite/src/routes.ts | 16 + .../helpers/rsc-vite/src/routes/home.tsx | 3 + .../helpers/rsc-vite/src/routes/root.tsx | 22 + .../helpers/rsc-vite/tsconfig.json | 11 + .../helpers/rsc-vite/vite.config.ts | 16 + .../integration/helpers/stream.ts | 40 + .../integration/helpers/templates.ts | 23 + .../helpers/vite-7-template/.gitignore | 6 + .../helpers/vite-7-template/app/root.tsx | 19 + .../helpers/vite-7-template/app/routes.ts | 4 + .../vite-7-template/app/routes/_index.tsx | 16 + .../helpers/vite-7-template/env.d.ts | 2 + .../helpers/vite-7-template/package.json | 41 + .../vite-7-template/public/favicon.ico | Bin 0 -> 15086 bytes .../helpers/vite-7-template/tsconfig.json | 16 + .../helpers/vite-7-template/vite.config.ts | 7 + .../helpers/vite-8-template/.gitignore | 6 + .../helpers/vite-8-template/app/root.tsx | 19 + .../helpers/vite-8-template/app/routes.ts | 4 + .../vite-8-template/app/routes/_index.tsx | 16 + .../helpers/vite-8-template/env.d.ts | 2 + .../helpers/vite-8-template/package.json | 40 + .../vite-8-template/public/favicon.ico | Bin 0 -> 15086 bytes .../helpers/vite-8-template/tsconfig.json | 16 + .../helpers/vite-8-template/vite.config.ts | 7 + .../.gitignore | 11 + .../app/cloudflare.ts | 10 + .../app/entry.server.tsx | 43 + .../app/root.tsx | 19 + .../app/routes.ts | 4 + .../app/routes/_index.tsx | 16 + .../package.json | 36 + .../public/favicon.ico | Bin 0 -> 15086 bytes .../react-router.config.ts | 3 + .../tsconfig.cloudflare.json | 27 + .../tsconfig.json | 12 + .../tsconfig.node.json | 13 + .../vite.config.ts | 35 + .../workers/app.ts | 16 + .../wrangler.toml | 8 + .../integration/helpers/vite.ts | 549 ++ .../integration/hook-useSubmit-test.ts | 135 + .../integration/http-test.ts | 87 + .../integration/layout-route-test.ts | 66 + .../integration/link-test.ts | 667 +++ .../integration/loader-test.ts | 128 + .../integration/matches-test.ts | 197 + .../integration/mdx-test.ts | 114 + .../integration/middleware-test.ts | 2947 +++++++++++ .../integration/multiple-cookies-test.ts | 78 + .../integration/navigation-state-test.ts | 467 ++ .../integration/package.json | 52 + .../integration/passthrough-requests-test.ts | 159 + .../integration/playwright.config.ts | 40 + .../integration/prefetch-test.ts | 725 +++ .../integration/react-router-serve-test.ts | 84 + .../integration/redirects-test.ts | 308 ++ .../integration/rendering-test.ts | 73 + .../integration/request-test.ts | 177 + .../integration/resource-routes-test.ts | 397 ++ .../integration/revalidate-test.ts | 367 ++ .../integration/root-route-test.ts | 414 ++ .../integration/route-collisions-test.ts | 144 + .../integration/route-config-test.ts | 275 + .../integration/rsc/rsc-nojs-test.ts | 291 + .../integration/rsc/rsc-prerender-test.ts | 185 + .../integration/rsc/rsc-test.ts | 2762 ++++++++++ .../integration/rsc/utils.ts | 74 + .../integration/scroll-test.ts | 124 + .../integration/server-entry-test.ts | 66 + .../session-storage-denied-test.ts | 115 + .../set-cookie-revalidation-test.ts | 136 + .../integration/single-fetch-test.ts | 4662 +++++++++++++++++ .../integration/splat-routes-test.ts | 129 + .../integration/split-route-modules-test.ts | 559 ++ .../integration/sri-test.ts | 87 + .../integration/transition-test.ts | 338 ++ .../integration/tsconfig.json | 13 + .../integration/typegen-test.ts | 890 ++++ .../integration/use-route-test.ts | 138 + .../integration/vite-absolute-base-test.ts | 40 + .../integration/vite-basename-test.ts | 741 +++ .../integration/vite-build-test.ts | 384 ++ .../integration/vite-css-lazy-loading-test.ts | 280 + .../integration/vite-css-test.ts | 585 +++ .../integration/vite-dev-custom-entry-test.ts | 103 + .../integration/vite-dev-test.ts | 557 ++ .../integration/vite-dot-client-test.ts | 47 + .../integration/vite-dot-server-test.ts | 270 + .../integration/vite-dotenv-test.ts | 115 + .../vite-extra-server-environment-test.ts | 118 + .../integration/vite-hmr-hdr-rsc-test.ts | 392 ++ .../integration/vite-hmr-hdr-test.ts | 362 ++ .../integration/vite-loader-context-test.ts | 82 + .../integration/vite-manifests-test.ts | 143 + .../integration/vite-node-env-test.ts | 79 + .../vite-plugin-cloudflare-test.ts | 133 + .../vite-plugin-order-validation-test.ts | 84 + .../integration/vite-prerender-test.ts | 3066 +++++++++++ .../integration/vite-presets-test.ts | 280 + .../integration/vite-preview-test.ts | 381 ++ .../integration/vite-route-added-test.ts | 76 + ...e-route-exports-modified-offscreen-test.ts | 107 + .../integration/vite-server-bundles-test.ts | 459 ++ .../integration/vite-server-fs-allow-test.ts | 51 + .../integration/vite-spa-mode-test.ts | 1285 +++++ .../vite-unused-route-exports-test.ts | 37 + .../__tests__/fixtures/basic/.gitignore | 6 + .../__tests__/fixtures/basic/app/root.tsx | 19 + .../__tests__/fixtures/basic/app/routes.ts | 5 + .../fixtures/basic/app/routes/_index.tsx | 16 + .../__tests__/fixtures/basic/package.json | 28 + .../fixtures/basic/public/favicon.ico | Bin 0 -> 15086 bytes .../__tests__/fixtures/basic/tsconfig.json | 16 + .../__tests__/route-config-test.ts | 565 ++ .../rsc-virtual-route-modules-test.ts | 632 +++ .../__tests__/setupAfterEnv.ts | 3 + .../react-router-dev/__tests__/styles-test.ts | 60 + .../__tests__/utils/captureError.ts | 14 + .../react-router-dev/__tests__/utils/cli.ts | 105 + .../react-router-dev/__tests__/utils/eol.ts | 2 + .../react-router-dev/__tests__/utils/git.ts | 18 + .../__tests__/utils/withApp.ts | 46 + .../__tests__/watcher-ignored-test.ts | 70 + 180 files changed, 46528 insertions(+), 41 deletions(-) create mode 100644 tests/react-router-framework/README.md create mode 100644 tests/react-router-framework/integration/CHANGELOG.md create mode 100644 tests/react-router-framework/integration/abort-signal-test.ts create mode 100644 tests/react-router-framework/integration/action-test.ts create mode 100644 tests/react-router-framework/integration/assets/toupload.txt create mode 100644 tests/react-router-framework/integration/assets/touploadtoobig.txt create mode 100644 tests/react-router-framework/integration/blocking-test.ts create mode 100644 tests/react-router-framework/integration/browser-entry-test.ts create mode 100644 tests/react-router-framework/integration/bug-report-test.ts create mode 100644 tests/react-router-framework/integration/catch-boundary-data-test.ts create mode 100644 tests/react-router-framework/integration/catch-boundary-test.ts create mode 100644 tests/react-router-framework/integration/cli-test.ts create mode 100644 tests/react-router-framework/integration/client-data-test.ts create mode 100644 tests/react-router-framework/integration/custom-entry-server-test.ts create mode 100644 tests/react-router-framework/integration/deduped-route-modules-test.ts create mode 100644 tests/react-router-framework/integration/defer-loader-test.ts create mode 100644 tests/react-router-framework/integration/defer-test.ts create mode 100644 tests/react-router-framework/integration/error-boundary-test.ts create mode 100644 tests/react-router-framework/integration/error-boundary-v2-test.ts create mode 100644 tests/react-router-framework/integration/error-data-request-test.ts create mode 100644 tests/react-router-framework/integration/error-sanitization-test.ts create mode 100644 tests/react-router-framework/integration/fetch-globals-test.ts create mode 100644 tests/react-router-framework/integration/fetcher-layout-test.ts create mode 100644 tests/react-router-framework/integration/fetcher-test.ts create mode 100644 tests/react-router-framework/integration/fog-of-war-test.ts create mode 100644 tests/react-router-framework/integration/form-data-test.ts create mode 100644 tests/react-router-framework/integration/form-test.ts create mode 100644 tests/react-router-framework/integration/fs-routes-test.ts create mode 100644 tests/react-router-framework/integration/headers-test.ts create mode 100644 tests/react-router-framework/integration/helpers/cleanup.mjs create mode 100644 tests/react-router-framework/integration/helpers/create-fixture.ts create mode 100644 tests/react-router-framework/integration/helpers/express.ts create mode 100644 tests/react-router-framework/integration/helpers/fixtures.ts create mode 100644 tests/react-router-framework/integration/helpers/playwright-fixture.ts create mode 100644 tests/react-router-framework/integration/helpers/rsbuild-adapter.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/.gitignore create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/app/root.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/app/routes.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/app/routes/_index.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/package.json create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/public/favicon.ico create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/start.js create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/tsconfig.json create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite-framework/vite.config.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/.gitignore create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/package.json create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/public/favicon.ico create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/server.js create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/config/basename.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/config/get-context.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/config/request-context.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/entry.browser.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/entry.rsc.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/entry.ssr.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/routes.ts create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/routes/home.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/src/routes/root.tsx create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/tsconfig.json create mode 100644 tests/react-router-framework/integration/helpers/rsc-vite/vite.config.ts create mode 100644 tests/react-router-framework/integration/helpers/stream.ts create mode 100644 tests/react-router-framework/integration/helpers/templates.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/.gitignore create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/app/root.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/app/routes.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/app/routes/_index.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/env.d.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/package.json create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/public/favicon.ico create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/tsconfig.json create mode 100644 tests/react-router-framework/integration/helpers/vite-7-template/vite.config.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/.gitignore create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/app/root.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/app/routes.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/app/routes/_index.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/env.d.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/package.json create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/public/favicon.ico create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/tsconfig.json create mode 100644 tests/react-router-framework/integration/helpers/vite-8-template/vite.config.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/.gitignore create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/app/cloudflare.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/app/entry.server.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/app/root.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/app/routes.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/app/routes/_index.tsx create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/package.json create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/public/favicon.ico create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/react-router.config.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/tsconfig.cloudflare.json create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/tsconfig.json create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/tsconfig.node.json create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/vite.config.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/workers/app.ts create mode 100644 tests/react-router-framework/integration/helpers/vite-plugin-cloudflare-template/wrangler.toml create mode 100644 tests/react-router-framework/integration/helpers/vite.ts create mode 100644 tests/react-router-framework/integration/hook-useSubmit-test.ts create mode 100644 tests/react-router-framework/integration/http-test.ts create mode 100644 tests/react-router-framework/integration/layout-route-test.ts create mode 100644 tests/react-router-framework/integration/link-test.ts create mode 100644 tests/react-router-framework/integration/loader-test.ts create mode 100644 tests/react-router-framework/integration/matches-test.ts create mode 100644 tests/react-router-framework/integration/mdx-test.ts create mode 100644 tests/react-router-framework/integration/middleware-test.ts create mode 100644 tests/react-router-framework/integration/multiple-cookies-test.ts create mode 100644 tests/react-router-framework/integration/navigation-state-test.ts create mode 100644 tests/react-router-framework/integration/package.json create mode 100644 tests/react-router-framework/integration/passthrough-requests-test.ts create mode 100644 tests/react-router-framework/integration/playwright.config.ts create mode 100644 tests/react-router-framework/integration/prefetch-test.ts create mode 100644 tests/react-router-framework/integration/react-router-serve-test.ts create mode 100644 tests/react-router-framework/integration/redirects-test.ts create mode 100644 tests/react-router-framework/integration/rendering-test.ts create mode 100644 tests/react-router-framework/integration/request-test.ts create mode 100644 tests/react-router-framework/integration/resource-routes-test.ts create mode 100644 tests/react-router-framework/integration/revalidate-test.ts create mode 100644 tests/react-router-framework/integration/root-route-test.ts create mode 100644 tests/react-router-framework/integration/route-collisions-test.ts create mode 100644 tests/react-router-framework/integration/route-config-test.ts create mode 100644 tests/react-router-framework/integration/rsc/rsc-nojs-test.ts create mode 100644 tests/react-router-framework/integration/rsc/rsc-prerender-test.ts create mode 100644 tests/react-router-framework/integration/rsc/rsc-test.ts create mode 100644 tests/react-router-framework/integration/rsc/utils.ts create mode 100644 tests/react-router-framework/integration/scroll-test.ts create mode 100644 tests/react-router-framework/integration/server-entry-test.ts create mode 100644 tests/react-router-framework/integration/session-storage-denied-test.ts create mode 100644 tests/react-router-framework/integration/set-cookie-revalidation-test.ts create mode 100644 tests/react-router-framework/integration/single-fetch-test.ts create mode 100644 tests/react-router-framework/integration/splat-routes-test.ts create mode 100644 tests/react-router-framework/integration/split-route-modules-test.ts create mode 100644 tests/react-router-framework/integration/sri-test.ts create mode 100644 tests/react-router-framework/integration/transition-test.ts create mode 100644 tests/react-router-framework/integration/tsconfig.json create mode 100644 tests/react-router-framework/integration/typegen-test.ts create mode 100644 tests/react-router-framework/integration/use-route-test.ts create mode 100644 tests/react-router-framework/integration/vite-absolute-base-test.ts create mode 100644 tests/react-router-framework/integration/vite-basename-test.ts create mode 100644 tests/react-router-framework/integration/vite-build-test.ts create mode 100644 tests/react-router-framework/integration/vite-css-lazy-loading-test.ts create mode 100644 tests/react-router-framework/integration/vite-css-test.ts create mode 100644 tests/react-router-framework/integration/vite-dev-custom-entry-test.ts create mode 100644 tests/react-router-framework/integration/vite-dev-test.ts create mode 100644 tests/react-router-framework/integration/vite-dot-client-test.ts create mode 100644 tests/react-router-framework/integration/vite-dot-server-test.ts create mode 100644 tests/react-router-framework/integration/vite-dotenv-test.ts create mode 100644 tests/react-router-framework/integration/vite-extra-server-environment-test.ts create mode 100644 tests/react-router-framework/integration/vite-hmr-hdr-rsc-test.ts create mode 100644 tests/react-router-framework/integration/vite-hmr-hdr-test.ts create mode 100644 tests/react-router-framework/integration/vite-loader-context-test.ts create mode 100644 tests/react-router-framework/integration/vite-manifests-test.ts create mode 100644 tests/react-router-framework/integration/vite-node-env-test.ts create mode 100644 tests/react-router-framework/integration/vite-plugin-cloudflare-test.ts create mode 100644 tests/react-router-framework/integration/vite-plugin-order-validation-test.ts create mode 100644 tests/react-router-framework/integration/vite-prerender-test.ts create mode 100644 tests/react-router-framework/integration/vite-presets-test.ts create mode 100644 tests/react-router-framework/integration/vite-preview-test.ts create mode 100644 tests/react-router-framework/integration/vite-route-added-test.ts create mode 100644 tests/react-router-framework/integration/vite-route-exports-modified-offscreen-test.ts create mode 100644 tests/react-router-framework/integration/vite-server-bundles-test.ts create mode 100644 tests/react-router-framework/integration/vite-server-fs-allow-test.ts create mode 100644 tests/react-router-framework/integration/vite-spa-mode-test.ts create mode 100644 tests/react-router-framework/integration/vite-unused-route-exports-test.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/.gitignore create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/app/root.tsx create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/app/routes.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/app/routes/_index.tsx create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/package.json create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/public/favicon.ico create mode 100644 tests/react-router-framework/react-router-dev/__tests__/fixtures/basic/tsconfig.json create mode 100644 tests/react-router-framework/react-router-dev/__tests__/route-config-test.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/rsc-virtual-route-modules-test.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/setupAfterEnv.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/styles-test.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/utils/captureError.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/utils/cli.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/utils/eol.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/utils/git.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/utils/withApp.ts create mode 100644 tests/react-router-framework/react-router-dev/__tests__/watcher-ignored-test.ts diff --git a/package.json b/package.json index 1fe9a04c..801a8352 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,8 @@ "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", + "test:react-router-framework": "pnpm build && playwright test --config tests/react-router-framework/integration/playwright.config.ts", + "test:react-router-framework:smoke": "pnpm build && playwright test --config tests/react-router-framework/integration/playwright.config.ts use-route-test.ts loader-test.ts", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", "changeset": "changeset", @@ -74,7 +76,7 @@ }, "dependencies": { "@react-router/node": "^7.0.0 || ^8.0.0", - "@remix-run/node-fetch-server": "^0.13.0", + "@remix-run/node-fetch-server": "^0.13.3", "@rspack/plugin-react-refresh": "^2.0.2", "execa": "^9.6.1", "fs-extra": "11.3.3", @@ -89,9 +91,15 @@ }, "devDependencies": { "@changesets/cli": "^2.29.8", + "@playwright/test": "^1.61.1", "@react-router/dev": "^8.0.1", + "@react-router/express": "^8.0.1", + "@react-router/fs-routes": "^8.0.1", + "@react-router/remix-routes-option-adapter": "^8.0.1", + "@react-router/serve": "^8.0.1", "@rsbuild/config": "workspace:*", "@rsbuild/core": "2.1.0", + "@rsbuild/plugin-mdx": "^1.1.3", "@rsbuild/plugin-react": "2.1.0", "@rslib/core": "^0.22.1", "@rspack/core": "2.1.0", @@ -103,16 +111,29 @@ "@types/node": "^25.0.10", "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", + "@vanilla-extract/css": "^1.20.1", + "cheerio": "^1.2.0", + "cross-spawn": "^7.0.6", + "dedent": "^1.7.2", "es-module-lexer": "1.7.0", + "express": "^4.22.2", + "fast-glob": "^3.3.3", + "get-port": "7.1.0", "kill-port": "^2.0.1", "pkg-pr-new": "^0.0.75", - "playwright": "1.58.0", + "playwright": "^1.61.1", "prettier": "3.8.1", "react": "^19.2.4", "react-dom": "^19.2.4", "react-router": "^7.13.0", "react-router-dom": "^7.13.0", - "typescript": "^5.9.3" + "semver": "^7.8.5", + "shelljs": "^0.10.0", + "strip-ansi": "^7.2.0", + "strip-indent": "^4.1.1", + "type-fest": "^5.7.0", + "typescript": "^5.9.3", + "wait-on": "^9.0.10" }, "peerDependencies": { "@react-router/dev": "^7.0.0 || ^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5c63cb3..5a396a46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ importers: specifier: ^7.0.0 || ^8.0.0 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 + specifier: ^0.13.3 version: 0.13.3 '@rspack/plugin-react-refresh': specifier: ^2.0.2 @@ -54,21 +54,39 @@ importers: '@changesets/cli': specifier: ^2.29.8 version: 2.31.0(@types/node@25.0.10) + '@playwright/test': + specifier: ^1.61.1 + version: 1.61.1 '@react-router/dev': specifier: ^8.0.1 - version: 8.0.1(@react-router/serve@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))(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)) + version: 8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)) + '@react-router/express': + specifier: ^8.0.1 + version: 8.0.1(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/fs-routes': + specifier: ^8.0.1 + version: 8.0.1(@react-router/dev@8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)))(typescript@5.9.3) + '@react-router/remix-routes-option-adapter': + specifier: ^8.0.1 + version: 8.0.1(@react-router/dev@8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)))(typescript@5.9.3) + '@react-router/serve': + specifier: ^8.0.1 + 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) '@rsbuild/config': specifier: workspace:* version: link:config '@rsbuild/core': 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-mdx': + specifier: ^1.1.3 + version: 1.1.3(@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))(webpack@5.108.1(lightningcss@1.32.0)) '@rsbuild/plugin-react': 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) + version: 0.22.1(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(core-js@3.47.0)(typescript@5.9.3) '@rspack/core': 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) @@ -96,9 +114,30 @@ importers: '@types/react-dom': specifier: ^19.2.3 version: 19.2.3(@types/react@19.2.10) + '@vanilla-extract/css': + specifier: ^1.20.1 + version: 1.21.0 + cheerio: + specifier: ^1.2.0 + version: 1.2.0 + cross-spawn: + specifier: ^7.0.6 + version: 7.0.6 + dedent: + specifier: ^1.7.2 + version: 1.7.2 es-module-lexer: specifier: 1.7.0 version: 1.7.0 + express: + specifier: ^4.22.2 + version: 4.22.2 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 + get-port: + specifier: 7.1.0 + version: 7.1.0 kill-port: specifier: ^2.0.1 version: 2.0.1 @@ -106,8 +145,8 @@ importers: specifier: ^0.0.75 version: 0.0.75 playwright: - specifier: 1.58.0 - version: 1.58.0 + specifier: ^1.61.1 + version: 1.61.1 prettier: specifier: 3.8.1 version: 3.8.1 @@ -123,9 +162,27 @@ importers: react-router-dom: specifier: ^7.13.0 version: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + semver: + specifier: ^7.8.5 + version: 7.8.5 + shelljs: + specifier: ^0.10.0 + version: 0.10.0 + strip-ansi: + specifier: ^7.2.0 + version: 7.2.0 + strip-indent: + specifier: ^4.1.1 + version: 4.1.1 + type-fest: + specifier: ^5.7.0 + version: 5.7.0 typescript: specifier: ^5.9.3 version: 5.9.3 + wait-on: + specifier: ^9.0.10 + version: 9.0.10 config: devDependencies: @@ -146,7 +203,7 @@ importers: dependencies: '@react-router/express': specifier: ^7.13.0 - 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) + 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.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) @@ -171,7 +228,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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) @@ -229,7 +286,7 @@ importers: 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.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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) @@ -290,7 +347,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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) @@ -354,7 +411,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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) @@ -480,7 +537,7 @@ importers: 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.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) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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) @@ -597,7 +654,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)(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) + 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.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:../.. @@ -643,7 +700,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.2.0)) @@ -862,7 +919,7 @@ importers: 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.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) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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) @@ -979,7 +1036,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)(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) + 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.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:../../.. @@ -1022,7 +1079,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.2.0)) @@ -1226,7 +1283,7 @@ importers: 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.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) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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) @@ -1343,7 +1400,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)(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) + 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.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:../../.. @@ -1386,7 +1443,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.2.0)) @@ -1534,7 +1591,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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) @@ -1588,7 +1645,7 @@ importers: dependencies: '@react-router/fs-routes': specifier: ^8.0.1 - version: 8.0.1(@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)))(typescript@5.9.3) + version: 8.0.1(@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@vitejs/plugin-rsc@0.5.27(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)))(react-router@8.0.1(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)))(typescript@5.9.3) '@react-router/node': specifier: ^8.0.1 version: 8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) @@ -1616,7 +1673,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^8.0.1 - version: 8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)) + version: 8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@vitejs/plugin-rsc@0.5.27(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)))(react-router@8.0.1(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/core': 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) @@ -1677,7 +1734,7 @@ importers: version: 1.58.0 '@react-router/dev': specifier: ^7.13.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)) + 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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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) @@ -2548,6 +2605,26 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.7': + resolution: {integrity: sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -2781,6 +2858,17 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@mdx-js/loader@3.1.1': + resolution: {integrity: sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ==} + peerDependencies: + webpack: '>=5' + peerDependenciesMeta: + webpack: + optional: true + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@mjackson/form-data-parser@0.9.1': resolution: {integrity: sha512-GQqet5qTAm8LfUOsMdfdInqnOBpuDO5GK/y7tBgpXs+DQhJY9Rf1fxMuFXiXczoNMRu8UIG5/RFSBDeaF1bbrw==} @@ -3294,6 +3382,11 @@ packages: engines: {node: '>=18'} hasBin: true + '@playwright/test@1.61.1': + resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} + engines: {node: '>=18'} + hasBin: true + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -4165,6 +4258,16 @@ packages: typescript: optional: true + '@react-router/remix-routes-option-adapter@8.0.1': + resolution: {integrity: sha512-t8xFxE/LDOAxJTDdyVHG34i5N3HnsPjQ/ZsrOQEnIcJwS7GZSLoHnNa045+Be/12Ma4Kwxj23fq3skxhKoR/fQ==} + engines: {node: '>=22.22.0'} + peerDependencies: + '@react-router/dev': ^8.0.1 + typescript: ^5.1.0 || ^6.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@react-router/serve@7.18.0': resolution: {integrity: sha512-IrF0cLcJNGBBavnRBm3HxaEGwRrLrLF8E4EzQFuCpkgP1sRli1x2xEOOTJl4zBgUbyIn0ey4TAD6ytg45MAUBQ==} engines: {node: '>=20.0.0'} @@ -4222,6 +4325,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/rollup-android-arm-eabi@4.62.2': resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} cpu: [arm] @@ -4388,6 +4494,14 @@ packages: '@rsbuild/core': optional: true + '@rsbuild/plugin-mdx@1.1.3': + resolution: {integrity: sha512-2o/Pg+VExsyfr6uzzXj+cBxzjiFbQ2yIUxzM5kQnrPC5J0D8ZhWyXuWN5rTtsolAS23QdfsycZ0iAtlRuZVwCw==} + peerDependencies: + '@rsbuild/core': ^1.0.0 || ^2.0.0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@rsbuild/plugin-react@2.1.0': resolution: {integrity: sha512-RQTIAWB/CwPjoWt9iAl+8HixeQVgZ7kEIBrWPCixfITyHdiD84h0YpUTpEUuz6kGHw1KXT9mHZ3Rwy6WG7aRDA==} peerDependencies: @@ -4919,6 +5033,9 @@ packages: '@speed-highlight/core@1.2.17': resolution: {integrity: sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -5145,12 +5262,18 @@ packages: '@types/d3-hierarchy@1.1.11': resolution: {integrity: sha512-lnQiU7jV+Gyk9oQYk0GGYccuexmQPTp08E0+4BidgFdiJivjEvf+esPSdZqCZ2C7UwTWejWpqetVaU8A+eX3FA==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -5170,6 +5293,9 @@ packages: resolution: {integrity: sha512-00UxlRaIUvYm4R4W9WYkN8/J+kV8fmOQ7okeH6YFtGWFMt3odD45tpG5yA5wnL7HE6lLgjaTW5n14ju2hl2NNA==} deprecated: This is a stub types definition. glob provides its own type definitions, so you do not need this installed. + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -5182,9 +5308,18 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + '@types/morgan@1.9.10': resolution: {integrity: sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/mysql@2.15.27': resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} @@ -5244,6 +5379,12 @@ packages: '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -5306,6 +5447,9 @@ packages: resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.2': + resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] @@ -5428,6 +5572,17 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitejs/plugin-rsc@0.5.27': + resolution: {integrity: sha512-s1fd5DUkPXk86DDHPM/kP93WrvI0MoA8klxdDZmD1fMSaA9xujfgunsm8ZoUH0FemR+63vNalFsIDR0AJH4ktg==} + peerDependencies: + react: '*' + react-dom: '*' + react-server-dom-webpack: '*' + vite: '*' + peerDependenciesMeta: + react-server-dom-webpack: + optional: true + '@vitest/eslint-plugin@1.6.20': resolution: {integrity: sha512-xRwWHFG0Utp6hXtbGiWk4VdKXCGdExD8kbWrrmFEiG5dk8anOJ+vbWbeOa8EbkocKQRTsx7JAWETccZiBgFp/Q==} engines: {node: '>=18'} @@ -5840,10 +5995,17 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.23: resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} engines: {node: ^10 || ^12 || >=14} @@ -5855,9 +6017,15 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} + axios@1.18.1: + resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==} + babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -6018,6 +6186,9 @@ packages: caniuse-lite@1.0.30001799: resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chain-function@1.0.1: resolution: {integrity: sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==} @@ -6041,9 +6212,28 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@2.2.0: resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.2.0: + resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} + engines: {node: '>=20.18.1'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6114,6 +6304,9 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -6130,6 +6323,13 @@ packages: colorjs.io@0.5.2: resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -6369,6 +6569,9 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -6410,6 +6613,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -6433,6 +6640,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dijkstrajs@1.0.3: resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} @@ -6504,6 +6714,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding-sniffer@0.2.1: + resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -6541,6 +6754,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + entities@8.0.0: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} @@ -6608,6 +6825,12 @@ packages: es-toolkit@1.49.0: resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -6744,6 +6967,27 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + 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'} @@ -6801,6 +7045,9 @@ packages: exsolve@1.1.0: resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -6882,6 +7129,15 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -6890,6 +7146,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -7109,6 +7369,15 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -7141,6 +7410,9 @@ packages: htmlparser2@10.0.0: resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} @@ -7232,6 +7504,9 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + input-otp@1.4.2: resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} peerDependencies: @@ -7254,6 +7529,12 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -7293,6 +7574,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -7322,6 +7606,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} @@ -7485,9 +7772,16 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + joi@18.2.3: + resolution: {integrity: sha512-N5A3KTWQpPWT4ExxxPlUx7WmykGXRzhNidWhV41d6Abu9YfI2NyWCJuxdPnslJCPWtbRpSVOWSnSS6GakLM/Rg==} + engines: {node: '>= 20'} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.15.0: resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} hasBin: true @@ -7718,6 +8012,9 @@ packages: long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -7759,6 +8056,10 @@ packages: resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==} engines: {node: '>=18'} + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + marked@15.0.12: resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} engines: {node: '>= 18'} @@ -7768,6 +8069,33 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -7804,6 +8132,90 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -8181,6 +8593,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -8201,6 +8616,15 @@ packages: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} @@ -8545,6 +8969,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -8552,6 +8979,10 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -8759,6 +9190,20 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -8781,6 +9226,18 @@ packages: resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} engines: {node: '>=0.10.0'} + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + remix-auth-github@3.0.2: resolution: {integrity: sha512-3XxykdwMrcPSyMsdGtBDl3DBc19gJM3t7q/1uzfz3g/SJRsxEytjGiQ17ztKykebCGM454Z0lVJMvSb+LF/yHA==} engines: {node: ^18.0.0 || ^20.0.0 || >=20.0.0} @@ -9201,6 +9658,10 @@ packages: resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} engines: {node: '>= 0.4'} + shelljs@0.10.0: + resolution: {integrity: sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==} + engines: {node: '>=18'} + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -9275,6 +9736,9 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} @@ -9302,6 +9766,11 @@ packages: resolution: {integrity: sha512-kTYRg5FIcvsDtYUG2Qn9pYT6xKwiLJN5TTIvc5Mur6hIg4pSfdpHu8Yyu5bqESLHnVM3mXzD446cb2+uEaKZXg==} hasBin: true + srvx@0.11.17: + resolution: {integrity: sha512-43yM4luKfCJamyCMhrUeHUPOrf8TdZe7kN8s5zayZCH5OeprYqi49Aso5ZvHXR4aB+DHaRNO/diNFgZSMNG8Xw==} + engines: {node: '>=20.16.0'} + hasBin: true + stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -9361,6 +9830,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -9389,6 +9861,10 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -9397,6 +9873,15 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -9524,6 +10009,12 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -9555,6 +10046,9 @@ packages: turbo-stream@2.4.1: resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} + turbo-stream@3.2.0: + resolution: {integrity: sha512-EK+bZ9UVrVh7JLslVFOV0GEMsociOqVOvEMTAd4ixMyffN5YNIEdLZWXUx5PJqDbTxSIBWw04HS9gCY4frYQDQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -9633,6 +10127,27 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -9720,6 +10235,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-env-only@3.0.3: resolution: {integrity: sha512-iAb7cTXRrvFShaF1n+G8f6Yqq7sRJcxipNYNQQu0DN5N9P55vJMmLG5lNU5moYGpd+ZH1WhBHdkWi5WjrfImHg==} peerDependencies: @@ -9775,10 +10296,23 @@ packages: yaml: optional: true + vitefu@1.1.3: + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + vite: + optional: true + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + wait-on@9.0.10: + resolution: {integrity: sha512-rCoJEhvMr0X6alHmwc9abbrA5ZrLZFKpFQVKPNFwl2h7DapXOGdmimIHDtLOWhT4PjhZhxFEtZoQgEXbkDWdZw==} + engines: {node: '>=20.0.0'} + hasBin: true + warning@3.0.0: resolution: {integrity: sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==} @@ -9813,6 +10347,11 @@ packages: webpack-cli: optional: true + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} @@ -9977,6 +10516,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@acemir/cssom@0.9.31': {} @@ -10001,7 +10543,7 @@ snapshots: '@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 + lru-cache: 11.5.1 '@asamuzakjp/dom-selector@6.8.1': dependencies: @@ -10795,6 +11337,22 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.7': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -10996,6 +11554,45 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@mdx-js/loader@3.1.1(webpack@5.108.1(lightningcss@1.32.0))': + dependencies: + '@mdx-js/mdx': 3.1.1 + source-map: 0.7.6 + optionalDependencies: + webpack: 5.108.1(lightningcss@1.32.0) + transitivePeerDependencies: + - supports-color + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.14 + acorn: 8.17.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.17.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + '@mjackson/form-data-parser@0.9.1': dependencies: '@mjackson/multipart-parser': 0.10.1 @@ -11650,6 +12247,10 @@ snapshots: dependencies: playwright: 1.58.0 + '@playwright/test@1.61.1': + dependencies: + playwright: 1.61.1 + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -12293,7 +12894,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@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))': + '@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)(@vitejs/plugin-rsc@0.5.27(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)))(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 @@ -12327,6 +12928,7 @@ snapshots: 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.18.0(react-router@7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@vitejs/plugin-rsc': 0.5.27(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)) typescript: 5.9.3 wrangler: 4.105.0(@cloudflare/workers-types@4.20260628.1) transitivePeerDependencies: @@ -12344,7 +12946,7 @@ snapshots: - tsx - yaml - '@react-router/dev@8.0.1(@react-router/serve@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))(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))': + '@react-router/dev@8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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 @@ -12377,13 +12979,14 @@ snapshots: 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: '@react-router/serve': 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) + '@vitejs/plugin-rsc': 0.5.27(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)) typescript: 5.9.3 wrangler: 4.105.0(@cloudflare/workers-types@4.20260628.1) transitivePeerDependencies: - babel-plugin-macros - supports-color - '@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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))': + '@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@vitejs/plugin-rsc@0.5.27(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)))(react-router@8.0.1(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 @@ -12416,12 +13019,21 @@ snapshots: 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: '@react-router/serve': 8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) + '@vitejs/plugin-rsc': 0.5.27(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)) typescript: 5.9.3 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.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.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.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.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3) @@ -12438,6 +13050,14 @@ snapshots: optionalDependencies: typescript: 5.9.3 + '@react-router/express@8.0.1(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': 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) + 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@8.0.1(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': 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) @@ -12445,7 +13065,6 @@ snapshots: react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 5.9.3 - optional: true '@react-router/express@8.0.1(express@5.2.1)(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: @@ -12455,17 +13074,24 @@ snapshots: optionalDependencies: typescript: 5.9.3 - '@react-router/fs-routes@8.0.1(@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)))(typescript@5.9.3)': + '@react-router/fs-routes@8.0.1(@react-router/dev@8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)))(typescript@5.9.3)': dependencies: - '@react-router/dev': 8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(react-router@8.0.1(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)) + '@react-router/dev': 8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)) minimatch: 10.2.5 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)': + '@react-router/fs-routes@8.0.1(@react-router/dev@8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@vitejs/plugin-rsc@0.5.27(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)))(react-router@8.0.1(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)))(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) + '@react-router/dev': 8.0.1(@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3))(@vitejs/plugin-rsc@0.5.27(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)))(react-router@8.0.1(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)) + minimatch: 10.2.5 + 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 @@ -12490,9 +13116,15 @@ snapshots: optionalDependencies: 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)': + '@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)(@vitejs/plugin-rsc@0.5.27(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)))(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.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)(@vitejs/plugin-rsc@0.5.27(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)))(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/remix-routes-option-adapter@8.0.1(@react-router/dev@8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)))(typescript@5.9.3)': dependencies: - '@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)) + '@react-router/dev': 8.0.1(@react-router/serve@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))(@vitejs/plugin-rsc@0.5.27(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)))(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)) optionalDependencies: typescript: 5.9.3 @@ -12525,7 +13157,6 @@ snapshots: transitivePeerDependencies: - supports-color - typescript - optional: true '@react-router/serve@8.0.1(react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@5.9.3)': dependencies: @@ -12586,6 +13217,9 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.53': {} + '@rolldown/pluginutils@1.0.1': + optional: true + '@rollup/rollup-android-arm-eabi@4.62.2': optional: true @@ -12669,6 +13303,15 @@ snapshots: core-js: 3.47.0 jiti: 2.7.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)': + 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) + '@swc/helpers': 0.5.23 + optionalDependencies: + core-js: 3.47.0 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + '@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) @@ -12709,6 +13352,15 @@ snapshots: - '@rspack/core' - webpack + '@rsbuild/plugin-mdx@1.1.3(@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))(webpack@5.108.1(lightningcss@1.32.0))': + dependencies: + '@mdx-js/loader': 3.1.1(webpack@5.108.1(lightningcss@1.32.0)) + optionalDependencies: + '@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: + - supports-color + - webpack + '@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.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) @@ -12831,6 +13483,17 @@ snapshots: - '@rspack/core' - webpack + '@rslib/core@0.22.1(@module-federation/runtime-tools@2.5.1(node-fetch@2.7.0(encoding@0.1.13)))(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-plugin-dts: 0.22.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))(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@module-federation/runtime-tools' + - '@typescript/native-preview' + - core-js + '@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)(core-js@3.47.0) @@ -12997,6 +13660,13 @@ snapshots: 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)': + 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/core@2.0.8(@module-federation/runtime-tools@2.5.1)(@swc/helpers@0.5.23)': dependencies: '@rspack/binding': 2.0.8 @@ -13334,6 +14004,8 @@ snapshots: '@speed-highlight/core@1.2.17': {} + '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -13593,6 +14265,10 @@ snapshots: '@types/d3-hierarchy@1.1.11': {} + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/eslint@9.6.1': @@ -13600,6 +14276,10 @@ snapshots: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + '@types/estree@1.0.5': {} '@types/estree@1.0.9': {} @@ -13626,6 +14306,10 @@ snapshots: dependencies: glob: 13.0.0 + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/http-errors@2.0.5': {} '@types/jsesc@3.0.3': {} @@ -13636,10 +14320,18 @@ snapshots: dependencies: '@types/node': 25.0.10 + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.14': {} + '@types/morgan@1.9.10': dependencies: '@types/node': 25.0.10 + '@types/ms@2.1.0': {} + '@types/mysql@2.15.27': dependencies: '@types/node': 25.0.10 @@ -13707,6 +14399,10 @@ snapshots: dependencies: '@types/node': 25.0.10 + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@types/ws@8.18.1': dependencies: '@types/node': 25.0.10 @@ -13802,6 +14498,8 @@ snapshots: '@typescript-eslint/types': 8.62.0 eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.2': {} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true @@ -13902,6 +14600,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-rsc@0.5.27(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))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + es-module-lexer: 2.1.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + srvx: 0.11.17 + strip-literal: 3.1.0 + turbo-stream: 3.2.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) + vitefu: 1.1.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)) + optional: true + '@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.62.0 @@ -14284,8 +14997,12 @@ snapshots: assertion-error@2.0.1: {} + astring@1.9.0: {} + async-function@1.0.0: {} + asynckit@0.4.0: {} + autoprefixer@10.4.23(postcss@8.5.16): dependencies: browserslist: 4.28.4 @@ -14299,6 +15016,16 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 + axios@1.18.1: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.7 @@ -14308,6 +15035,8 @@ snapshots: transitivePeerDependencies: - supports-color + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -14487,6 +15216,8 @@ snapshots: caniuse-lite@1.0.30001799: {} + ccount@2.0.1: {} + chain-function@1.0.1: {} chalk-template@0.4.0: @@ -14508,8 +15239,39 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chardet@2.2.0: {} + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.2.2 + css-what: 6.2.2 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + + cheerio@1.2.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.1 + htmlparser2: 10.1.0 + parse5: 7.3.0 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 7.28.0 + whatwg-mimetype: 4.0.0 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -14578,6 +15340,8 @@ snapshots: clsx@2.1.1: {} + collapse-white-space@2.1.0: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -14592,6 +15356,12 @@ snapshots: colorjs.io@0.5.2: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} commander@2.20.3: {} @@ -14720,7 +15490,7 @@ snapshots: '@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 + lru-cache: 11.5.1 csstype@3.2.3: {} @@ -14811,6 +15581,10 @@ snapshots: decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -14845,6 +15619,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + depd@2.0.0: {} dequal@2.0.3: {} @@ -14857,6 +15633,10 @@ snapshots: detect-node-es@1.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dijkstrajs@1.0.3: {} dir-glob@3.0.1: @@ -14919,6 +15699,11 @@ snapshots: encodeurl@2.0.0: {} + encoding-sniffer@0.2.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -14967,6 +15752,8 @@ snapshots: entities@6.0.1: {} + entities@7.0.1: {} + entities@8.0.0: {} envinfo@7.21.0: {} @@ -15101,6 +15888,20 @@ snapshots: es-toolkit@1.49.0: {} + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.17.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -15316,6 +16117,39 @@ snapshots: estraverse@5.3.0: {} + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.9 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} etag@1.8.1: {} @@ -15447,6 +16281,8 @@ snapshots: exsolve@1.1.0: {} + extend@3.0.2: {} + extendable-error@0.1.7: {} fast-deep-equal@3.1.3: {} @@ -15537,6 +16373,8 @@ snapshots: flatted@3.4.2: {} + follow-redirects@1.16.0: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -15546,6 +16384,14 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + forwarded-parse@2.1.2: {} forwarded@0.2.0: {} @@ -15753,6 +16599,51 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.9 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + he@1.2.0: {} headers-polyfill@4.0.3: {} @@ -15788,6 +16679,13 @@ snapshots: domutils: 3.2.2 entities: 6.0.1 + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 @@ -15882,6 +16780,8 @@ snapshots: ini@1.3.8: {} + inline-style-parser@0.2.7: {} + input-otp@1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: react: 19.2.7 @@ -15899,6 +16799,13 @@ snapshots: ipaddr.js@1.9.1: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -15945,6 +16852,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-docker@2.2.1: {} is-document.all@1.0.0: @@ -15971,6 +16880,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-interactive@2.0.0: {} is-map@2.0.3: {} @@ -16113,8 +17024,21 @@ snapshots: jiti@2.7.0: {} + joi@18.2.3: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.7 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.1.0 + js-tokens@4.0.0: {} + js-tokens@9.0.1: + optional: true + js-yaml@3.15.0: dependencies: argparse: 1.0.10 @@ -16331,6 +17255,8 @@ snapshots: long-timeout@0.1.1: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -16364,10 +17290,111 @@ snapshots: make-dir@5.1.0: optional: true + markdown-extensions@2.0.0: {} + marked@15.0.12: {} math-intrinsics@1.1.0: {} + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.2 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdn-data@2.27.1: {} media-query-parser@2.0.2: @@ -16390,6 +17417,212 @@ snapshots: methods@1.1.2: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.9 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.9 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -16763,6 +17996,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@4.0.0: dependencies: error-ex: 1.3.4 @@ -16781,6 +18024,19 @@ snapshots: parse-passwd@1.0.0: {} + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.3.0 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.3.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + parse5@8.0.1: dependencies: entities: 8.0.0 @@ -16984,6 +18240,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.2.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -16991,6 +18249,8 @@ snapshots: proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} + prr@1.0.1: optional: true @@ -17221,6 +18481,35 @@ snapshots: readdirp@5.0.0: {} + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.9 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.9 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -17257,6 +18546,38 @@ snapshots: dependencies: rc: 1.2.8 + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + remix-auth-github@3.0.2(remix-auth@4.2.0): dependencies: '@mjackson/headers': 0.9.0 @@ -17273,12 +18594,13 @@ snapshots: fs-extra: 11.3.3 minimatch: 10.2.5 - 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): + 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.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.7 react-router: 7.18.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -17387,6 +18709,13 @@ snapshots: transitivePeerDependencies: - supports-color + rsbuild-plugin-dts@0.22.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))(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) + optionalDependencies: + typescript: 5.9.3 + 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 @@ -17727,6 +19056,11 @@ snapshots: shell-quote@1.9.0: {} + shelljs@0.10.0: + dependencies: + execa: 5.1.1 + fast-glob: 3.3.3 + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -17825,6 +19159,8 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 @@ -17855,6 +19191,9 @@ snapshots: argparse: 2.0.1 nearley: 2.20.1 + srvx@0.11.17: + optional: true + stable-hash-x@0.2.0: {} statuses@2.0.2: {} @@ -17943,6 +19282,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -17963,10 +19307,25 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-indent@4.1.1: {} + strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + optional: true + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + supports-color@10.2.2: {} supports-color@5.5.0: @@ -18072,6 +19431,10 @@ snapshots: tree-kill@1.2.2: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -18095,6 +19458,9 @@ snapshots: turbo-stream@2.4.1: {} + turbo-stream@3.2.0: + optional: true + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -18185,6 +19551,43 @@ snapshots: unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@0.1.2: {} universalify@2.0.1: {} @@ -18278,6 +19681,16 @@ snapshots: vary@1.1.2: {} + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + 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.29.7 @@ -18341,10 +19754,26 @@ snapshots: terser: 5.48.0 tsx: 4.21.0 + vitefu@1.1.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)): + optionalDependencies: + 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) + optional: true + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + wait-on@9.0.10: + dependencies: + axios: 1.18.1 + joi: 18.2.3 + lodash: 4.18.1 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + - supports-color + warning@3.0.0: dependencies: loose-envify: 1.4.0 @@ -18442,6 +19871,10 @@ snapshots: - postcss - uglify-js + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + whatwg-mimetype@4.0.0: {} whatwg-mimetype@5.0.0: {} @@ -18681,3 +20114,5 @@ snapshots: '@yuku-parser/binding-win32-x64': 0.5.39 zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/tests/react-router-framework/README.md b/tests/react-router-framework/README.md new file mode 100644 index 00000000..e976ff83 --- /dev/null +++ b/tests/react-router-framework/README.md @@ -0,0 +1,36 @@ +# React Router Framework Tests + +This folder contains copied React Router upstream framework-mode tests adapted +to exercise `rsbuild-plugin-react-router`. + +## Source + +- `integration/` is copied from `/home/zack/projects/react-router/integration`. +- `react-router-dev/__tests__/` is copied from + `/home/zack/projects/react-router/packages/react-router-dev/__tests__`. + +Keep upstream test files as close to source as practical so the suite can be +refreshed by copying those folders again. + +## Rsbuild Adapter + +The copied integration harness still has Vite-oriented names because the +upstream test suite does. Execution is redirected through +`integration/helpers/rsbuild-adapter.ts` and the patched helper files: + +- fixture projects get `rsbuild.config.ts`, not `vite.config.ts` +- builds run `@rsbuild/core` +- dev servers run `rsbuild dev` +- production servers run `react-router-serve` +- MDX routes use the official `@rsbuild/plugin-mdx` + +Do not add a local ignore list for Rsbuild gaps. Upstream `test.skip` and +`test.fixme` calls should remain intact, but otherwise unsupported cases should +fail visibly. + +## Commands + +```sh +pnpm test:react-router-framework:smoke +pnpm test:react-router-framework +``` diff --git a/tests/react-router-framework/integration/CHANGELOG.md b/tests/react-router-framework/integration/CHANGELOG.md new file mode 100644 index 00000000..2cf67d87 --- /dev/null +++ b/tests/react-router-framework/integration/CHANGELOG.md @@ -0,0 +1,14 @@ +# integration-tests + +## 0.0.0 + +### Minor Changes + +- Unstable Vite support for Node-based Remix apps ([#7590](https://github.com/remix-run/remix/pull/7590)) + - `remix build` 👉 `vite build && vite build --ssr` + - `remix dev` 👉 `vite dev` + + Other runtimes (e.g. Deno, Cloudflare) not yet supported. + Custom server (e.g. Express) not yet supported. + + See "Future > Vite" in the Remix Docs for details. diff --git a/tests/react-router-framework/integration/abort-signal-test.ts b/tests/react-router-framework/integration/abort-signal-test.ts new file mode 100644 index 00000000..f650ec6f --- /dev/null +++ b/tests/react-router-framework/integration/abort-signal-test.ts @@ -0,0 +1,65 @@ +import { test } from "@playwright/test"; + +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; + +let fixture: Fixture; +let appFixture: AppFixture; + +test.beforeAll(async () => { + fixture = await createFixture({ + files: { + "app/routes/_index.tsx": js` + import { useActionData, useLoaderData, Form } from "react-router"; + + export async function action ({ request }) { + // New event loop causes express request to close + await new Promise(r => setTimeout(r, 0)); + return { aborted: request.signal.aborted }; + } + + export function loader({ request }) { + return { aborted: request.signal.aborted }; + } + + export default function Index() { + let actionData = useActionData(); + let data = useLoaderData(); + return ( +
+

{actionData ? String(actionData.aborted) : "empty"}

+

{String(data.aborted)}

+
+ +
+
+ ) + } + `, + }, + }); + + // This creates an interactive app using playwright. + appFixture = await createAppFixture(fixture); +}); + +test.afterAll(() => { + appFixture.close(); +}); + +test("should not abort the request in a new event loop", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await page.waitForSelector(`.action:has-text("empty")`); + await page.waitForSelector(`.loader:has-text("false")`); + + await app.clickElement('button[type="submit"]'); + + await page.waitForSelector(`.action:has-text("false")`); + await page.waitForSelector(`.loader:has-text("false")`); +}); diff --git a/tests/react-router-framework/integration/action-test.ts b/tests/react-router-framework/integration/action-test.ts new file mode 100644 index 00000000..6e9a3a57 --- /dev/null +++ b/tests/react-router-framework/integration/action-test.ts @@ -0,0 +1,232 @@ +import { test, expect } from "@playwright/test"; + +import { + createFixture, + createAppFixture, + js, +} from "./helpers/create-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { PlaywrightFixture, selectHtml } from "./helpers/playwright-fixture.js"; +import { type TemplateName } from "./helpers/vite.js"; + +const templateNames = [ + "vite-7-template", + "rsc-vite-framework", +] as const satisfies TemplateName[]; + +test.describe("actions", () => { + for (const templateName of templateNames) { + test.describe(`template: ${templateName}`, () => { + let fixture: Fixture; + let appFixture: AppFixture; + + let FIELD_NAME = "message"; + let WAITING_VALUE = "Waiting..."; + let SUBMITTED_VALUE = "Submission"; + let THROWS_REDIRECT = "redirect-throw"; + let REDIRECT_TARGET = "page"; + let PAGE_TEXT = "PAGE_TEXT"; + + test.beforeAll(async () => { + fixture = await createFixture({ + templateName, + files: { + "app/routes/urlencoded.tsx": js` + import { Form, useActionData } from "react-router"; + + export let action = async ({ request }) => { + let formData = await request.formData(); + return formData.get("${FIELD_NAME}"); + }; + + export default function Actions() { + let data = useActionData() + + return ( +
+

+ {data ? {data} : "${WAITING_VALUE}"} +

+

+ + +

+
+ ); + } + `, + + "app/routes/request-text.tsx": js` + import { Form, useActionData } from "react-router"; + + export let action = async ({ request }) => { + let text = await request.text(); + return text; + }; + + export default function Actions() { + let data = useActionData() + + return ( +
+

+ {data ? {data} : "${WAITING_VALUE}"} +

+

+ + + +

+
+ ); + } + `, + + [`app/routes/${THROWS_REDIRECT}.jsx`]: js` + import { redirect, Form } from "react-router"; + + export function action() { + throw redirect("/${REDIRECT_TARGET}") + } + + export default function () { + return ( +
+ +
+ ) + } + `, + + [`app/routes/${REDIRECT_TARGET}.jsx`]: js` + export default function () { + return
${PAGE_TEXT}
+ } + `, + + "app/routes/no-action.tsx": js` + import { Form } from "react-router"; + + export default function Component() { + return ( +
+ +
+ ); + } + `, + }, + }); + + appFixture = await createAppFixture(fixture); + }); + + test.afterAll(() => { + appFixture.close(); + }); + + let logs: string[] = []; + + test.beforeEach(({ page }) => { + page.on("console", (msg) => { + logs.push(msg.text()); + }); + }); + + test.afterEach(() => { + expect(logs).toHaveLength(0); + }); + + test("is not called on document GET requests", async () => { + let res = await fixture.requestDocument("/urlencoded"); + let html = await selectHtml(await res.text(), "#text"); + expect(html).toMatch(WAITING_VALUE); + }); + + test("is called on document POST requests", async () => { + let FIELD_VALUE = "cheeseburger"; + + let params = new URLSearchParams(); + params.append(FIELD_NAME, FIELD_VALUE); + + let res = await fixture.postDocument("/urlencoded", params); + + let html = await selectHtml(await res.text(), "#text"); + expect(html).toMatch(FIELD_VALUE); + }); + + test("is called on script transition POST requests", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(`/urlencoded`); + await page.waitForSelector(`#text:has-text("${WAITING_VALUE}")`); + + await page.click("button[type=submit]"); + await page.waitForSelector("#action-text"); + await page.waitForSelector(`#text:has-text("${SUBMITTED_VALUE}")`); + }); + + test("throws a 405 when no action exists", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(`/no-action`); + await page.click("button[type=submit]"); + await page.waitForSelector(`h1:has-text("405 Method Not Allowed")`); + expect(logs.length).toBe(2); + expect(logs[0]).toMatch( + 'Route "routes/no-action" does not have an action', + ); + // logs[1] is the raw ErrorResponse instance from the boundary but playwright + // seems to just log the name of the constructor, which in the minified code + // is meaningless so we don't bother asserting + + // The rest of the tests in this suite assert no logs, so clear this out to + // avoid failures in afterEach + logs = []; + }); + + test("properly encodes form data for request.text() usage", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(`/request-text`); + await page.waitForSelector(`#text:has-text("${WAITING_VALUE}")`); + + await page.click("button[type=submit]"); + await page.waitForSelector("#action-text"); + expect(await app.getHtml("#action-text")).toBe( + 'a=1&b=2', + ); + }); + + test("redirects a thrown response on document requests", async () => { + let params = new URLSearchParams(); + let res = await fixture.postDocument(`/${THROWS_REDIRECT}`, params); + expect(res.status).toBe(302); + expect(res.headers.get("Location")).toBe(`/${REDIRECT_TARGET}`); + }); + + test("redirects a thrown response on script transitions", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(`/${THROWS_REDIRECT}`); + let responses = app.collectSingleFetchResponses(); + await app.clickSubmitButton(`/${THROWS_REDIRECT}`); + + await page.waitForSelector(`#${REDIRECT_TARGET}`); + + // In RSC, every route implicitly has a loader, so we get an extra + // response for the page we've redirected to. To keep the rest of the + // test RSC-agnostic, we drop the last response. + if (templateName.includes("rsc")) { + responses = responses.slice(0, -1); + } + + expect(responses).toHaveLength(1); + expect(responses[0].status()).toBe(202); + + expect(new URL(page.url()).pathname).toBe(`/${REDIRECT_TARGET}`); + expect(await app.getHtml()).toMatch(PAGE_TEXT); + }); + }); + } +}); diff --git a/tests/react-router-framework/integration/assets/toupload.txt b/tests/react-router-framework/integration/assets/toupload.txt new file mode 100644 index 00000000..b45ef6fe --- /dev/null +++ b/tests/react-router-framework/integration/assets/toupload.txt @@ -0,0 +1 @@ +Hello, World! \ No newline at end of file diff --git a/tests/react-router-framework/integration/assets/touploadtoobig.txt b/tests/react-router-framework/integration/assets/touploadtoobig.txt new file mode 100644 index 00000000..8811b052 --- /dev/null +++ b/tests/react-router-framework/integration/assets/touploadtoobig.txt @@ -0,0 +1 @@ +Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World!Hello, World! \ No newline at end of file diff --git a/tests/react-router-framework/integration/blocking-test.ts b/tests/react-router-framework/integration/blocking-test.ts new file mode 100644 index 00000000..1929f02b --- /dev/null +++ b/tests/react-router-framework/integration/blocking-test.ts @@ -0,0 +1,113 @@ +import { test, expect } from "@playwright/test"; + +import type { AppFixture, Fixture } from "./helpers/create-fixture.js"; +import { + createFixture, + js, + createAppFixture, +} from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; + +let fixture: Fixture; +let appFixture: AppFixture; + +test.afterAll(() => appFixture.close()); + +test("handles synchronous proceeding correctly", async ({ page }) => { + fixture = await createFixture({ + files: { + "app/routes/_index.tsx": js` + import { Link } from "react-router"; + export default function Component() { + return ( +
+

Index

+ /a +
+ ) + } + `, + "app/routes/a.tsx": js` + import { Link } from "react-router"; + export default function Component() { + return ( +
+

A

+ /b +
+ ) + } + `, + "app/routes/b.tsx": js` + import * as React from "react"; + import { Form, useBlocker } from "react-router"; + export default function Component() { + return ( +
+

B

+ +
+ ) + } + function ImportantForm() { + let [value, setValue] = React.useState(""); + let shouldBlock = React.useCallback( + ({ currentLocation, nextLocation }) => + value !== "" && currentLocation.pathname !== nextLocation.pathname, + [value] + ); + let blocker = useBlocker(shouldBlock); + // Reset the blocker if the user cleans the form + React.useEffect(() => { + if (blocker.state === "blocked") { + blocker.proceed(); + } + }, [blocker]); + return ( + <> +

+ Is the form dirty?{" "} + {value !== "" ? ( + Yes + ) : ( + No + )} +

+
+ + +
+ + ); + } + `, + }, + }); + + // This creates an interactive app using puppeteer. + appFixture = await createAppFixture(fixture); + + let app = new PlaywrightFixture(appFixture, page); + + await app.goto("/"); + await app.clickLink("/a"); + await page.waitForSelector("#a"); + await app.clickLink("/b"); + await page.waitForSelector("#b"); + await page.getByLabel("Enter some important data:").fill("Hello Remix!"); + + // Going back should: + // - block + // - immediately call blocker.proceed() once we enter the blocked state + // - and land back one history entry (/a) + await page.goBack(); + await page.waitForSelector("#a"); + expect(await app.getHtml()).toContain("A"); +}); diff --git a/tests/react-router-framework/integration/browser-entry-test.ts b/tests/react-router-framework/integration/browser-entry-test.ts new file mode 100644 index 00000000..ec1731f5 --- /dev/null +++ b/tests/react-router-framework/integration/browser-entry-test.ts @@ -0,0 +1,322 @@ +import { test, expect } from "@playwright/test"; + +import { + createFixture, + js, + createAppFixture, +} from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; + +test( + "expect to be able to browse backward out of a remix app, then forward " + + "twice in history and have pages render correctly", + async ({ page, browserName }) => { + test.skip( + browserName === "firefox", + "FireFox doesn't support browsing to an empty page (aka about:blank)", + ); + + let fixture = await createFixture({ + files: { + "app/routes/_index.tsx": js` + import { Link } from "react-router"; + + export default function Index() { + return ( +
+
pizza
+ burger link +
+ ) + } + `, + + "app/routes/burgers.tsx": js` + export default function Index() { + return
cheeseburger
; + } + `, + }, + }); + + // This creates an interactive app using puppeteer. + let appFixture = await createAppFixture(fixture); + + let app = new PlaywrightFixture(appFixture, page); + + // Slow down the entry chunk on the second load so the bug surfaces + let isSecondLoad = false; + await page.route(/entry/, async (route) => { + if (isSecondLoad) { + await new Promise((r) => setTimeout(r, 1000)); + } + route.continue(); + }); + + // This sets up the Remix modules cache in memory, priming the error case. + await app.goto("/"); + await app.clickLink("/burgers"); + await page.waitForSelector("#cheeseburger"); + expect(await page.content()).toContain("cheeseburger"); + await page.goBack(); + await page.waitForSelector("#pizza"); + expect(await app.getHtml()).toContain("pizza"); + + // Takes the browser out of the Remix app + await page.goBack(); + expect(page.url()).toContain("about:blank"); + + // Forward to / and immediately again to /burgers. This will trigger the + // error since we'll load __routeModules for / but then try to hydrate /burgers + isSecondLoad = true; + await page.goForward(); + await page.goForward(); + await page.waitForSelector("#cheeseburger"); + + // If we resolve the error, we should hard reload and eventually + // successfully render /burgers + await page.waitForSelector("#cheeseburger"); + expect(await app.getHtml()).toContain("cheeseburger"); + + appFixture.close(); + }, +); + +test("allows users to pass a client side context to HydratedRouter", async ({ + page, +}) => { + let fixture = await createFixture({ + files: { + "app/entry.client.tsx": js` + import { createContext, RouterContextProvider } from "react-router"; + import { HydratedRouter } from "react-router/dom"; + import { startTransition, StrictMode } from "react"; + import { hydrateRoot } from "react-dom/client"; + + export const myContext = new createContext('foo'); + + startTransition(() => { + hydrateRoot( + document, + + { + return new RouterContextProvider([ + [myContext, 'bar'] + ]); + }} + /> + + ); + }); + `, + "app/routes/_index.tsx": js` + import { myContext } from "../entry.client"; + + export function clientLoader({ context }) { + return context.get(myContext); + } + export default function Index({ loaderData }) { + return

Hello, {loaderData}

+ } + `, + }, + }); + + let appFixture = await createAppFixture(fixture); + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + expect(await app.getHtml()).toContain("Hello, bar"); + + appFixture.close(); +}); + +test("allows users to pass an onError function to HydratedRouter", async ({ + page, + browserName, +}) => { + let fixture = await createFixture({ + files: { + "app/entry.client.tsx": js` + import { HydratedRouter } from "react-router/dom"; + import { startTransition, StrictMode } from "react"; + import { hydrateRoot } from "react-dom/client"; + + startTransition(() => { + hydrateRoot( + document, + + { + console.log(error.message, JSON.stringify(errorInfo)) + }} + /> + + ); + }); + `, + "app/routes/_index.tsx": js` + import { Link } from "react-router"; + export default function Index() { + return Go to Page; + } + `, + "app/routes/page.tsx": js` + export default function Page() { + throw new Error("Render error"); + } + export function ErrorBoundary({ error }) { + return

Error: {error.message}

+ } + `, + }, + }); + + let logs: string[] = []; + page.on("console", (msg) => logs.push(msg.text())); + + let appFixture = await createAppFixture(fixture); + let app = new PlaywrightFixture(appFixture, page); + + await app.goto("/", true); + await page.click('a[href="/page"]'); + await page.waitForSelector("[data-error]"); + + expect(await app.getHtml()).toContain("Error: Render error"); + expect(logs.length).toBe(2); + // First one is react logging the error + if (browserName === "firefox") { + expect(logs[0]).toContain("Error"); + } else { + expect(logs[0]).toContain("Error: Render error"); + } + expect(logs[0]).not.toContain("componentStack"); + // Second one is ours + expect(logs[1]).toContain("Render error"); + expect(logs[1]).toContain('"componentStack":'); + + appFixture.close(); +}); + +test("allows users to instrument the client side router via HydratedRouter", async ({ + page, +}) => { + let fixture = await createFixture({ + files: { + "app/entry.client.tsx": js` + import { HydratedRouter } from "react-router/dom"; + import { startTransition, StrictMode } from "react"; + import { hydrateRoot } from "react-dom/client"; + + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); + `, + "app/routes/_index.tsx": js` + import { Link } from "react-router"; + export default function Index() { + return Go to Page; + } + `, + "app/routes/page.tsx": js` + import { useFetcher } from "react-router"; + export function loader() { + return { data: "hello world" }; + } + export function action() { + return "OK"; + } + export default function Page({ loaderData }) { + let fetcher = useFetcher({ key: 'a' }); + return ( + <> +

{loaderData.data}

; + + {fetcher.data ?
{fetcher.data}
: null} + + ); + } + `, + }, + }); + + let logs: string[] = []; + page.on("console", (msg) => logs.push(msg.text())); + + let appFixture = await createAppFixture(fixture); + let app = new PlaywrightFixture(appFixture, page); + + await app.goto("/", true); + await page.click('a[href="/page"]'); + await page.waitForSelector("[data-page]"); + + expect(await app.getHtml()).toContain("hello world"); + expect(logs).toEqual([ + 'start navigate [["currentUrl","/"],["to","/page"]]', + "start loader root /page", + "start loader routes/page /page", + "end loader root /page", + "end loader routes/page /page", + 'end navigate [["currentUrl","/"],["to","/page"]]', + ]); + logs.splice(0); + + await page.click("[data-fetch]"); + await page.waitForSelector("[data-fetcher-data]"); + await expect(page.locator("[data-fetcher-data]")).toContainText("OK"); + expect(logs).toEqual([ + 'start fetch [["body",{"key":"value"}],["currentUrl","/page"],["fetcherKey","a"],["formData",null],["formEncType","application/x-www-form-urlencoded"],["formMethod","post"],["href","/page"]]', + "start action routes/page /page", + "end action routes/page /page", + "start loader root /page", + "start loader routes/page /page", + "end loader root /page", + "end loader routes/page /page", + 'end fetch [["body",{"key":"value"}],["currentUrl","/page"],["fetcherKey","a"],["formData",null],["formEncType","application/x-www-form-urlencoded"],["formMethod","post"],["href","/page"]]', + ]); + + appFixture.close(); +}); diff --git a/tests/react-router-framework/integration/bug-report-test.ts b/tests/react-router-framework/integration/bug-report-test.ts new file mode 100644 index 00000000..8be63c7f --- /dev/null +++ b/tests/react-router-framework/integration/bug-report-test.ts @@ -0,0 +1,127 @@ +import { test, expect } from "@playwright/test"; + +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; + +let fixture: Fixture; +let appFixture: AppFixture; + +//////////////////////////////////////////////////////////////////////////////// +// 👋 Hola! I'm here to help you write a great bug report pull request. +// +// You don't need to fix the bug, this is just to report one. +// +// The pull request you are submitting is supposed to fail when created, to let +// the team see the erroneous behavior, and understand what's going wrong. +// +// If you happen to have a fix as well, it will have to be applied in a subsequent +// commit to this pull request, and your now-succeeding test will have to be moved +// to the appropriate file. +// +// First, make sure to install dependencies and build React Router. From the root of +// the project, run this: +// +// ``` +// pnpm install && pnpm build +// ``` +// +// If you have never installed playwright on your system before, you may also need +// to install a browser engine: +// +// ``` +// pnpm exec playwright install chromium +// ``` +// +// Now try running this test: +// +// ``` +// pnpm test:integration bug-report --project chromium +// ``` +// +// You can add `--watch` to the end to have it re-run on file changes: +// +// ``` +// pnpm test:integration bug-report --project chromium --watch +// ``` +//////////////////////////////////////////////////////////////////////////////// + +test.beforeEach(async ({ context }) => { + await context.route(/\.data$/, async (route) => { + await new Promise((resolve) => setTimeout(resolve, 50)); + route.continue(); + }); +}); + +test.beforeAll(async () => { + fixture = await createFixture({ + //////////////////////////////////////////////////////////////////////////// + // 💿 Next, add files to this object, just like files in a real app, + // `createFixture` will make an app and run your tests against it. + //////////////////////////////////////////////////////////////////////////// + files: { + "app/routes/_index.tsx": js` + import { useLoaderData, Link } from "react-router"; + + export function loader() { + return "pizza"; + } + + export default function Index() { + let data = useLoaderData(); + return ( +
+ {data} + Other Route +
+ ) + } + `, + + "app/routes/burgers.tsx": js` + export default function Index() { + return
cheeseburger
; + } + `, + }, + }); + + // This creates an interactive app using playwright. + appFixture = await createAppFixture(fixture); +}); + +test.afterAll(() => { + appFixture.close(); +}); + +//////////////////////////////////////////////////////////////////////////////// +// 💿 Almost done, now write your failing test case(s) down here Make sure to +// add a good description for what you expect React Router to do 👇🏽 +//////////////////////////////////////////////////////////////////////////////// + +test("[description of what you expect it to do]", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + // You can test any request your app might get using `fixture`. + let response = await fixture.requestDocument("/"); + expect(await response.text()).toMatch("pizza"); + + // If you need to test interactivity use the `app` + await app.goto("/"); + await app.clickLink("/burgers"); + await page.waitForSelector("text=cheeseburger"); + + // If you're not sure what's going on, you can "poke" the app, it'll + // automatically open up in your browser for 20 seconds, so be quick! + // await app.poke(20); + + // Go check out the other tests to see what else you can do. +}); + +//////////////////////////////////////////////////////////////////////////////// +// 💿 Finally, push your changes to your fork of React Router +// and open a pull request! +//////////////////////////////////////////////////////////////////////////////// diff --git a/tests/react-router-framework/integration/catch-boundary-data-test.ts b/tests/react-router-framework/integration/catch-boundary-data-test.ts new file mode 100644 index 00000000..7deba08e --- /dev/null +++ b/tests/react-router-framework/integration/catch-boundary-data-test.ts @@ -0,0 +1,258 @@ +import { test, expect } from "@playwright/test"; + +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import { type TemplateName } from "./helpers/vite.js"; + +const templateNames = [ + "vite-7-template", + "rsc-vite-framework", +] as const satisfies TemplateName[]; + +let ROOT_BOUNDARY_TEXT = "ROOT_TEXT" as const; +let LAYOUT_BOUNDARY_TEXT = "LAYOUT_BOUNDARY_TEXT" as const; +let OWN_BOUNDARY_TEXT = "OWN_BOUNDARY_TEXT" as const; + +let NO_BOUNDARY_LOADER_FILE = "/no.loader" as const; +let NO_BOUNDARY_LOADER = "/no/loader" as const; + +let HAS_BOUNDARY_LAYOUT_NESTED_LOADER_FILE = + "/yes.loader-layout-boundary" as const; +let HAS_BOUNDARY_LAYOUT_NESTED_LOADER = "/yes/loader-layout-boundary" as const; + +let HAS_BOUNDARY_NESTED_LOADER_FILE = "/yes.loader-self-boundary" as const; +let HAS_BOUNDARY_NESTED_LOADER = "/yes/loader-self-boundary" as const; + +let ROOT_DATA = "root data"; +let LAYOUT_DATA = "root data"; + +test.describe("ErrorBoundary (thrown responses)", () => { + for (const templateName of templateNames) { + let fixture: Fixture; + let appFixture: AppFixture; + + test.describe(`template: ${templateName}`, () => { + test.beforeEach(async ({ context }) => { + await context.route(/.(data|rsc)/, async (route) => { + await new Promise((resolve) => setTimeout(resolve, 50)); + route.continue(); + }); + }); + + test.beforeAll(async () => { + fixture = await createFixture({ + templateName, + files: { + "app/root.tsx": js` + import { + Links, + Meta, + Outlet, + Scripts, + useLoaderData, + useMatches, + } from "react-router"; + + export const loader = () => "${ROOT_DATA}"; + + export default function Root() { + const loaderData = useLoaderData(); + + return ( + + + + + + +
{loaderData}
+ + + + + ); + } + + export function ErrorBoundary() { + let matches = useMatches(); + let { loaderData } = matches.find(match => match.id === "root"); + + return ( + + + +
${ROOT_BOUNDARY_TEXT}
+
{loaderData}
+ + + + ); + } + `, + + "app/routes/_index.tsx": js` + import { Link } from "react-router"; + export default function Index() { + return ( +
+ ${NO_BOUNDARY_LOADER} + ${HAS_BOUNDARY_LAYOUT_NESTED_LOADER} + ${HAS_BOUNDARY_NESTED_LOADER} +
+ ); + } + `, + + [`app/routes${NO_BOUNDARY_LOADER_FILE}.jsx`]: js` + export function loader() { + throw new Response("", { status: 401 }); + } + export default function Index() { + return
; + } + `, + + [`app/routes${HAS_BOUNDARY_LAYOUT_NESTED_LOADER_FILE}.jsx`]: js` + import { useMatches } from "react-router"; + export function loader() { + return "${LAYOUT_DATA}"; + } + export default function Layout() { + return
; + } + export function ErrorBoundary() { + let matches = useMatches(); + let { loaderData } = matches.find(match => match.id === "routes${HAS_BOUNDARY_LAYOUT_NESTED_LOADER_FILE}"); + + return ( +
+
${LAYOUT_BOUNDARY_TEXT}
+
{loaderData}
+
+ ); + } + `, + + [`app/routes${HAS_BOUNDARY_LAYOUT_NESTED_LOADER_FILE}._index.jsx`]: js` + export function loader() { + throw new Response("", { status: 401 }); + } + export default function Index() { + return
; + } + `, + + [`app/routes${HAS_BOUNDARY_NESTED_LOADER_FILE}.jsx`]: js` + import { Outlet, useLoaderData } from "react-router"; + export function loader() { + return "${LAYOUT_DATA}"; + } + export default function Layout() { + let loaderData = useLoaderData(); + return ( +
+
{loaderData}
+ +
+ ); + } + `, + + [`app/routes${HAS_BOUNDARY_NESTED_LOADER_FILE}._index.jsx`]: js` + export function loader() { + throw new Response("", { status: 401 }); + } + export default function Index() { + return
; + } + export function ErrorBoundary() { + return ( +
${OWN_BOUNDARY_TEXT}
+ ); + } + `, + }, + }); + + appFixture = await createAppFixture(fixture); + }); + + test.afterAll(() => { + appFixture.close(); + }); + + test("renders root boundary with data available", async () => { + let res = await fixture.requestDocument(NO_BOUNDARY_LOADER); + expect(res.status).toBe(401); + let html = await res.text(); + expect(html).toMatch(ROOT_BOUNDARY_TEXT); + expect(html).toMatch(ROOT_DATA); + }); + + test("renders root boundary with data available on transition", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(NO_BOUNDARY_LOADER); + await page.waitForSelector("#root-boundary"); + await page.waitForSelector( + `#root-boundary-data:has-text("${ROOT_DATA}")`, + ); + }); + + test("renders layout boundary with data available", async () => { + let res = await fixture.requestDocument( + HAS_BOUNDARY_LAYOUT_NESTED_LOADER, + ); + expect(res.status).toBe(401); + let html = await res.text(); + expect(html).toMatch(ROOT_DATA); + expect(html).toMatch(LAYOUT_BOUNDARY_TEXT); + expect(html).toMatch(LAYOUT_DATA); + }); + + test("renders layout boundary with data available on transition", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(HAS_BOUNDARY_LAYOUT_NESTED_LOADER); + await page.waitForSelector(`#root-data:has-text("${ROOT_DATA}")`); + await page.waitForSelector( + `#layout-boundary:has-text("${LAYOUT_BOUNDARY_TEXT}")`, + ); + await page.waitForSelector( + `#layout-boundary-data:has-text("${LAYOUT_DATA}")`, + ); + }); + + test("renders self boundary with layout data available", async () => { + let res = await fixture.requestDocument(HAS_BOUNDARY_NESTED_LOADER); + expect(res.status).toBe(401); + let html = await res.text(); + expect(html).toMatch(ROOT_DATA); + expect(html).toMatch(LAYOUT_DATA); + expect(html).toMatch(OWN_BOUNDARY_TEXT); + }); + + test("renders self boundary with layout data available on transition", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(HAS_BOUNDARY_NESTED_LOADER); + await page.waitForSelector(`#root-data:has-text("${ROOT_DATA}")`); + await page.waitForSelector(`#layout-data:has-text("${LAYOUT_DATA}")`); + await page.waitForSelector( + `#own-boundary:has-text("${OWN_BOUNDARY_TEXT}")`, + ); + }); + }); + } +}); diff --git a/tests/react-router-framework/integration/catch-boundary-test.ts b/tests/react-router-framework/integration/catch-boundary-test.ts new file mode 100644 index 00000000..c4f2a8fe --- /dev/null +++ b/tests/react-router-framework/integration/catch-boundary-test.ts @@ -0,0 +1,379 @@ +import { test, expect } from "@playwright/test"; + +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; + +test.describe("ErrorBoundary (thrown responses)", () => { + let fixture: Fixture; + let appFixture: AppFixture; + let originalConsoleError: typeof console.error; + let originalConsoleWarn: typeof console.warn; + + let ROOT_BOUNDARY_TEXT = "ROOT_TEXT" as const; + let OWN_BOUNDARY_TEXT = "OWN_BOUNDARY_TEXT" as const; + + let HAS_BOUNDARY_LOADER = "/yes/loader" as const; + let HAS_BOUNDARY_LOADER_FILE = "/yes.loader" as const; + let HAS_BOUNDARY_ACTION = "/yes/action" as const; + let HAS_BOUNDARY_ACTION_FILE = "/yes.action" as const; + let NO_BOUNDARY_ACTION = "/no/action" as const; + let NO_BOUNDARY_ACTION_FILE = "/no.action" as const; + let NO_BOUNDARY_LOADER = "/no/loader" as const; + let NO_BOUNDARY_LOADER_FILE = "/no.loader" as const; + + let NOT_FOUND_HREF = "/not/found"; + + test.beforeAll(async () => { + fixture = await createFixture({ + files: { + "app/root.tsx": js` + import { Links, Meta, Outlet, Scripts, useMatches } from "react-router"; + + export function loader() { + return { data: "ROOT LOADER" }; + } + + export default function Root() { + return ( + + + + + + + + + + + ); + } + + export function ErrorBoundary() { + let matches = useMatches() + return ( + + + +
${ROOT_BOUNDARY_TEXT}
+
{JSON.stringify(matches)}
+ + + + ) + } + `, + + "app/routes/_index.tsx": js` + import { Link, Form } from "react-router"; + export default function() { + return ( +
+ ${NOT_FOUND_HREF} + +
+
+ ) + } + `, + + [`app/routes${HAS_BOUNDARY_ACTION_FILE}.jsx`]: js` + import { Form } from "react-router"; + export async function action() { + throw new Response("", { status: 401 }) + } + export function ErrorBoundary() { + return

${OWN_BOUNDARY_TEXT}

+ } + export default function Index() { + return ( +
+ +
+ ); + } + `, + + [`app/routes${NO_BOUNDARY_ACTION_FILE}.jsx`]: js` + import { Form } from "react-router"; + export function action() { + throw new Response("", { status: 401 }) + } + export default function Index() { + return ( +
+ +
+ ) + } + `, + + [`app/routes${HAS_BOUNDARY_LOADER_FILE}.jsx`]: js` + import { useRouteError } from "react-router"; + export function loader() { + throw new Response("", { status: 401 }) + } + export function ErrorBoundary() { + let error = useRouteError(); + return ( + <> +
${OWN_BOUNDARY_TEXT}
+
{error.status}
+ + ); + } + export default function Index() { + return
+ } + `, + + [`app/routes${HAS_BOUNDARY_LOADER_FILE}.child.jsx`]: js` + export function loader() { + throw new Response("", { status: 404 }) + } + export default function Index() { + return
+ } + `, + + [`app/routes${NO_BOUNDARY_LOADER_FILE}.jsx`]: js` + export function loader() { + throw new Response("", { status: 401 }) + } + export default function Index() { + return
+ } + `, + + "app/routes/action.tsx": js` + import { Outlet, useLoaderData } from "react-router"; + + export function loader() { + return "PARENT"; + } + + export default function () { + return ( +
+

{useLoaderData()}

+ +
+ ) + } + `, + + "app/routes/action.child-catch.tsx": js` + import { Form, useLoaderData, useRouteError } from "react-router"; + + export function loader() { + return "CHILD"; + } + + export function action() { + throw new Response("Caught!", { status: 400 }); + } + + export default function () { + return ( + <> +

{useLoaderData()}

+
+ +
+ + ) + } + + export function ErrorBoundary() { + let error = useRouteError() + return

{error.status} {error.data}

; + } + `, + }, + }); + + appFixture = await createAppFixture(fixture); + originalConsoleError = console.error; + console.error = () => {}; + originalConsoleWarn = console.warn; + console.warn = () => {}; + }); + + test.afterAll(() => { + appFixture.close(); + console.error = originalConsoleError; + console.warn = originalConsoleWarn; + }); + + test("non-matching urls on document requests", async () => { + let oldConsoleError; + oldConsoleError = console.error; + console.error = () => {}; + + let res = await fixture.requestDocument(NOT_FOUND_HREF); + expect(res.status).toBe(404); + let html = await res.text(); + expect(html).toMatch(ROOT_BOUNDARY_TEXT); + + // There should be no loader data on the root route + let expected = JSON.stringify([ + { id: "root", pathname: "", params: {} }, + ]).replace(/"/g, """); + expect(html).toContain(`
${expected}
`); + + console.error = oldConsoleError; + }); + + test("non-matching urls on client transitions", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(NOT_FOUND_HREF, { wait: false }); + await page.waitForSelector("#root-boundary"); + + // Root loader data sticks around from previous load + let expected = JSON.stringify([ + { + id: "root", + pathname: "", + params: {}, + loaderData: { data: "ROOT LOADER" }, + }, + ]); + expect(await app.getHtml("#matches")).toContain(expected); + }); + + test("own boundary, action, document request", async () => { + let params = new URLSearchParams(); + let res = await fixture.postDocument(HAS_BOUNDARY_ACTION, params); + expect(res.status).toBe(401); + expect(await res.text()).toMatch(OWN_BOUNDARY_TEXT); + }); + + test("own boundary, action, client transition from other route", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickSubmitButton(HAS_BOUNDARY_ACTION); + await page.waitForSelector("#action-boundary"); + }); + + test("own boundary, action, client transition from itself", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(HAS_BOUNDARY_ACTION); + await app.clickSubmitButton(HAS_BOUNDARY_ACTION); + await page.waitForSelector("#action-boundary"); + }); + + test("bubbles to parent in action document requests", async () => { + let params = new URLSearchParams(); + let res = await fixture.postDocument(NO_BOUNDARY_ACTION, params); + expect(res.status).toBe(401); + expect(await res.text()).toMatch(ROOT_BOUNDARY_TEXT); + }); + + test("bubbles to parent in action script transitions from other routes", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickSubmitButton(NO_BOUNDARY_ACTION); + await page.waitForSelector("#root-boundary"); + }); + + test("bubbles to parent in action script transitions from self", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(NO_BOUNDARY_ACTION); + await app.clickSubmitButton(NO_BOUNDARY_ACTION); + await page.waitForSelector("#root-boundary"); + }); + + test("own boundary, loader, document request", async () => { + let res = await fixture.requestDocument(HAS_BOUNDARY_LOADER); + expect(res.status).toBe(401); + expect(await res.text()).toMatch(OWN_BOUNDARY_TEXT); + }); + + test("own boundary, loader, client transition", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(HAS_BOUNDARY_LOADER); + await page.waitForSelector("#boundary-loader"); + }); + + test("bubbles to parent in loader document requests", async () => { + let res = await fixture.requestDocument(NO_BOUNDARY_LOADER); + expect(res.status).toBe(401); + expect(await res.text()).toMatch(ROOT_BOUNDARY_TEXT); + }); + + test("bubbles to parent in loader transitions from other routes", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(NO_BOUNDARY_LOADER); + await page.waitForSelector("#root-boundary"); + }); + + test("uses correct catch boundary on server action errors", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto(`/action/child-catch`); + expect(await app.getHtml("#parent-data")).toMatch("PARENT"); + expect(await app.getHtml("#child-data")).toMatch("CHILD"); + await page.click("button[type=submit]"); + await page.waitForSelector("#child-catch"); + // Preserves parent loader data + expect(await app.getHtml("#parent-data")).toMatch("PARENT"); + expect(await app.getHtml("#child-catch")).toMatch("400"); + expect(await app.getHtml("#child-catch")).toMatch("Caught!"); + }); + + test("prefers parent catch when child loader also bubbles, document request", async () => { + let res = await fixture.requestDocument(`${HAS_BOUNDARY_LOADER}/child`); + expect(res.status).toBe(401); + let text = await res.text(); + expect(text).toMatch(OWN_BOUNDARY_TEXT); + expect(text).toMatch('
401
'); + }); + + test("prefers parent catch when child loader also bubbles, client transition", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink(`${HAS_BOUNDARY_LOADER}/child`); + await page.waitForSelector("#boundary-loader"); + expect(await app.getHtml("#boundary-loader")).toMatch(OWN_BOUNDARY_TEXT); + expect(await app.getHtml("#status")).toMatch("401"); + }); +}); diff --git a/tests/react-router-framework/integration/cli-test.ts b/tests/react-router-framework/integration/cli-test.ts new file mode 100644 index 00000000..70e73554 --- /dev/null +++ b/tests/react-router-framework/integration/cli-test.ts @@ -0,0 +1,201 @@ +import { spawnSync } from "node:child_process"; +import { existsSync, rmSync } from "node:fs"; +import * as path from "node:path"; + +import { expect, test } from "@playwright/test"; +import dedent from "dedent"; +import semver from "semver"; + +import { createProject } from "./helpers/vite"; + +const nodeBin = process.argv[0]; +const reactRouterBin = "node_modules/@react-router/dev/dist/cli/index.js"; + +const run = (command: string[], options: Parameters[2]) => + spawnSync(nodeBin, [reactRouterBin, ...command], options); + +const helpText = dedent` + react-router + + Usage: + $ react-router build [projectDir] + $ react-router dev [projectDir] + $ react-router routes [projectDir] + + Options: + --help, -h Print this help message and exit + --version, -v Print the CLI version and exit + --no-color Disable ANSI colors in console output + \`build\` Options: + --assetsInlineLimit Static asset base64 inline threshold in bytes (default: 4096) (number) + --clearScreen Allow/disable clear screen when logging (boolean) + --config, -c Use specified config file (string) + --emptyOutDir Force empty outDir when it's outside of root (boolean) + --logLevel, -l Info | warn | error | silent (string) + --minify Enable/disable minification, or specify minifier to use (default: "esbuild") (boolean | "terser" | "esbuild") + --mode, -m Set env mode (string) + --profile Start built-in Node.js inspector + --sourcemapClient Output source maps for client build (default: false) (boolean | "inline" | "hidden") + --sourcemapServer Output source maps for server build (default: false) (boolean | "inline" | "hidden") + \`dev\` Options: + --clearScreen Allow/disable clear screen when logging (boolean) + --config, -c Use specified config file (string) + --cors Enable CORS (boolean) + --force Force the optimizer to ignore the cache and re-bundle (boolean) + --host Specify hostname (string) + --logLevel, -l Info | warn | error | silent (string) + --mode, -m Set env mode (string) + --open Open browser on startup (boolean | string) + --port Specify port (number) + --profile Start built-in Node.js inspector + --strictPort Exit if specified port is already in use (boolean) + \`routes\` Options: + --config, -c Use specified Vite config file (string) + --json Print the routes as JSON + \`reveal\` Options: + --config, -c Use specified Vite config file (string) + --no-typescript Generate plain JavaScript files + \`typegen\` Options: + --watch Automatically regenerate types whenever route config (\`routes.ts\`) or route modules change + + Build your project: + + $ react-router build + + Run your project locally in development: + + $ react-router dev + + Show all routes in your app: + + $ react-router routes + $ react-router routes my-app + $ react-router routes --json + $ react-router routes --config vite.react-router.config.ts + + Reveal the used entry point: + + $ react-router reveal entry.client + $ react-router reveal entry.server + $ react-router reveal entry.client --no-typescript + $ react-router reveal entry.server --no-typescript + $ react-router reveal entry.server --config vite.react-router.config.ts + + Generate types for route modules: + + $ react-router typegen + $ react-router typegen --watch +`; + +test.describe("cli", () => { + test("--help", async () => { + const cwd = await createProject(); + const { stdout, stderr, status } = run(["--help"], { + cwd, + env: { + NO_COLOR: "1", + }, + }); + expect(stdout.toString().trim()).toBe(helpText); + expect(stderr.toString()).toBe(""); + expect(status).toBe(0); + }); + + test("--version", async () => { + const cwd = await createProject(); + let { stdout, stderr, status } = run(["--version"], { cwd }); + expect(semver.valid(stdout.toString().trim())).not.toBeNull(); + expect(stderr.toString()).toBe(""); + expect(status).toBe(0); + }); + + test("routes", async () => { + const cwd = await createProject(); + let { stdout, stderr, status } = run(["routes"], { cwd }); + + // Filter out future flag warnings for the format: + // ⚠️ Future Flag Warning: [Something] is changing in React Router v8. + // You can use the `future.v8_[whatever]` flag to opt in early. + // -> https://reactrouter.com/upgrading/future-flags#v8_[whatever] + let filteredStdOut = stdout.toString().split("\n"); + while (filteredStdOut[0]?.includes("Future Flag Warning:")) { + filteredStdOut.splice(0, 3); + } + + expect(filteredStdOut.join("\n").trim()).toBe(dedent` + + + + + + `); + expect(stderr.toString()).toBe(""); + expect(status).toBe(0); + }); + + test.describe("reveal", async () => { + test("generates entry.{server,client}.tsx in the app directory", async () => { + const cwd = await createProject(); + let entryClientFile = path.join(cwd, "app", "entry.client.tsx"); + let entryServerFile = path.join(cwd, "app", "entry.server.tsx"); + + expect(existsSync(entryServerFile)).toBeFalsy(); + expect(existsSync(entryClientFile)).toBeFalsy(); + + run(["reveal"], { cwd }); + + expect(existsSync(entryServerFile)).toBeTruthy(); + expect(existsSync(entryClientFile)).toBeTruthy(); + }); + + test("rsc generates entry.{ssr,rsc,client}.tsx in the app directory", async () => { + const cwd = await createProject({}, "rsc-vite-framework"); + let entrySSRFile = path.join(cwd, "app", "entry.ssr.tsx"); + let entryRSCFile = path.join(cwd, "app", "entry.rsc.tsx"); + let entryClientFile = path.join(cwd, "app", "entry.client.tsx"); + + expect(existsSync(entrySSRFile)).toBeFalsy(); + expect(existsSync(entryRSCFile)).toBeFalsy(); + expect(existsSync(entryClientFile)).toBeFalsy(); + + run(["reveal"], { cwd }); + + expect(existsSync(entrySSRFile)).toBeTruthy(); + expect(existsSync(entryRSCFile)).toBeTruthy(); + expect(existsSync(entryClientFile)).toBeTruthy(); + }); + + test("generates specified entries in the app directory", async () => { + const cwd = await createProject(); + + let entryClientFile = path.join(cwd, "app", "entry.client.tsx"); + let entryServerFile = path.join(cwd, "app", "entry.server.tsx"); + + expect(existsSync(entryServerFile)).toBeFalsy(); + expect(existsSync(entryClientFile)).toBeFalsy(); + + run(["reveal", "entry.server"], { cwd }); + expect(existsSync(entryServerFile)).toBeTruthy(); + expect(existsSync(entryClientFile)).toBeFalsy(); + rmSync(entryServerFile); + + run(["reveal", "entry.client"], { cwd }); + expect(existsSync(entryClientFile)).toBeTruthy(); + expect(existsSync(entryServerFile)).toBeFalsy(); + }); + + test("generates entry.{server,client}.jsx in the app directory with --no-typescript", async () => { + const cwd = await createProject(); + let entryClientFile = path.join(cwd, "app", "entry.client.jsx"); + let entryServerFile = path.join(cwd, "app", "entry.server.jsx"); + + expect(existsSync(entryServerFile)).toBeFalsy(); + expect(existsSync(entryClientFile)).toBeFalsy(); + + run(["reveal", "--no-typescript"], { cwd }); + + expect(existsSync(entryServerFile)).toBeTruthy(); + expect(existsSync(entryClientFile)).toBeTruthy(); + }); + }); +}); diff --git a/tests/react-router-framework/integration/client-data-test.ts b/tests/react-router-framework/integration/client-data-test.ts new file mode 100644 index 00000000..aa9b3777 --- /dev/null +++ b/tests/react-router-framework/integration/client-data-test.ts @@ -0,0 +1,1772 @@ +import { test, expect } from "@playwright/test"; + +import { UNSAFE_ServerMode as ServerMode } from "react-router"; +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; +import type { AppFixture } from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import { type TemplateName, reactRouterConfig } from "./helpers/vite.js"; + +const templateNames = [ + "vite-7-template", + "rsc-vite-framework", +] as const satisfies TemplateName[]; + +test.describe("Client Data", () => { + for (const templateName of templateNames) { + function getFiles( + routeBaseFileName: string, + { + parentClientLoader, + parentClientLoaderHydrate, + parentAdditions, + childClientLoader, + childClientLoaderHydrate, + childAdditions, + }: { + parentClientLoader: boolean; + parentClientLoaderHydrate: boolean; + parentAdditions?: string; + childClientLoader: boolean; + childClientLoaderHydrate: boolean; + childAdditions?: string; + }, + ) { + return { + [`app/routes/${routeBaseFileName}.parent.tsx`]: js` + import { Outlet, useLoaderData } from "react-router" + export function loader() { + return { message: 'Parent Server Loader' }; + } + ${ + parentClientLoader + ? js` + export async function clientLoader({ serverLoader }) { + // Need a small delay to ensure we capture the server-rendered + // fallbacks for assertions + await new Promise(r => setTimeout(r, 100)) + let data = await serverLoader(); + return { message: data.message + " (mutated by client)" }; + } + ` + : "" + } + ${ + parentClientLoaderHydrate + ? js` + clientLoader.hydrate = true; + export function HydrateFallback() { + return

Parent Fallback

+ } + ` + : "" + } + ${parentAdditions || ""} + export default function Component() { + let data = useLoaderData(); + return ( + <> +

{data.message}

+ + + ); + } + `, + [`app/routes/${routeBaseFileName}.parent.child.tsx`]: js` + import { Form, Outlet, useActionData, useLoaderData } from "react-router" + export function loader() { + return { message: 'Child Server Loader' }; + } + export function action() { + return { message: 'Child Server Action' }; + } + ${ + childClientLoader + ? js` + export async function clientLoader({ serverLoader }) { + // Need a small delay to ensure we capture the server-rendered + // fallbacks for assertions + await new Promise(r => setTimeout(r, 100)) + let data = await serverLoader(); + return { message: data.message + " (mutated by client)" }; + } + ` + : "" + } + ${ + childClientLoaderHydrate + ? js` + clientLoader.hydrate = true; + export function HydrateFallback() { + return

Child Fallback

+ } + ` + : "" + } + ${childAdditions || ""} + export default function Component() { + let data = useLoaderData(); + let actionData = useActionData(); + return ( + <> +

{data.message}

+
+ + {actionData ?

{actionData.message}

: null} +
+ + ); + } + `, + }; + } + + test.describe(`template: ${templateName}`, () => { + for (const splitRouteModules of [true, false]) { + test.describe(`splitRouteModules: ${splitRouteModules}`, () => { + test.skip( + templateName.includes("rsc") && splitRouteModules, + "RSC Framework Mode doesn't support splitRouteModules", + ); + + test.skip( + ({ browserName }) => + Boolean(process.env.CI) && + splitRouteModules && + (browserName === "webkit" || process.platform === "win32"), + "Webkit/Windows tests only run on a single worker in CI and splitRouteModules is not OS/browser-specific", + ); + + let appFixture: AppFixture; + + test.beforeAll(async () => { + appFixture = await createAppFixture( + await createFixture( + { + templateName, + files: { + "react-router.config.ts": reactRouterConfig({ + splitRouteModules, + }), + "app/root.tsx": js` + import { Form, Outlet, Scripts } from "react-router" + + export const middleware = [ + async ({ request }, next) => { + let response = await next(); + + if ( + request.method === "GET" && + response instanceof Response && + response.status === 200 && + request.headers.get("sec-purpose") === "prefetch" && + !response.headers.has("Cache-Control") + ) { + let cachedResponse = new Response(response.body, response); + cachedResponse.headers.set("Cache-Control", "max-age=5"); + return cachedResponse; + } + return response; + } + ]; + + export default function Root() { + return ( + + + +
+ +
+ + + + ); + } + `, + "app/routes/_index.tsx": js` + import { Link } from "react-router" + export default function Component() { + return ( +
    +
  • /client-loader-lazy/no-client-loaders-or-fallbacks/parent/child
  • +
  • /client-loader-lazy/parent-client-loader/parent/child
  • +
  • /client-loader-lazy/child-client-loader/parent/child
  • +
  • /client-loader-lazy/parent-client-loader-child-client-loader/parent/child
  • +
  • /client-loader-lazy/throws-a-400-if-you-call-serverloader-without-a-server-loader/parent/child
  • +
  • /client-loader-lazy/does-not-prefetch-server-loader-if-a-client-loader-is-present/parent
  • +
  • /client-loader-lazy/does-not-prefetch-server-loader-if-a-client-loader-is-present/parent/child
  • +
  • /client-action-lazy/child-client-action/parent/child
  • +
  • /client-action-lazy/child-client-action-parent-child-loader/parent/child
  • +
  • /client-action-lazy/child-client-action-child-client-loader/parent/child
  • +
  • /client-action-lazy/child-client-action-parent-child-loader-child-client-loader/parent/child
  • +
  • /client-action-lazy/throws-a-400-if-you-call-serveraction-without-a-server-action/parent/child
  • +
+ ); + } + `, + + ...getFiles( + "client-loader-critical.no-client-loaders-or-fallbacks", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + + ...getFiles( + "client-loader-critical.parent-client-loader-child-client-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + }, + ), + + ...getFiles( + "client-loader-critical.parent-client-loader-hydrate-child-client-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: true, + childClientLoader: true, + childClientLoaderHydrate: false, + }, + ), + + ...getFiles( + "client-loader-critical.parent-client-loader-child-client-loader-hydrate", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: true, + }, + ), + + ...getFiles( + "client-loader-critical.parent-client-loader-child-client-loader-hydrate-both", + { + parentClientLoader: true, + parentClientLoaderHydrate: true, + childClientLoader: true, + childClientLoaderHydrate: true, + }, + ), + + ...getFiles( + "client-loader-critical.handles-synchronous-client-loaders", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + parentAdditions: js` + export function clientLoader() { + return { message: "Parent Client Loader" }; + } + clientLoader.hydrate=true + export function HydrateFallback() { + return

Parent Fallback

+ } + `, + childAdditions: js` + export function clientLoader() { + return { message: "Child Client Loader" }; + } + clientLoader.hydrate=true + `, + }, + ), + + ...getFiles( + "client-loader-critical.handles-deferred-data-through-client-loaders", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.handles-deferred-data-through-client-loaders.parent.child.tsx": js` + import * as React from 'react'; + import { Await, useLoaderData } from "react-router" + export function loader() { + return { + message: 'Child Server Loader', + lazy: new Promise(r => setTimeout(() => r("Child Deferred Data"), 1000)), + }; + } + export async function clientLoader({ serverLoader }) { + let data = await serverLoader(); + return { + ...data, + message: data.message + " (mutated by client)", + }; + } + clientLoader.hydrate = true; + export function HydrateFallback() { + return

Child Fallback

+ } + export default function Component() { + let data = useLoaderData(); + return ( + <> +

{data.message}

+ Loading Deferred Data...

}> + + {(value) =>

{value}

} +
+
+ + ); + } + `, + + ...getFiles( + "client-loader-critical.allows-hydration-without-rendering-a-fallback", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientLoader() { + await new Promise(r => setTimeout(r, 100)); + return { message: "Child Client Loader" }; + } + clientLoader.hydrate=true + `, + }, + ), + + ...getFiles( + "client-loader-critical.hydrate-fallback-not-rendered-if-not-set-with-server-loader", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.hydrate-fallback-not-rendered-if-not-set-with-server-loader.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData } from "react-router"; + export function loader() { + return { message: "Child Server Loader Data" }; + } + export async function clientLoader({ serverLoader }) { + await new Promise(r => setTimeout(r, 100)); + return { message: "Child Client Loader Data" }; + } + export function HydrateFallback() { + return

SHOULD NOT SEE ME

+ } + export default function Component() { + let data = useLoaderData(); + return

{data.message}

; + } + `, + + ...getFiles( + "client-loader-critical.client-loader-hydrate-is-automatically-implied-when-no-server-loader-exists-with-hydrate-fallback", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.client-loader-hydrate-is-automatically-implied-when-no-server-loader-exists-with-hydrate-fallback.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData } from "react-router"; + // Even without setting hydrate=true, this should run on hydration + export async function clientLoader({ serverLoader }) { + await new Promise(r => setTimeout(r, 100)); + return { + message: "Loader Data (clientLoader only)", + }; + } + export function HydrateFallback() { + return

Child Fallback

+ } + export default function Component() { + let data = useLoaderData(); + return

{data.message}

; + } + `, + + ...getFiles( + "client-loader-critical.client-loader-hydrate-is-automatically-implied-when-no-server-loader-exists-without-hydrate-fallback", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.client-loader-hydrate-is-automatically-implied-when-no-server-loader-exists-without-hydrate-fallback.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData } from "react-router"; + // Even without setting hydrate=true, this should run on hydration + export async function clientLoader({ serverLoader }) { + await new Promise(r => setTimeout(r, 100)); + return { + message: "Loader Data (clientLoader only)", + }; + } + export default function Component() { + let data = useLoaderData(); + return

{data.message}

; + } + `, + + ...getFiles( + "client-loader-critical.throws-a-400-if-you-call-serverloader-without-a-server-loader", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.throws-a-400-if-you-call-serverloader-without-a-server-loader.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData, useRouteError } from "react-router"; + export async function clientLoader({ serverLoader }) { + return await serverLoader(); + } + export default function Component() { + return

Child

; + } + export function HydrateFallback() { + return

Loading...

; + } + export function ErrorBoundary() { + let error = useRouteError(); + return

{error.status} {error.data}

; + } + `, + + ...getFiles( + "client-loader-critical.initial-hydration-data-check-functions-properly", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.initial-hydration-data-check-functions-properly.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData, useRevalidator } from "react-router"; + let isFirstCall = true; + export async function loader({ serverLoader }) { + if (isFirstCall) { + isFirstCall = false + return { message: "Child Server Loader Data (1)" }; + } + return { message: "Child Server Loader Data (2+)" }; + } + export async function clientLoader({ serverLoader }) { + await new Promise(r => setTimeout(r, 100)); + let serverData = await serverLoader(); + return { + message: serverData.message + " (mutated by client)", + }; + } + clientLoader.hydrate=true; + export default function Component() { + let data = useLoaderData(); + let revalidator = useRevalidator(); + return ( + <> +

{data.message}

+ + + ); + } + export function HydrateFallback() { + return

Loading...

+ } + `, + + ...getFiles( + "client-loader-critical.initial-hydration-data-check-functions-properly-even-if-serverloader-isnt-called-on-hydration", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.initial-hydration-data-check-functions-properly-even-if-serverloader-isnt-called-on-hydration.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData, useRevalidator } from "react-router"; + let isFirstCall = true; + export async function loader({ serverLoader }) { + if (isFirstCall) { + isFirstCall = false + return { message: "Child Server Loader Data (1)" }; + } + return { message: "Child Server Loader Data (2+)" }; + } + let isFirstClientCall = true; + export async function clientLoader({ serverLoader }) { + await new Promise(r => setTimeout(r, 100)); + if (isFirstClientCall) { + isFirstClientCall = false; + // First time through - don't even call serverLoader + return { + message: "Child Client Loader Data", + }; + } + // Only call the serverLoader on subsequent calls and this + // should *not* return us the initialData any longer + let serverData = await serverLoader(); + return { + message: serverData.message + " (mutated by client)", + }; + } + clientLoader.hydrate=true; + export default function Component() { + let data = useLoaderData(); + let revalidator = useRevalidator(); + return ( + <> +

{data.message}

+ + + ); + } + export function HydrateFallback() { + return

Loading...

+ } + `, + + ...getFiles( + "client-loader-critical.server-loader-errors-are-re-thrown-from-serverloader", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.server-loader-errors-are-re-thrown-from-serverloader.parent.child.tsx": js` + import { useRouteError } from "react-router"; + + export function loader() { + throw new Error("Broken!") + } + + export async function clientLoader({ serverLoader }) { + return await serverLoader(); + } + clientLoader.hydrate = true; + + export default function Index() { + return

Should not see me

; + } + + export function ErrorBoundary() { + let error = useRouteError(); + return

{error.message}

; + } + `, + + ...getFiles( + "client-loader-critical.bubbled-server-loader-errors-are-persisted-for-hydrating-routes", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-critical.bubbled-server-loader-errors-are-persisted-for-hydrating-routes.parent.tsx": js` + import { Outlet, useLoaderData, useRouteLoaderData, useRouteError } from 'react-router' + export function loader() { + return { message: 'Parent Server Loader' }; + } + export async function clientLoader({ serverLoader }) { + console.log('running parent client loader') + // Need a small delay to ensure we capture the server-rendered + // fallbacks for assertions + await new Promise(r => setTimeout(r, 100)); + let data = await serverLoader(); + return { message: data.message + " (mutated by client)" }; + } + clientLoader.hydrate = true; + export default function Component() { + let data = useLoaderData(); + return ( + <> +

{data.message}

+ + + ); + } + export function ErrorBoundary() { + let data = useRouteLoaderData("routes/client-loader-critical.bubbled-server-loader-errors-are-persisted-for-hydrating-routes.parent") + let error = useRouteError(); + return ( + <> +

Parent Error

+

{data?.message}

+

{error?.message}

+ + ); + } + `, + "app/routes/client-loader-critical.bubbled-server-loader-errors-are-persisted-for-hydrating-routes.parent.child.tsx": js` + import { useLoaderData } from 'react-router' + export function loader() { + throw new Error('Child Server Error'); + } + export function clientLoader() { + console.log('running child client loader') + return "Should not see me"; + } + clientLoader.hydrate = true; + export default function Component() { + let data = useLoaderData() + return ( + <> +

Should not see me

+

{data}

; + + ); + } + `, + + ...getFiles( + "client-loader-lazy.no-client-loaders-or-fallbacks", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + + ...getFiles("client-loader-lazy.parent-client-loader", { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }), + + ...getFiles("client-loader-lazy.child-client-loader", { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + }), + + ...getFiles( + "client-loader-lazy.parent-client-loader-child-client-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + }, + ), + + ...getFiles( + "client-loader-lazy.throws-a-400-if-you-call-serverloader-without-a-server-loader", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-loader-lazy.throws-a-400-if-you-call-serverloader-without-a-server-loader.parent.child.tsx": js` + import * as React from 'react'; + import { useLoaderData, useRouteError } from "react-router"; + export async function clientLoader({ serverLoader }) { + return await serverLoader(); + } + export default function Component() { + return

Child

; + } + export function HydrateFallback() { + return

Loading...

; + } + export function ErrorBoundary() { + let error = useRouteError(); + return

{error.status} {error.data}

; + } + `, + + ...getFiles( + "client-loader-lazy.does-not-prefetch-server-loader-if-a-client-loader-is-present", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + + ...getFiles("client-action-critical.child-client-action", { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }), + + ...getFiles( + "client-action-critical.child-client-action-parent-child-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }, + ), + + ...getFiles( + "client-action-critical.child-client-action-child-client-loader", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }, + ), + + ...getFiles( + "client-action-critical.child-client-action-parent-child-loader-child-client-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }, + ), + + ...getFiles( + "client-action-critical.throws-a-400-if-you-call-serveraction-without-a-server-action", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-action-critical.throws-a-400-if-you-call-serveraction-without-a-server-action.parent.child.tsx": js` + import * as React from 'react'; + import { Form, useRouteError } from "react-router"; + export async function clientAction({ serverAction }) { + return await serverAction(); + } + export default function Component() { + return ( +
+ +
+ ); + } + export function ErrorBoundary() { + let error = useRouteError(); + return

{error.status} {error.data}

; + } + `, + + ...getFiles("client-action-lazy.child-client-action", { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }), + + ...getFiles( + "client-action-lazy.child-client-action-parent-child-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }, + ), + + ...getFiles( + "client-action-lazy.child-client-action-child-client-loader", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }, + ), + + ...getFiles( + "client-action-lazy.child-client-action-parent-child-loader-child-client-loader", + { + parentClientLoader: true, + parentClientLoaderHydrate: false, + childClientLoader: true, + childClientLoaderHydrate: false, + childAdditions: js` + export async function clientAction({ serverAction }) { + let data = await serverAction(); + return { + message: data.message + " (mutated by client)" + } + } + `, + }, + ), + + ...getFiles( + "client-action-lazy.throws-a-400-if-you-call-serveraction-without-a-server-action", + { + parentClientLoader: false, + parentClientLoaderHydrate: false, + childClientLoader: false, + childClientLoaderHydrate: false, + }, + ), + "app/routes/client-action-lazy.throws-a-400-if-you-call-serveraction-without-a-server-action.parent.child.tsx": js` + import * as React from 'react'; + import { Form, useRouteError } from "react-router"; + export async function clientAction({ serverAction }) { + return await serverAction(); + } + export default function Component() { + return ( +
+ +
+ ); + } + export function ErrorBoundary() { + let error = useRouteError(); + return

{error.status} {error.data}

; + } + `, + + "app/routes/client-loader-critical.hydrating-clientloader-redirects-trigger-new-data-requests-to-the-server.tsx": js` + import { Outlet } from 'react-router' + + let count = 1; + export function loader() { + return count++; + } + export default function Component({ loaderData }) { + return ( + <> +

{loaderData}

+ + + ); + } + `, + + "app/routes/client-loader-critical.hydrating-clientloader-redirects-trigger-new-data-requests-to-the-server.parent.tsx": js` + import { Outlet } from 'react-router' + let count = 1; + export function loader() { + return count++; + } + export default function Component({ loaderData }) { + return ( + <> +

{loaderData}

+ + + ); + } + export function shouldRevalidate() { + return false; + } + `, + "app/routes/client-loader-critical.hydrating-clientloader-redirects-trigger-new-data-requests-to-the-server.parent.a.tsx": js` + import { redirect } from 'react-router' + export function clientLoader() { + return redirect('/client-loader-critical/hydrating-clientloader-redirects-trigger-new-data-requests-to-the-server/parent/b'); + } + clientLoader.hydrate = true; + export default function Component({ loaderData }) { + return

Should not see me

; + } + `, + "app/routes/client-loader-critical.hydrating-clientloader-redirects-trigger-new-data-requests-to-the-server.parent.b.tsx": js` + export default function Component({ loaderData }) { + return

Hi!

; + } + `, + + "app/routes/client-loader-critical.aborted-hydration-fetches-fresh-data.tsx": js` + import { Link } from "react-router"; + + export function loader({ request }) { + return { query: new URL(request.url).searchParams.get("q") || "empty" }; + } + + export async function clientLoader({ serverLoader, request }) { + let q = new URL(request.url).searchParams.get("q") || "empty"; + + // Delay the initial invocation + if (q === "initial") { + if (!window.__hydrationBlock) { + let { promise, resolve } = Promise.withResolvers(); + window.__resolveHydrationBlock = resolve + window.__hydrationBlock = promise; + await window.__hydrationBlock; + } + } + + let serverData = await serverLoader(); + return { + ...serverData, + clientLoaderRan: true, + clientLoaderQuery: q, + }; + } + + clientLoader.hydrate = true; + + export default function Component({ loaderData }) { + return ( +
+

{loaderData.query}

+

{String(loaderData.clientLoaderQuery ?? "none")}

+ + Update query + +
+ ); + } + `, + }, + }, + ServerMode.Development, // Avoid error sanitization + ), + ServerMode.Development, // Avoid error sanitization + ); + }); + + test.afterAll(() => { + appFixture?.close(); + }); + + test.describe("clientLoader - critical route module", () => { + test("no client loaders or fallbacks", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + + // Full SSR - normal loader behavior due to lack of clientLoader + await app.goto( + "/client-loader-critical/no-client-loaders-or-fallbacks/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + }); + + test("parent.clientLoader/child.clientLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + + // Full SSR - normal loader behavior due to lack of HydrateFallback components + await app.goto( + "/client-loader-critical/parent-client-loader-child-client-loader/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + }); + + test("parent.clientLoader.hydrate/child.clientLoader", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/parent-client-loader-hydrate-child-client-loader/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Fallback"); + expect(html).not.toMatch("Parent Server Loader"); + expect(html).not.toMatch("Child Server Loader"); + + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).not.toMatch("Parent Fallback"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader"); + }); + + test("parent.clientLoader/child.clientLoader.hydrate", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/parent-client-loader-child-client-loader-hydrate/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Fallback"); + expect(html).not.toMatch("Child Server Loader"); + + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).not.toMatch("Child Fallback"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + }); + + test("parent.clientLoader.hydrate/child.clientLoader.hydrate", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/parent-client-loader-child-client-loader-hydrate-both/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Fallback"); + expect(html).not.toMatch("Parent Server Loader"); + expect(html).not.toMatch("Child Fallback"); + expect(html).not.toMatch("Child Server Loader"); + + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).not.toMatch("Parent Fallback"); + expect(html).not.toMatch("Child Fallback"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + }); + + test("handles synchronous client loaders", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + + // Ensure we SSR the fallbacks + let response = await app.goto( + "/client-loader-critical/handles-synchronous-client-loaders/parent/child", + ); + let html = await response?.text(); + expect(html).toMatch("Parent Fallback"); + + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Client Loader"); + expect(html).toMatch("Child Client Loader"); + }); + + test("handles deferred data through client loaders", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + // Ensure initial document request contains the child fallback _and_ the + // subsequent streamed/resolved deferred data + let response = await app.goto( + "/client-loader-critical/handles-deferred-data-through-client-loaders/parent/child", + ); + let html = await response?.text(); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Fallback"); + expect(html).toMatch("Child Deferred Data"); + + await page.waitForSelector("#child-deferred-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + // app.goto() doesn't resolve until the document finishes loading so by + // then the HTML has updated via the streamed suspense updates + expect(html).toMatch("Child Server Loader (mutated by client)"); + expect(html).toMatch("Child Deferred Data"); + }); + + test("allows hydration execution without rendering a fallback", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto( + "/client-loader-critical/allows-hydration-without-rendering-a-fallback/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Child Server Loader"); + await page.waitForSelector(':has-text("Child Client Loader")'); + html = await app.getHtml("main"); + expect(html).toMatch("Child Client Loader"); + }); + + test("HydrateFallback is not rendered if clientLoader.hydrate is not set (w/server loader)", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + // Ensure initial document request contains the child fallback _and_ the + // subsequent streamed/resolved deferred data + let response = await app.goto( + "/client-loader-critical/hydrate-fallback-not-rendered-if-not-set-with-server-loader/parent/child", + ); + let html = await response?.text(); + expect(html).toMatch("Child Server Loader Data"); + expect(html).not.toMatch("SHOULD NOT SEE ME"); + + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Child Server Loader Data"); + }); + + test("clientLoader.hydrate is automatically implied when no server loader exists (w HydrateFallback)", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/client-loader-hydrate-is-automatically-implied-when-no-server-loader-exists-with-hydrate-fallback/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Child Fallback"); + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Loader Data (clientLoader only)"); + }); + + test("clientLoader.hydrate is automatically implied when no server loader exists (w/o HydrateFallback)", async ({ + page, + }) => { + test.skip( + templateName.includes("rsc"), + "RSC Framework Mode doesn't need to provide a default root HydrateFallback since it doesn't need to ensure is rendered, and you already get a console warning", + ); + + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/client-loader-hydrate-is-automatically-implied-when-no-server-loader-exists-without-hydrate-fallback/parent/child", + ); + let html = await app.getHtml(); + // Production builds strip dev-only warning logs, but we should + // still render the default root loading shell until hydration runs. + expect(html).toMatch("Loading..."); + expect(html).not.toMatch("child-data"); + await page.waitForSelector("#child-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Loader Data (clientLoader only)"); + }); + + test("throws a 400 if you call serverLoader without a server loader", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/throws-a-400-if-you-call-serverloader-without-a-server-loader/parent/child", + ); + await page.waitForSelector("#child-error"); + let html = await app.getHtml("#child-error"); + expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch( + "400 Error: You are trying to call serverLoader() on a route that does " + + 'not have a server loader (routeId: "routes/client-loader-critical.throws-a-400-if-you-call-serverloader-without-a-server-loader.parent.child")', + ); + }); + + test("initial hydration data check functions properly", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/initial-hydration-data-check-functions-properly/parent/child", + ); + await page.waitForSelector("#child-data"); + let html = await app.getHtml(); + expect(html).toMatch( + "Child Server Loader Data (1) (mutated by client)", + ); + app.clickElement("button"); + await page.waitForSelector( + ':has-text("Child Server Loader Data (2+)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch( + "Child Server Loader Data (2+) (mutated by client)", + ); + }); + + test("initial hydration data check functions properly even if serverLoader isn't called on hydration", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/initial-hydration-data-check-functions-properly-even-if-serverloader-isnt-called-on-hydration/parent/child", + ); + await page.waitForSelector("#child-data"); + let html = await app.getHtml(); + expect(html).toMatch("Child Client Loader Data"); + app.clickElement("button"); + await page.waitForSelector( + ':has-text("Child Server Loader Data (2+)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch( + "Child Server Loader Data (2+) (mutated by client)", + ); + }); + + test("server loader errors are re-thrown from serverLoader()", async ({ + page, + }) => { + let _consoleError = console.error; + console.error = () => {}; + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/server-loader-errors-are-re-thrown-from-serverloader/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Broken!"); + // Ensure we hydrate and remain on the boundary + await new Promise((r) => setTimeout(r, 100)); + html = await app.getHtml("main"); + expect(html).toMatch("Broken!"); + expect(html).not.toMatch("Should not see me"); + console.error = _consoleError; + }); + + test("bubbled server loader errors are persisted for hydrating routes", async ({ + page, + }) => { + // test.skip(browserName === "firefox", "this test fails there due to extra debug logs.") + let _consoleError = console.error; + console.error = () => {}; + let app = new PlaywrightFixture(appFixture, page); + let logs: string[] = []; + page.on("console", (msg) => { + let text = msg.text(); + // Firefox surfaces React performance track labels on the console + // during hydration, so only capture the application log this + // assertion actually cares about. + if (text === "running parent client loader") { + logs.push(text); + } + }); + await app.goto( + "/client-loader-critical/bubbled-server-loader-errors-are-persisted-for-hydrating-routes/parent/child", + false, + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader

"); + expect(html).toMatch("Child Server Error"); + expect(html).not.toMatch("Should not see me"); + // Ensure we hydrate and remain on the boundary + await page.waitForSelector( + ":has-text('Parent Server Loader (mutated by client)')", + ); + html = await app.getHtml("main"); + expect(html).toMatch( + "Parent Server Loader (mutated by client)

", + ); + expect(html).toMatch("Child Server Error"); + expect(html).not.toMatch("Should not see me"); + expect(logs).toEqual(["running parent client loader"]); + console.error = _consoleError; + }); + + test("hydrating clientLoader redirects trigger new data requests to the server", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/hydrating-clientloader-redirects-trigger-new-data-requests-to-the-server/parent/a", + ); + await page.waitForSelector("#b"); + // 1st route parent re-runs + await expect(page.locator("#parent-1-data")).toHaveText("2"); + // But 2nd parent opted out of revalidation + await expect(page.locator("#parent-2-data")).toHaveText("1"); + await expect(page.locator("#b")).toHaveText("Hi!"); + }); + + // When a same-route navigation aborts the pending hydration + // POP, serverLoader() must fetch fresh data — not return the + // stale SSR initialData captured for the original URL. + test("serverLoader() fetches fresh data when a same-route navigation aborts hydration", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto( + "/client-loader-critical/aborted-hydration-fetches-fresh-data?q=initial", + ); + + // SSR shows the server loader's data; clientLoader hasn't completed yet + await expect(page.locator("[data-server-query]")).toHaveText( + "initial", + ); + await expect( + page.locator("[data-client-loader-query]"), + ).toHaveText("none"); + + // Click before hydration completes to abort the hydration clientLoader call before it calls serverLoader + await app.clickLink( + "/client-loader-critical/aborted-hydration-fetches-fresh-data?q=updated", + { wait: false }, + ); + + await page.waitForURL(/q=updated/); + + // PUSH ran the clientLoader as call #2 and saw the new URL and the serverLoader + // invocation doesn't return hydrationData + await expect(page.locator("[data-server-query]")).toHaveText( + "updated", + ); + await expect( + page.locator("[data-client-loader-query]"), + ).toHaveText("updated"); + + // Release the still-pending hydration call so it can unwind. + await page.evaluate(() => + (window as any).__resolveHydrationBlock(), + ); + + await expect(page.locator("[data-server-query]")).toHaveText( + "updated", + ); + await expect( + page.locator("[data-client-loader-query]"), + ).toHaveText("updated"); + }); + }); + + test.describe("clientLoader - lazy route module", () => { + test("no client loaders or fallbacks", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-loader-lazy/no-client-loaders-or-fallbacks/parent/child", + ); + await page.waitForSelector("#child-data"); + + // Normal Remix behavior due to lack of clientLoader + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + }); + + test("parent.clientLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-loader-lazy/parent-client-loader/parent/child", + ); + await page.waitForSelector("#child-data"); + + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader"); + }); + + test("child.clientLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-loader-lazy/child-client-loader/parent/child", + ); + await page.waitForSelector("#child-data"); + + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + }); + + test("parent.clientLoader/child.clientLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-loader-lazy/parent-client-loader-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-data"); + + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader (mutated by client"); + }); + + test("throws a 400 if you call serverLoader without a server loader", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + + await app.goto("/", true); + await app.clickLink( + "/client-loader-lazy/throws-a-400-if-you-call-serverloader-without-a-server-loader/parent/child", + ); + await page.waitForSelector("#child-error"); + let html = await app.getHtml("#child-error"); + expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch( + "400 Error: You are trying to call serverLoader() on a route that does " + + 'not have a server loader (routeId: "routes/client-loader-lazy.throws-a-400-if-you-call-serverloader-without-a-server-loader.parent.child")', + ); + }); + + test("does not prefetch server loader if a client loader is present", async ({ + page, + browserName, + }) => { + test.skip( + templateName.includes("rsc"), + "This test is specific to non-RSC Framework Mode", + ); + + let dataUrls: string[] = []; + page.on("request", (request) => { + let url = request.url(); + if (url.includes(".data") || url.includes(".rsc")) { + dataUrls.push(url); + } + }); + + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + + if (browserName === "webkit") { + // No prefetch support :/ + expect(dataUrls).toEqual([]); + } else { + // Only prefetch child server loader since parent has a `clientLoader` + expect(dataUrls).toEqual([ + expect.stringMatching( + /client-loader-lazy\/does-not-prefetch-server-loader-if-a-client-loader-is-present\/parent\/child\.data\?_routes=routes%2Fclient-loader-lazy\.does-not-prefetch-server-loader-if-a-client-loader-is-present\.parent\.child/, + ), + ]); + } + }); + }); + + test.describe("clientAction - critical route module", () => { + test("child.clientAction", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto( + "/client-action-critical/child-client-action/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-critical/child-client-action/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("child.clientAction/parent.childLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto( + "/client-action-critical/child-client-action-parent-child-loader/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-critical/child-client-action-parent-child-loader/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); // still revalidating + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + + await page.waitForSelector( + ':has-text("Parent Server Loader (mutated by client)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("child.clientAction/child.clientLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto( + "/client-action-critical/child-client-action-child-client-loader/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-critical/child-client-action-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); // still revalidating + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + + await page.waitForSelector( + ':has-text("Child Server Loader (mutated by client)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("child.clientAction/parent.childLoader/child.clientLoader", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto( + "/client-action-critical/child-client-action-parent-child-loader-child-client-loader/parent/child", + ); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-critical/child-client-action-parent-child-loader-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); // still revalidating + expect(html).toMatch("Child Server Loader"); // still revalidating + expect(html).toMatch("Child Server Action (mutated by client)"); + + await page.waitForSelector( + ':has-text("Child Server Loader (mutated by client)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("throws a 400 if you call serverAction without a server action", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto( + "/client-action-critical/throws-a-400-if-you-call-serveraction-without-a-server-action/parent/child", + ); + app.clickSubmitButton( + "/client-action-critical/throws-a-400-if-you-call-serveraction-without-a-server-action/parent/child", + ); + await page.waitForSelector("#child-error"); + let html = await app.getHtml("#child-error"); + expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch( + "400 Error: You are trying to call serverAction() on a route that does " + + 'not have a server action (routeId: "routes/client-action-critical.throws-a-400-if-you-call-serveraction-without-a-server-action.parent.child")', + ); + }); + }); + + test.describe("clientAction - lazy route module", () => { + test("child.clientAction", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-action-lazy/child-client-action/parent/child", + ); + await page.waitForSelector("#child-data"); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-lazy/child-client-action/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("child.clientAction/parent.childLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-action-lazy/child-client-action-parent-child-loader/parent/child", + ); + await page.waitForSelector("#child-data"); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-lazy/child-client-action-parent-child-loader/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); // still revalidating + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + + await page.waitForSelector( + ':has-text("Parent Server Loader (mutated by client)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("child.clientAction/child.clientLoader", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-action-lazy/child-client-action-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-data"); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-lazy/child-client-action-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); // still revalidating + expect(html).toMatch("Child Server Loader"); + expect(html).toMatch("Child Server Action (mutated by client)"); + + await page.waitForSelector( + ':has-text("Child Server Loader (mutated by client)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("child.clientAction/parent.childLoader/child.clientLoader", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.clickLink( + "/client-action-lazy/child-client-action-parent-child-loader-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-data"); + let html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); + expect(html).toMatch("Child Server Loader"); + expect(html).not.toMatch("Child Server Action"); + + app.clickSubmitButton( + "/client-action-lazy/child-client-action-parent-child-loader-child-client-loader/parent/child", + ); + await page.waitForSelector("#child-action-data"); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader"); // still revalidating + expect(html).toMatch("Child Server Loader"); // still revalidating + expect(html).toMatch("Child Server Action (mutated by client)"); + + await page.waitForSelector( + ':has-text("Child Server Loader (mutated by client)")', + ); + html = await app.getHtml("main"); + expect(html).toMatch("Parent Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Loader (mutated by client)"); + expect(html).toMatch("Child Server Action (mutated by client)"); + }); + + test("throws a 400 if you call serverAction without a server action", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/", true); + await app.goto( + "/client-action-lazy/throws-a-400-if-you-call-serveraction-without-a-server-action/parent/child", + ); + await page.waitForSelector("form"); + app.clickSubmitButton( + "/client-action-lazy/throws-a-400-if-you-call-serveraction-without-a-server-action/parent/child", + ); + await page.waitForSelector("#child-error"); + let html = await app.getHtml("#child-error"); + expect(html.replace(/\n/g, " ").replace(/ +/g, " ")).toMatch( + "400 Error: You are trying to call serverAction() on a route that does " + + 'not have a server action (routeId: "routes/client-action-lazy.throws-a-400-if-you-call-serveraction-without-a-server-action.parent.child")', + ); + }); + }); + }); + } + }); + } +}); diff --git a/tests/react-router-framework/integration/custom-entry-server-test.ts b/tests/react-router-framework/integration/custom-entry-server-test.ts new file mode 100644 index 00000000..ed8240b1 --- /dev/null +++ b/tests/react-router-framework/integration/custom-entry-server-test.ts @@ -0,0 +1,60 @@ +import { expect, test } from "@playwright/test"; + +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; + +let fixture: Fixture; +let appFixture: AppFixture; + +test.beforeAll(async () => { + fixture = await createFixture({ + files: { + "app/entry.server.tsx": js` + import * as React from "react"; + import { ServerRouter } from "react-router"; + import { renderToString } from "react-dom/server"; + + export default function handleRequest( + request, + responseStatusCode, + responseHeaders, + remixContext + ) { + let markup = renderToString( + + ); + responseHeaders.set("Content-Type", "text/html"); + responseHeaders.set("x-custom-header", "custom-value"); + return new Response('' + markup, { + headers: responseHeaders, + status: responseStatusCode, + }); + } + `, + "app/routes/_index.tsx": js` + export default function Index() { + return

Hello World

+ } + `, + }, + }); + + appFixture = await createAppFixture(fixture); +}); + +test.afterAll(() => { + appFixture.close(); +}); + +test("allows user specified entry.server", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + let responses = app.collectResponses((url) => url.pathname === "/"); + await app.goto("/"); + let header = await responses[0].headerValues("x-custom-header"); + expect(header).toEqual(["custom-value"]); +}); diff --git a/tests/react-router-framework/integration/deduped-route-modules-test.ts b/tests/react-router-framework/integration/deduped-route-modules-test.ts new file mode 100644 index 00000000..0a6d9056 --- /dev/null +++ b/tests/react-router-framework/integration/deduped-route-modules-test.ts @@ -0,0 +1,287 @@ +import { test, expect } from "@playwright/test"; + +import { createFixture, createAppFixture } from "./helpers/create-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import { type TemplateName, viteConfig } from "./helpers/vite.js"; + +const templateNames = [ + "vite-7-template", + "rsc-vite-framework", +] as const satisfies TemplateName[]; + +// This test ensures that code is not accidentally duplicated when a route is +// imported within user code since they're not importing one of our internal +// virtual route modules. +test.describe("Deduped route modules", () => { + for (const templateName of templateNames) { + test.describe(`template: ${templateName}`, () => { + let fixture: Fixture; + let appFixture: AppFixture; + + test.beforeAll(async () => { + fixture = await createFixture({ + templateName, + files: { + "vite.config.js": await viteConfig.basic({ + templateName, + }), + "app/routes/client-first.a.tsx": ` + import { Link } from "react-router"; + + export const customExport = (() => { + globalThis.custom_export_count = (globalThis.custom_export_count || 0) + 1; + return () => true; + })(); + + export const loader = (() => { + globalThis.loader_count = (globalThis.loader_count || 0) + 1; + return () => ({ + customExportCount: globalThis.custom_export_count, + loaderCount: globalThis.loader_count, + componentCount: globalThis.component_count, + }); + })(); + + export const clientLoader = (() => { + globalThis.client_loader_count = (globalThis.client_loader_count || 0) + 1; + return async ({ serverLoader }) => { + const loaderData = await serverLoader(); + return { + loaderCount: loaderData.loaderCount, + clientLoaderCount: globalThis.client_loader_count, + serverCustomExportCount: loaderData.customExportCount, + clientCustomExportCount: globalThis.custom_export_count, + serverComponentCount: loaderData.componentCount, + clientComponentCount: globalThis.component_count, + }; + }; + })(); + clientLoader.hydrate = true; + + const RouteA = (() => { + globalThis.component_count = (globalThis.component_count || 0) + 1; + return ({ loaderData }: Route.ComponentProps) => { + return ( + <> +

Module Count

+

Loader count: {loaderData.loaderCount}

+

Client loader count: {loaderData.clientLoaderCount}

+

Server custom export count: {loaderData.serverCustomExportCount}

+

Client custom export count: {loaderData.clientCustomExportCount}

+

Server component count: {loaderData.serverComponentCount}

+

Client component count: {loaderData.clientComponentCount}

+

Go to Route B

+ + ); + }; + })(); + + export default RouteA; + `, + "app/routes/client-first.b.tsx": ` + import { Link } from "react-router"; + + import { customExport } from "./client-first.a"; + + export default function RouteB() { + return customExport && ( + <> +

Route B

+

This route imports the route module from Route A, so could potentially cause code duplication.

+

Go to Route A

+ + ); + } + `, + + ...(templateName.includes("rsc") + ? { + "app/routes/rsc-server-first.a/route.tsx": ` + import { Link } from "react-router"; + import { ModuleCounts, clientLoader } from "./client"; + + export const customExport = (() => { + globalThis.rsc_custom_export_count = (globalThis.rsc_custom_export_count || 0) + 1; + return () => true; + })(); + + export const loader = (() => { + globalThis.rsc_loader_count = (globalThis.rsc_loader_count || 0) + 1; + return () => ({ + customExportCount: globalThis.rsc_custom_export_count, + loaderCount: globalThis.rsc_loader_count, + componentCount: globalThis.rsc_component_count, + }); + })(); + + export { clientLoader }; + + export const ServerComponent = (() => { + globalThis.rsc_component_count = (globalThis.rsc_component_count || 0) + 1; + return () => { + return ( + <> +

RSC Server-First Module Count

+ +

Go to RSC Route B

+ + ); + }; + })(); + `, + "app/routes/rsc-server-first.a/client.tsx": ` + "use client"; + + import { useLoaderData } from "react-router"; + + export const clientLoader = (() => { + globalThis.rsc_client_loader_count = (globalThis.rsc_client_loader_count || 0) + 1; + return async ({ serverLoader }) => { + const loaderData = await serverLoader(); + return { + loaderCount: loaderData.loaderCount, + clientLoaderCount: globalThis.rsc_client_loader_count, + serverCustomExportCount: loaderData.customExportCount, + clientCustomExportCount: globalThis.rsc_custom_export_count, + serverComponentCount: loaderData.componentCount, + }; + }; + })(); + clientLoader.hydrate = true; + + export function ModuleCounts() { + const loaderData = useLoaderData(); + return ( + <> +

Loader count: {loaderData.loaderCount}

+

Client loader count: {loaderData.clientLoaderCount}

+

Server custom export count: {loaderData.serverCustomExportCount}

+

Client custom export count: {loaderData.clientCustomExportCount}

+

Server component count: {loaderData.serverComponentCount}

+ + ); + } + `, + "app/routes/rsc-server-first.b.tsx": ` + import { Link } from "react-router"; + + import { customExport } from "./rsc-server-first.a/route"; + + // Ensure custom export is used in the client build in this route + export const handle = customExport; + + export function ServerComponent() { + return customExport && ( + <> +

RSC Route B

+

This route imports the route module from RSC Route A, so could potentially cause code duplication.

+

Go to RSC Route A

+ + ); + } + `, + } + : {}), + }, + }); + + appFixture = await createAppFixture(fixture); + }); + + test.afterAll(() => { + appFixture.close(); + }); + + let logs: string[] = []; + + test.beforeEach(({ page }) => { + page.on("console", (msg) => { + logs.push(msg.text()); + }); + }); + + test.afterEach(() => { + expect(logs).toHaveLength(0); + }); + + test("Client-first routes", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + + let pageErrors: unknown[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await app.goto(`/client-first/b`, true); + expect(pageErrors).toEqual([]); + + await app.clickLink("/client-first/a"); + await page.waitForSelector("[data-loader-count]"); + expect(await page.locator("[data-loader-count]").textContent()).toBe( + "1", + ); + expect( + await page.locator("[data-client-loader-count]").textContent(), + ).toBe("1"); + expect( + await page.locator("[data-server-custom-export-count]").textContent(), + ).toBe( + templateName.includes("rsc") + ? // In RSC, custom exports are present in both the react-server and react-client + // environments (so they're available to be imported by both), + // which means the Node server actually gets 2 copies + "2" + : "1", + ); + expect( + await page.locator("[data-client-custom-export-count]").textContent(), + ).toBe("1"); + expect( + await page.locator("[data-server-component-count]").textContent(), + ).toBe("1"); + expect( + await page.locator("[data-client-component-count]").textContent(), + ).toBe("1"); + expect(pageErrors).toEqual([]); + }); + + test("Server-first routes", async ({ page }) => { + test.skip( + !templateName.includes("rsc"), + "Server-first routes are an RSC-only feature", + ); + + let app = new PlaywrightFixture(appFixture, page); + + let pageErrors: unknown[] = []; + page.on("pageerror", (error) => pageErrors.push(error)); + + await app.goto(`/rsc-server-first/b`, true); + expect(pageErrors).toEqual([]); + + await app.clickLink("/rsc-server-first/a"); + await page.waitForSelector("[data-loader-count]"); + expect(await page.locator("[data-loader-count]").textContent()).toBe( + "1", + ); + expect( + await page.locator("[data-client-loader-count]").textContent(), + ).toBe("1"); + expect( + await page.locator("[data-server-custom-export-count]").textContent(), + ).toBe( + // In RSC, custom exports are present in both the react-server and react-client + // environments (so they're available to be imported by both), + // which means the Node server actually gets 2 copies + "2", + ); + expect( + await page.locator("[data-client-custom-export-count]").textContent(), + ).toBe("1"); + expect( + await page.locator("[data-server-component-count]").textContent(), + ).toBe("1"); + expect(pageErrors).toEqual([]); + }); + }); + } +}); diff --git a/tests/react-router-framework/integration/defer-loader-test.ts b/tests/react-router-framework/integration/defer-loader-test.ts new file mode 100644 index 00000000..9fc9beb6 --- /dev/null +++ b/tests/react-router-framework/integration/defer-loader-test.ts @@ -0,0 +1,110 @@ +import { test, expect } from "@playwright/test"; + +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; + +let fixture: Fixture; +let appFixture: AppFixture; + +test.describe("deferred loaders", () => { + test.beforeAll(async () => { + fixture = await createFixture({ + files: { + "app/routes/_index.tsx": js` + import { useLoaderData, Link } from "react-router"; + export default function Index() { + return ( +
+ Redirect + Direct Promise Access +
+ ) + } + `, + + "app/routes/redirect.tsx": js` + import { data } from 'react-router'; + export function loader() { + return data( + { food: "pizza" }, + { + status: 301, + headers: { + Location: "/?redirected" + } + } + ); + } + export default function Redirect() { + return null; + } + `, + + "app/routes/direct-promise-access.tsx": js` + import * as React from "react"; + import { useLoaderData, Link, Await } from "react-router"; + export function loader() { + return { + bar: new Promise(async (resolve, reject) => { + resolve("hamburger"); + }), + }; + } + let count = 0; + export default function Index() { + let {bar} = useLoaderData(); + React.useEffect(() => { + let aborted = false; + bar.then((data) => { + if (aborted) return; + document.getElementById("content").innerHTML = data + " " + (++count); + document.getElementById("content").setAttribute("data-done", ""); + }); + return () => { + aborted = true; + }; + }, [bar]); + return ( +
+ Waiting for client hydration.... +
+ ) + } + `, + }, + }); + + appFixture = await createAppFixture(fixture); + }); + + test.afterAll(async () => appFixture.close()); + + test("deferred response can redirect on document request", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/redirect"); + await page.waitForURL(/\?redirected/); + }); + + test("deferred response can redirect on transition", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/"); + await app.clickLink("/redirect"); + await page.waitForURL(/\?redirected/); + }); + + test("can directly access result from deferred promise on document request", async ({ + page, + }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/direct-promise-access"); + let element = await page.waitForSelector("[data-done]"); + expect(await element.innerText()).toMatch("hamburger 1"); + }); +}); diff --git a/tests/react-router-framework/integration/defer-test.ts b/tests/react-router-framework/integration/defer-test.ts new file mode 100644 index 00000000..945683bc --- /dev/null +++ b/tests/react-router-framework/integration/defer-test.ts @@ -0,0 +1,699 @@ +import { test, expect } from "@playwright/test"; + +import { PlaywrightFixture } from "./helpers/playwright-fixture.js"; +import type { Fixture, AppFixture } from "./helpers/create-fixture.js"; +import { + createAppFixture, + createFixture, + js, +} from "./helpers/create-fixture.js"; + +const ROOT_ID = "ROOT_ID"; +const INDEX_ID = "INDEX_ID"; +const DEFERRED_ID = "DEFERRED_ID"; +const RESOLVED_DEFERRED_ID = "RESOLVED_DEFERRED_ID"; +const FALLBACK_ID = "FALLBACK_ID"; +const ERROR_ID = "ERROR_ID"; +const ERROR_BOUNDARY_ID = "ERROR_BOUNDARY_ID"; +const MANUAL_RESOLVED_ID = "MANUAL_RESOLVED_ID"; +const MANUAL_FALLBACK_ID = "MANUAL_FALLBACK_ID"; +const MANUAL_ERROR_ID = "MANUAL_ERROR_ID"; + +let originalConsoleError: typeof console.error; + +declare global { + var __deferredManualResolveCache: { + nextId: number; + deferreds: Record< + string, + { resolve: (value: any) => void; reject: (error: Error) => void } + >; + }; +} + +function counterHtml(id: string, val: number) { + return `

${val}

`; +} + +const deferredHTMLStartString = "